feat: drop empty states and instead render nothing (#17049)
Dropping the empty state as currently seen in production. ## Before <img width="3738" height="2442" alt="CleanShot 2026-01-09 at 16 09 37@2x" src="https://github.com/user-attachments/assets/ed6e2132-4a07-4673-ab59-dea37e9949e9" /> ## After https://github.com/user-attachments/assets/32566a17-d5b6-4d62-80d5-144ba0050276
This commit is contained in:
committed by
GitHub
parent
9a2f18660f
commit
b906b4353c
+1
-23
@@ -20,15 +20,7 @@ import { generateFieldWidgetInstanceId } from '@/page-layout/widgets/field/utils
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
|
||||
type FieldWidgetMorphRelationCardProps = {
|
||||
fieldDefinition: FieldDefinition<FieldMorphRelationMetadata>;
|
||||
@@ -115,21 +107,7 @@ export const FieldWidgetMorphRelationCard = ({
|
||||
);
|
||||
|
||||
if (validRecords.length === 0) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noRecord" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{t`No related records`}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+11
-35
@@ -4,14 +4,6 @@ import { type FieldDefinition } from '@/object-record/record-field/ui/types/Fiel
|
||||
import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
@@ -44,38 +36,22 @@ export const FieldWidgetMorphRelationField = ({
|
||||
morphRelations: fieldMetadata.morphRelations,
|
||||
});
|
||||
|
||||
if (recordsWithObjectNameSingular.length > 0) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<StyledRelationChipsContainer>
|
||||
{recordsWithObjectNameSingular.map((morphItem, index) => (
|
||||
<RecordChip
|
||||
key={morphItem.value?.id ?? index}
|
||||
objectNameSingular={morphItem.objectNameSingular}
|
||||
record={morphItem.value}
|
||||
/>
|
||||
))}
|
||||
</StyledRelationChipsContainer>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
if (recordsWithObjectNameSingular.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noRecord" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{t`No related records`}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
<StyledRelationChipsContainer>
|
||||
{recordsWithObjectNameSingular.map((morphItem, index) => (
|
||||
<RecordChip
|
||||
key={morphItem.value?.id ?? index}
|
||||
objectNameSingular={morphItem.objectNameSingular}
|
||||
record={morphItem.value}
|
||||
/>
|
||||
))}
|
||||
</StyledRelationChipsContainer>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
|
||||
+1
-23
@@ -18,15 +18,7 @@ import { generateFieldWidgetInstanceId } from '@/page-layout/widgets/field/utils
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
|
||||
type FieldWidgetRelationCardProps = {
|
||||
fieldDefinition: FieldDefinition<FieldRelationMetadata>;
|
||||
@@ -112,21 +104,7 @@ export const FieldWidgetRelationCard = ({
|
||||
: [];
|
||||
|
||||
if (records.length === 0) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noRecord" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{t`No related records`}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+15
-35
@@ -3,15 +3,7 @@ import { type FieldDefinition } from '@/object-record/record-field/ui/types/Fiel
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
@@ -41,7 +33,15 @@ export const FieldWidgetRelationField = ({
|
||||
const relationObjectNameSingular =
|
||||
fieldMetadata.relationObjectMetadataNameSingular;
|
||||
|
||||
if (isOneToMany && Array.isArray(relationValue)) {
|
||||
if (isOneToMany && !Array.isArray(relationValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isOneToMany && !isDefined(relationValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isOneToMany) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
@@ -59,35 +59,15 @@ export const FieldWidgetRelationField = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOneToMany && isDefined(relationValue)) {
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<StyledRelationChipsContainer>
|
||||
<RecordChip
|
||||
objectNameSingular={relationObjectNameSingular}
|
||||
record={relationValue}
|
||||
/>
|
||||
</StyledRelationChipsContainer>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RightDrawerProvider value={{ isInRightDrawer }}>
|
||||
<StyledContainer>
|
||||
<AnimatedPlaceholderEmptyContainer
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||
>
|
||||
<AnimatedPlaceholder type="noRecord" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{t`No related records`}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
<StyledRelationChipsContainer>
|
||||
<RecordChip
|
||||
objectNameSingular={relationObjectNameSingular}
|
||||
record={relationValue}
|
||||
/>
|
||||
</StyledRelationChipsContainer>
|
||||
</StyledContainer>
|
||||
</RightDrawerProvider>
|
||||
);
|
||||
|
||||
-195
@@ -1762,103 +1762,6 @@ export const OneToManyRelationCardWidget: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ManyToOneRelationCardWidgetEmpty: Story = {
|
||||
render: () => {
|
||||
const widget: PageLayoutWidget = {
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-relation-card-empty',
|
||||
pageLayoutTabId: TAB_ID_OVERVIEW,
|
||||
type: WidgetType.FIELD,
|
||||
title: 'Account Owner',
|
||||
objectMetadataId: companyObjectMetadataItem.id,
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition',
|
||||
row: 4,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 2,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: accountOwnerField.id,
|
||||
layout: 'CARD',
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
const initializeState = (snapshot: MutableSnapshot) => {
|
||||
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
pageLayoutData,
|
||||
);
|
||||
snapshot.set(
|
||||
pageLayoutDraftComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
pageLayoutData,
|
||||
);
|
||||
snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), {
|
||||
...mockCompanyRecord,
|
||||
accountOwner: null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '400px', padding: '20px' }}>
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<CoreClientProviderWrapper>
|
||||
<PageLayoutTestWrapper initializeState={initializeState}>
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
layoutType: PageLayoutType.RECORD_PAGE,
|
||||
targetRecordIdentifier: {
|
||||
id: TEST_RECORD_ID,
|
||||
targetObjectNameSingular:
|
||||
companyObjectMetadataItem.nameSingular,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PageLayoutContentProvider
|
||||
value={{
|
||||
layoutMode: 'vertical-list',
|
||||
tabId: 'fields',
|
||||
}}
|
||||
>
|
||||
<WidgetComponentInstanceContext.Provider
|
||||
value={{ instanceId: widget.id }}
|
||||
>
|
||||
<FieldWidget widget={widget} />
|
||||
</WidgetComponentInstanceContext.Provider>
|
||||
</PageLayoutContentProvider>
|
||||
</LayoutRenderingProvider>
|
||||
</PageLayoutTestWrapper>
|
||||
</CoreClientProviderWrapper>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await waitFor(() => {
|
||||
const emptyPlaceholder = canvas.queryByText('No related records');
|
||||
expect(emptyPlaceholder).toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const TimelineActivityRelationCardWidget: Story = {
|
||||
render: () => {
|
||||
const widget: PageLayoutWidget = {
|
||||
@@ -1966,101 +1869,3 @@ export const TimelineActivityRelationCardWidget: Story = {
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const TimelineActivityRelationCardWidgetEmpty: Story = {
|
||||
render: () => {
|
||||
const widget: PageLayoutWidget = {
|
||||
__typename: 'PageLayoutWidget',
|
||||
id: 'widget-timeline-activity-relation-card-empty',
|
||||
pageLayoutTabId: TAB_ID_OVERVIEW,
|
||||
type: WidgetType.FIELD,
|
||||
title: 'Workspace Member',
|
||||
objectMetadataId: timelineActivityObjectMetadataItem.id,
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition',
|
||||
row: 12,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 2,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: timelineActivityWorkspaceMemberField.id,
|
||||
layout: 'CARD',
|
||||
},
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
const initializeState = (snapshot: MutableSnapshot) => {
|
||||
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
timelineActivityObjectMetadataItem.id,
|
||||
);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
pageLayoutData,
|
||||
);
|
||||
snapshot.set(
|
||||
pageLayoutDraftComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
}),
|
||||
pageLayoutData,
|
||||
);
|
||||
snapshot.set(recordStoreFamilyState(TEST_TIMELINE_ACTIVITY_RECORD_ID), {
|
||||
...mockTimelineActivityRecord,
|
||||
workspaceMember: null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '400px', padding: '20px' }}>
|
||||
<JestMetadataAndApolloMocksWrapper>
|
||||
<CoreClientProviderWrapper>
|
||||
<PageLayoutTestWrapper initializeState={initializeState}>
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
isInRightDrawer: false,
|
||||
layoutType: PageLayoutType.RECORD_PAGE,
|
||||
targetRecordIdentifier: {
|
||||
id: TEST_TIMELINE_ACTIVITY_RECORD_ID,
|
||||
targetObjectNameSingular:
|
||||
timelineActivityObjectMetadataItem.nameSingular,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PageLayoutContentProvider
|
||||
value={{
|
||||
layoutMode: 'vertical-list',
|
||||
tabId: 'fields',
|
||||
}}
|
||||
>
|
||||
<WidgetComponentInstanceContext.Provider
|
||||
value={{ instanceId: widget.id }}
|
||||
>
|
||||
<FieldWidget widget={widget} />
|
||||
</WidgetComponentInstanceContext.Provider>
|
||||
</PageLayoutContentProvider>
|
||||
</LayoutRenderingProvider>
|
||||
</PageLayoutTestWrapper>
|
||||
</CoreClientProviderWrapper>
|
||||
</JestMetadataAndApolloMocksWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const emptyPlaceholder = await canvas.findByText('No related records');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(emptyPlaceholder).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user