feat: use side-column variant for all widgets on mobile and side panel (#16822)

Closes [1957](https://github.com/twentyhq/core-team-issues/issues/1957).

`getWidgetCardVariant` now returns `side-panel` for mobile and right
drawer instead of returning `record-page`.

Added stories to WidgetRenderer.stories.tsx because WidgetRenderer is
where getWidgetCardVariant is called and the variant is applied. These
stories test the actual behavior (mobile/side panel triggering
side-column variant) rather than just visual appearance, following the
existing pattern of individual behavior-testing stories in this file.
This commit is contained in:
Abdullah.
2025-12-29 18:02:52 +05:00
committed by GitHub
parent bb18f78f1b
commit bee58fd03d
3 changed files with 206 additions and 1 deletions
@@ -18,6 +18,8 @@ import { getWidgetCardVariant } from '@/page-layout/widgets/utils/getWidgetCardV
import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard';
import { WidgetCardContent } from '@/page-layout/widgets/widget-card/components/WidgetCardContent';
import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader';
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyState';
import { useTheme } from '@emotion/react';
@@ -60,6 +62,8 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
const { layoutMode } = usePageLayoutContentContext();
const { isInPinnedTab } = useIsInPinnedTab();
const { isInRightDrawer } = useLayoutRenderingContext();
const isMobile = useIsMobile();
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
@@ -99,6 +103,8 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
layoutMode,
isInPinnedTab,
pageLayoutType: currentPageLayout.type,
isMobile,
isInRightDrawer,
});
const actions = useWidgetActions({ widget });
@@ -1195,6 +1195,201 @@ export const WithOneToManyRelationFieldWidget: Story = {
},
};
export const OnMobile: Story = {
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
docs: {
description: {
story:
'Widget on mobile viewport should use side-column variant instead of record-page variant.',
},
},
},
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
id: 'widget-mobile',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
title: 'Mobile Widget',
objectMetadataId: companyObjectMetadataItem.id,
gridPosition: {
__typename: 'GridPosition',
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 3,
},
configuration: {
__typename: 'AggregateChartConfiguration',
graphType: GraphType.AGGREGATE,
aggregateOperation: AggregateOperations.COUNT,
aggregateFieldMetadataId: idField.id,
displayDataLabel: true,
},
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,
PageLayoutType.RECORD_PAGE,
);
snapshot.set(
pageLayoutPersistedComponentState.atomFamily({
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
}),
pageLayoutData,
);
snapshot.set(
pageLayoutDraftComponentState.atomFamily({
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
}),
pageLayoutData,
);
};
return (
<div style={{ width: '100%', 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: 'grid',
tabId: TAB_ID_OVERVIEW,
}}
>
<WidgetComponentInstanceContext.Provider
value={{
instanceId: 'widget-mobile',
}}
>
<WidgetRenderer widget={widget} />
</WidgetComponentInstanceContext.Provider>
</PageLayoutContentProvider>
</LayoutRenderingProvider>
</PageLayoutTestWrapper>
</CoreClientProviderWrapper>
</JestMetadataAndApolloMocksWrapper>
</div>
);
},
};
export const InSidePanel: Story = {
parameters: {
docs: {
description: {
story:
'Widget in side panel (right drawer) should use side-column variant instead of record-page variant.',
},
},
},
render: () => {
const widget: PageLayoutWidget = {
__typename: 'PageLayoutWidget',
id: 'widget-side-panel',
pageLayoutTabId: TAB_ID_OVERVIEW,
type: WidgetType.GRAPH,
title: 'Side Panel Widget',
objectMetadataId: companyObjectMetadataItem.id,
gridPosition: {
__typename: 'GridPosition',
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 3,
},
configuration: {
__typename: 'AggregateChartConfiguration',
graphType: GraphType.AGGREGATE,
aggregateOperation: AggregateOperations.COUNT,
aggregateFieldMetadataId: idField.id,
displayDataLabel: true,
},
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,
PageLayoutType.RECORD_PAGE,
);
snapshot.set(
pageLayoutPersistedComponentState.atomFamily({
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
}),
pageLayoutData,
);
snapshot.set(
pageLayoutDraftComponentState.atomFamily({
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
}),
pageLayoutData,
);
};
return (
<div style={{ width: '400px', padding: '20px' }}>
<JestMetadataAndApolloMocksWrapper>
<CoreClientProviderWrapper>
<PageLayoutTestWrapper initializeState={initializeState}>
<LayoutRenderingProvider
value={{
isInRightDrawer: true,
layoutType: PageLayoutType.RECORD_PAGE,
targetRecordIdentifier: {
id: TEST_RECORD_ID,
targetObjectNameSingular:
companyObjectMetadataItem.nameSingular,
},
}}
>
<PageLayoutContentProvider
value={{
layoutMode: 'grid',
tabId: TAB_ID_OVERVIEW,
}}
>
<WidgetComponentInstanceContext.Provider
value={{
instanceId: 'widget-side-panel',
}}
>
<WidgetRenderer widget={widget} />
</WidgetComponentInstanceContext.Provider>
</PageLayoutContentProvider>
</LayoutRenderingProvider>
</PageLayoutTestWrapper>
</CoreClientProviderWrapper>
</JestMetadataAndApolloMocksWrapper>
</div>
);
},
};
export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
parameters: {
catalog: {
@@ -6,12 +6,16 @@ type GetWidgetCardVariantParams = {
layoutMode: PageLayoutTabLayoutMode;
isInPinnedTab: boolean;
pageLayoutType: PageLayoutType | null;
isMobile: boolean;
isInRightDrawer: boolean;
};
export const getWidgetCardVariant = ({
layoutMode,
isInPinnedTab,
pageLayoutType,
isMobile,
isInRightDrawer,
}: GetWidgetCardVariantParams): WidgetCardVariant => {
if (pageLayoutType === PageLayoutType.DASHBOARD) {
return 'dashboard';
@@ -21,7 +25,7 @@ export const getWidgetCardVariant = ({
return 'canvas';
}
if (isInPinnedTab) {
if (isInPinnedTab || isMobile || isInRightDrawer) {
return 'side-column';
}