From bfe1f47065965f97cb5de9bc239384453ce13d4d Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Thu, 6 Nov 2025 18:16:42 +0100 Subject: [PATCH] Create old fields design widget (#15645) In this PR: - Pass `layoutMode` and `tabId` via PageLayoutContentContext provider - Getting `pageLayoutType` from the current page layout - Getting isInPinnedTab through `useIsInPinnedTab` hook ## Before CleanShot 2025-11-06 at 14 22
44@2x ## After CleanShot 2025-11-06 at 14 20
38@2x --------- Co-authored-by: Lucas Bordeau --- .../page-layout/PageLayoutMainContent.tsx | 31 +++++++ .../components/PageLayoutCanvasViewer.tsx | 8 +- .../components/PageLayoutContent.tsx | 26 +++--- .../components/PageLayoutGridLayout.tsx | 6 +- .../components/PageLayoutLeftPanel.tsx | 20 +++- .../components/PageLayoutRendererContent.tsx | 8 +- .../PageLayoutVerticalListEditor.tsx | 8 +- .../PageLayoutVerticalListViewer.tsx | 8 +- .../contexts/PageLayoutContentContext.ts | 12 +++ .../hooks/useCurrentPageLayoutOrThrow.ts | 12 +++ ...ageLayoutTabHasDefinedLayoutModeOrThrow.ts | 15 +++ .../page-layout/utils/getTabLayoutMode.ts | 22 +++++ .../widgets/components/WidgetPlaceholder.tsx | 15 ++- .../widgets/components/WidgetRenderer.tsx | 36 +++++--- .../__stories__/WidgetPlaceholder.stories.tsx | 40 +++++++- .../__stories__/WidgetRenderer.stories.tsx | 92 ++++++++++--------- .../widgets/hooks/useIsInPinnedTab.ts | 24 +++++ .../widget-card/components/WidgetCard.tsx | 22 ++++- .../components/WidgetCardContent.tsx | 17 +++- .../__stories__/WidgetCard.stories.tsx | 13 +++ 20 files changed, 328 insertions(+), 107 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/PageLayoutMainContent.tsx create mode 100644 packages/twenty-front/src/modules/page-layout/contexts/PageLayoutContentContext.ts create mode 100644 packages/twenty-front/src/modules/page-layout/hooks/useCurrentPageLayoutOrThrow.ts create mode 100644 packages/twenty-front/src/modules/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow.ts create mode 100644 packages/twenty-front/src/modules/page-layout/utils/getTabLayoutMode.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/hooks/useIsInPinnedTab.ts diff --git a/packages/twenty-front/src/modules/page-layout/PageLayoutMainContent.tsx b/packages/twenty-front/src/modules/page-layout/PageLayoutMainContent.tsx new file mode 100644 index 0000000000..9bea1d4e26 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/PageLayoutMainContent.tsx @@ -0,0 +1,31 @@ +import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent'; +import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; +import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode'; + +type PageLayoutMainContentProps = { + tabId: string; +}; + +export const PageLayoutMainContent = ({ + tabId, +}: PageLayoutMainContentProps) => { + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + + const activeTab = currentPageLayout.tabs.find((tab) => tab.id === tabId); + const layoutMode = getTabLayoutMode({ + tab: activeTab, + pageLayoutType: currentPageLayout.type, + }); + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutCanvasViewer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutCanvasViewer.tsx index 18e4d76f17..0840a58c76 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutCanvasViewer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutCanvasViewer.tsx @@ -1,7 +1,7 @@ import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; -import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; +import { type PageLayoutWidget } from '~/generated/graphql'; const StyledCanvasContainer = styled.div` display: grid; @@ -23,11 +23,7 @@ export const PageLayoutCanvasViewer = ({ return ( - + ); }; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx index 49ecde4f6e..32c3e8876d 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx @@ -2,30 +2,29 @@ import { PageLayoutCanvasViewer } from '@/page-layout/components/PageLayoutCanva import { PageLayoutGridLayout } from '@/page-layout/components/PageLayoutGridLayout'; import { PageLayoutVerticalListEditor } from '@/page-layout/components/PageLayoutVerticalListEditor'; import { PageLayoutVerticalListViewer } from '@/page-layout/components/PageLayoutVerticalListViewer'; +import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext'; import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; import { useReorderPageLayoutWidgets } from '@/page-layout/hooks/useReorderPageLayoutWidgets'; import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; +import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; import { FeatureFlagKey } from '~/generated/graphql'; -const StyledContainer = styled.div` +const StyledContainer = styled.div<{ isInPinnedTab: boolean }>` background: ${({ theme }) => theme.background.primary}; box-sizing: border-box; flex: 1; min-height: 100%; position: relative; - padding: ${({ theme }) => theme.spacing(2)}; width: 100%; + padding: ${({ theme, isInPinnedTab }) => + isInPinnedTab ? 0 : theme.spacing(2)}; `; -type PageLayoutContentProps = { - tabId: string; -}; - -export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => { +export const PageLayoutContent = () => { const isRecordPageEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_ENABLED, ); @@ -35,18 +34,21 @@ export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => { ); const { currentPageLayout } = useCurrentPageLayout(); + const { tabId } = usePageLayoutContentContext(); + const { reorderWidgets } = useReorderPageLayoutWidgets(tabId); const activeTab = currentPageLayout?.tabs.find((tab) => tab.id === tabId); + const { layoutMode } = usePageLayoutContentContext(); + const { isInPinnedTab } = useIsInPinnedTab(); + if (!isDefined(currentPageLayout) || !isDefined(activeTab)) { return null; } - const isCanvasLayout = - isRecordPageEnabled && activeTab.layoutMode === 'canvas'; - const isVerticalList = - isRecordPageEnabled && activeTab.layoutMode === 'vertical-list'; + const isCanvasLayout = isRecordPageEnabled && layoutMode === 'canvas'; + const isVerticalList = isRecordPageEnabled && layoutMode === 'vertical-list'; if (isCanvasLayout) { return ; @@ -54,7 +56,7 @@ export const PageLayoutContent = ({ tabId }: PageLayoutContentProps) => { if (isVerticalList) { return ( - + {isPageLayoutInEditMode ? ( { {item.type === 'placeholder' ? ( ) : ( - + )} ))} diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutLeftPanel.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutLeftPanel.tsx index bf44f8c08b..db2df23925 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutLeftPanel.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutLeftPanel.tsx @@ -1,6 +1,8 @@ import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent'; +import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; +import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; @@ -21,6 +23,15 @@ export const PageLayoutLeftPanel = ({ return null; } + const pinnedTab = currentPageLayout.tabs.find( + (tab) => tab.id === pinnedLeftTabId, + ); + + const layoutMode = getTabLayoutMode({ + tab: pinnedTab, + pageLayoutType: currentPageLayout.type, + }); + return ( - + + + ); }; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx index cb52dba85d..75a6e18977 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRendererContent.tsx @@ -1,4 +1,5 @@ -import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent'; +import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu'; +import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel'; import { PageLayoutTabHeader } from '@/page-layout/components/PageLayoutTabHeader'; import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList'; @@ -6,6 +7,7 @@ import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabL import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab'; import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; import { useReorderPageLayoutTabs } from '@/page-layout/hooks/useReorderPageLayoutTabs'; +import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent'; import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; @@ -18,8 +20,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu'; -import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; @@ -124,7 +124,7 @@ export const PageLayoutRendererContent = () => { defaultEnableXScroll={false} > {isDefined(activeTabId) && ( - + )} diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx index 88f72d52ff..1309f2be56 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListEditor.tsx @@ -9,7 +9,7 @@ import { type DropResult, } from '@hello-pangea/dnd'; import { useId } from 'react'; -import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; +import { type PageLayoutWidget } from '~/generated/graphql'; const StyledVerticalListContainer = styled.div` display: flex; @@ -67,11 +67,7 @@ export const PageLayoutVerticalListEditor = ({ > {/* eslint-disable-next-line react/jsx-props-no-spreading */}
- +
)} diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx index f3da18ae92..b33c198696 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutVerticalListViewer.tsx @@ -1,6 +1,6 @@ import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; import styled from '@emotion/styled'; -import { PageLayoutType, type PageLayoutWidget } from '~/generated/graphql'; +import { type PageLayoutWidget } from '~/generated/graphql'; const StyledVerticalListContainer = styled.div` display: flex; @@ -19,11 +19,7 @@ export const PageLayoutVerticalListViewer = ({ {widgets.map((widget) => (
- +
))}
diff --git a/packages/twenty-front/src/modules/page-layout/contexts/PageLayoutContentContext.ts b/packages/twenty-front/src/modules/page-layout/contexts/PageLayoutContentContext.ts new file mode 100644 index 0000000000..589dcdddd2 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/contexts/PageLayoutContentContext.ts @@ -0,0 +1,12 @@ +import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode'; +import { createRequiredContext } from '~/utils/createRequiredContext'; + +export type PageLayoutContentContextType = { + tabId: string; + layoutMode: PageLayoutTabLayoutMode; +}; + +export const [PageLayoutContentProvider, usePageLayoutContentContext] = + createRequiredContext( + 'PageLayoutContentContext', + ); diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useCurrentPageLayoutOrThrow.ts b/packages/twenty-front/src/modules/page-layout/hooks/useCurrentPageLayoutOrThrow.ts new file mode 100644 index 0000000000..6d3e720027 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/hooks/useCurrentPageLayoutOrThrow.ts @@ -0,0 +1,12 @@ +import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout'; +import { isDefined } from 'twenty-shared/utils'; + +export const useCurrentPageLayoutOrThrow = () => { + const { currentPageLayout } = useCurrentPageLayout(); + + if (!isDefined(currentPageLayout)) { + throw new Error('No current page layout found'); + } + + return { currentPageLayout }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow.ts b/packages/twenty-front/src/modules/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow.ts new file mode 100644 index 0000000000..e6e7da68b7 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow.ts @@ -0,0 +1,15 @@ +import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; +import { isNonEmptyString } from '@sniptt/guards'; + +type AssertPageLayoutTabHasDefinedLayoutModeOrThrow = ( + tab: PageLayoutTab | undefined, +) => asserts tab is PageLayoutTab & { + layoutMode: NonNullable; +}; + +export const assertPageLayoutTabHasDefinedLayoutModeOrThrow: AssertPageLayoutTabHasDefinedLayoutModeOrThrow = + (tab) => { + if (!isNonEmptyString(tab?.layoutMode)) { + throw new Error('Tab layout mode is not defined'); + } + }; diff --git a/packages/twenty-front/src/modules/page-layout/utils/getTabLayoutMode.ts b/packages/twenty-front/src/modules/page-layout/utils/getTabLayoutMode.ts new file mode 100644 index 0000000000..3fb1398837 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/utils/getTabLayoutMode.ts @@ -0,0 +1,22 @@ +import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; +import { type PageLayoutTabLayoutMode } from '@/page-layout/types/PageLayoutTabLayoutMode'; +import { assertPageLayoutTabHasDefinedLayoutModeOrThrow } from '@/page-layout/utils/assertPageLayoutTabHasDefinedLayoutModeOrThrow'; +import { PageLayoutType } from '~/generated/graphql'; + +type GetTabLayoutModeParams = { + tab: PageLayoutTab | undefined; + pageLayoutType: PageLayoutType; +}; + +export const getTabLayoutMode = ({ + tab, + pageLayoutType, +}: GetTabLayoutModeParams): PageLayoutTabLayoutMode => { + if (pageLayoutType === PageLayoutType.RECORD_PAGE) { + assertPageLayoutTabHasDefinedLayoutModeOrThrow(tab); + + return tab.layoutMode; + } + + return 'grid'; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx index 7691b3cdea..e01196316c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetPlaceholder.tsx @@ -1,8 +1,11 @@ import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; +import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; +import { useIsInPinnedTab } from '@/page-layout/widgets/hooks/useIsInPinnedTab'; import { WidgetCard } from '@/page-layout/widgets/widget-card/components/WidgetCard'; import { WidgetCardHeader } from '@/page-layout/widgets/widget-card/components/WidgetCardHeader'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -17,7 +20,6 @@ import { AnimatedPlaceholderEmptyTitle, EMPTY_PLACEHOLDER_TRANSITION_PROPS, } from 'twenty-ui/layout'; -import { PageLayoutType } from '~/generated/graphql'; export const WidgetPlaceholder = () => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( @@ -33,6 +35,10 @@ export const WidgetPlaceholder = () => { const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu(); + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + const { layoutMode } = usePageLayoutContentContext(); + const { isInPinnedTab } = useIsInPinnedTab(); + const handleClick = () => { if (!isPageLayoutInEditMode) { setIsPageLayoutInEditMode(true); @@ -44,11 +50,12 @@ export const WidgetPlaceholder = () => { return ( { +export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { const theme = useTheme(); const { deletePageLayoutWidget } = useDeletePageLayoutWidget(); const { handleEditWidget } = useEditPageLayoutWidget(); @@ -49,6 +45,13 @@ export const WidgetRenderer = ({ const { hasAccess, restriction } = useWidgetPermissions(widget); + const { layoutMode } = usePageLayoutContentContext(); + const { isInPinnedTab } = useIsInPinnedTab(); + + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + + const showHeader = layoutMode !== 'canvas' && !isInPinnedTab; + const handleClick = () => { handleEditWidget({ widgetId: widget.id, @@ -73,15 +76,16 @@ export const WidgetRenderer = ({ return ( - {layoutMode !== 'canvas' && ( + {showHeader && ( {hasAccess && } - {!hasAccess && pageLayoutType === PageLayoutType.DASHBOARD && ( + {!hasAccess && currentPageLayout.type === PageLayoutType.DASHBOARD && ( = { title: 'Modules/PageLayout/Widgets/WidgetPlaceholder', component: WidgetPlaceholder, @@ -19,11 +37,29 @@ const meta: Meta = { generatedMockObjectMetadataItems, ); snapshot.set(shouldAppBeLoadingState, false); + snapshot.set( + pageLayoutPersistedComponentState.atomFamily({ + instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, + }), + mockPageLayout, + ); }; return ( - + + + + + ); }, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx index d0846812d2..f34c7b7b02 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx @@ -12,9 +12,15 @@ import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreCl import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper'; +import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; +import { + PAGE_LAYOUT_TEST_INSTANCE_ID, + PageLayoutTestWrapper, +} from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper'; +import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer'; import { generateGroupByQuery } from '@/page-layout/widgets/graph/utils/generateGroupByQuery'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { GraphOrderBy, GraphType, @@ -124,6 +130,21 @@ const meta: Meta = { generatedMockObjectMetadataItems, ); snapshot.set(shouldAppBeLoadingState, false); + snapshot.set( + pageLayoutPersistedComponentState.atomFamily({ + instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, + }), + { + id: PAGE_LAYOUT_TEST_INSTANCE_ID, + name: 'Mock Page Layout', + type: PageLayoutType.DASHBOARD, + objectMetadataId: companyObjectMetadataItem.id, + tabs: [], + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + deletedAt: null, + }, + ); }; return ( @@ -131,7 +152,26 @@ const meta: Meta = { - + + + + + @@ -183,11 +223,7 @@ export const WithNumberChart: Story = { }, render: (args) => (
- +
), }; @@ -222,11 +258,7 @@ export const WithGaugeChart: Story = { }, render: (args) => (
- +
), }; @@ -264,11 +296,7 @@ export const WithBarChart: Story = { }, render: (args) => (
- +
), }; @@ -310,11 +338,7 @@ export const SmallWidget: Story = { }, render: (args) => (
- +
), }; @@ -359,11 +383,7 @@ export const MediumWidget: Story = { }, render: (args) => (
- +
), }; @@ -408,11 +428,7 @@ export const LargeWidget: Story = { }, render: (args) => (
- +
), }; @@ -454,11 +470,7 @@ export const WideWidget: Story = { }, render: (args) => (
- +
), }; @@ -503,11 +515,7 @@ export const TallWidget: Story = { }, render: (args) => (
- +
), }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/hooks/useIsInPinnedTab.ts b/packages/twenty-front/src/modules/page-layout/widgets/hooks/useIsInPinnedTab.ts new file mode 100644 index 0000000000..d8bccf0404 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/hooks/useIsInPinnedTab.ts @@ -0,0 +1,24 @@ +import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext'; +import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; +import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode'; +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { isDefined } from 'twenty-shared/utils'; +import { useIsMobile } from 'twenty-ui/utilities'; + +export const useIsInPinnedTab = () => { + const isMobile = useIsMobile(); + + const { tabId } = usePageLayoutContentContext(); + const { isInRightDrawer } = useLayoutRenderingContext(); + const { currentPageLayout } = useCurrentPageLayoutOrThrow(); + + const { pinnedLeftTab } = getTabsByDisplayMode({ + pageLayout: currentPageLayout, + isMobile, + isInRightDrawer, + }); + + return { + isInPinnedTab: isDefined(pinnedLeftTab) && pinnedLeftTab.id === tabId, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx index 36426fb33d..22d88eddc2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx @@ -14,6 +14,7 @@ export type WidgetCardProps = { onClick?: () => void; isEditing: boolean; isDragging: boolean; + isInPinnedTab: boolean; className?: string; onMouseEnter?: () => void; onMouseLeave?: () => void; @@ -23,6 +24,7 @@ const StyledWidgetCard = styled.div<{ onClick?: () => void; pageLayoutType: PageLayoutType; layoutMode: PageLayoutTabLayoutMode; + isInPinnedTab: boolean; isPageLayoutInEditMode: boolean; isEditing: boolean; isDragging: boolean; @@ -41,6 +43,7 @@ const StyledWidgetCard = styled.div<{ isPageLayoutInEditMode, isEditing, isDragging, + isInPinnedTab, onClick, }) => { if (layoutMode === 'canvas') { @@ -50,7 +53,7 @@ const StyledWidgetCard = styled.div<{ } switch (pageLayoutType) { - case PageLayoutType.DASHBOARD: + case PageLayoutType.DASHBOARD: { return css` background: ${theme.background.secondary}; border: 1px solid ${theme.border.color.light}; @@ -85,8 +88,9 @@ const StyledWidgetCard = styled.div<{ border: 1px solid ${theme.color.blue} !important; `} `; + } - case PageLayoutType.RECORD_PAGE: + case PageLayoutType.RECORD_PAGE: { return css` background: ${theme.background.primary}; border: 1px solid transparent; @@ -120,10 +124,20 @@ const StyledWidgetCard = styled.div<{ ${theme.background.secondary}; border: 1px solid ${theme.color.blue} !important; `} + + ${isInPinnedTab && + !isPageLayoutInEditMode && + css` + border: none; + padding: 0; + border-radius: 0; + background: ${theme.background.secondary}; + `} `; + } default: - return ''; + return undefined; } }} `; @@ -135,6 +149,7 @@ export const WidgetCard = ({ onClick, isEditing, isDragging, + isInPinnedTab, className, onMouseEnter, onMouseLeave, @@ -151,6 +166,7 @@ export const WidgetCard = ({ isPageLayoutInEditMode={isPageLayoutInEditMode} isEditing={isEditing} isDragging={isDragging} + isInPinnedTab={isInPinnedTab} className={className} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx index 2f848a465e..731d6dabe5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardContent.tsx @@ -7,6 +7,8 @@ import { type PageLayoutType } from '~/generated/graphql'; export type WidgetCardContentProps = { children?: ReactNode; pageLayoutType: PageLayoutType; + isInPinnedTab: boolean; + isPageLayoutInEditMode: boolean; layoutMode: PageLayoutTabLayoutMode; className?: string; }; @@ -20,7 +22,13 @@ const StyledWidgetCardContent = styled.div` box-sizing: border-box; padding: ${({ theme }) => theme.spacing(2)}; - ${({ theme, pageLayoutType, layoutMode }) => { + ${({ + theme, + pageLayoutType, + layoutMode, + isPageLayoutInEditMode, + isInPinnedTab, + }) => { if (layoutMode === 'canvas') { return css` padding: 0; @@ -32,6 +40,13 @@ const StyledWidgetCardContent = styled.div` return css` border: 1px solid ${theme.border.color.medium}; border-radius: ${theme.border.radius.md}; + + ${isInPinnedTab && + !isPageLayoutInEditMode && + css` + border: none; + padding: 0; + `} `; default: diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx index b42003bf3c..798c0ed5b7 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/__stories__/WidgetCard.stories.tsx @@ -73,6 +73,7 @@ export const Default: Story = { args: { pageLayoutType: PageLayoutType.DASHBOARD, layoutMode: 'grid', + isInPinnedTab: false, isEditing: false, isDragging: false, }, @@ -80,6 +81,7 @@ export const Default: Story = { @@ -92,6 +94,8 @@ export const Default: Story = { Widget @@ -119,7 +123,9 @@ export const Catalog: CatalogStory = { name: 'contextVariant', values: [ 'Record Page - Default', + 'Record Page - Default - Pinned', 'Record Page - Restriction', + 'Record Page - Restriction - Pinned', 'Dashboard - Default', 'Dashboard - Restriction', ], @@ -128,11 +134,13 @@ export const Catalog: CatalogStory = { ? PageLayoutType.RECORD_PAGE : PageLayoutType.DASHBOARD; const hasRestriction = contextName.includes('Restriction'); + const isInPinnedTab = contextName.includes('Pinned'); return { pageLayoutType, contextVariant: contextName, hasRestriction, + isInPinnedTab, }; }, }, @@ -171,7 +179,9 @@ export const Catalog: CatalogStory = { const isReadMode = args.state === 'Read Mode'; const pageLayoutType = args.pageLayoutType || PageLayoutType.DASHBOARD; const layoutMode = args.layoutMode || 'grid'; + const isInPinnedTab = args.isInPinnedTab || false; const hasRestriction = args.hasRestriction || false; + const isPageLayoutInEditMode = false; return ( @@ -182,6 +192,7 @@ export const Catalog: CatalogStory = { isEditing={args.isEditing ?? false} pageLayoutType={pageLayoutType} layoutMode={layoutMode} + isInPinnedTab={isInPinnedTab} > = { Widget