From 5bcbe4359618f3acee8c53a89029092c03143f6d Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Fri, 16 Jan 2026 18:06:34 +0100 Subject: [PATCH] Various layout fixes so that RPL v1 look the same as production version (#17195) > [!WARNING] > Most of the quick fixes will be dropped once we release the feature and deprecate the old show pages code. This PR fixes padding and alignment issues so that the new Record Page Layout feature looks like the old show pages. ## This PR CleanShot 2026-01-16 at 17 24
40@2x CleanShot 2026-01-16 at 17 27
14@2x ## The production CleanShot 2026-01-16 at 17 25
11@2x CleanShot 2026-01-16 at 17 27
19@2x --- .../RecordDetailRecordsListItemContainer.tsx | 42 ++++++++++++-- .../RecordDetailRelationSection.stories.tsx | 55 ++++++++++++------- .../property-box/components/PropertyBox.tsx | 38 ++++++++++--- .../components/PageLayoutDispatcher.tsx | 2 + .../PageLayoutVerticalListViewer.tsx | 22 +++++++- .../getPageLayoutVerticalListViewerVariant.ts | 19 +++++++ .../hooks/useTemporaryFieldsConfiguration.ts | 2 +- .../PageLayoutVerticalListViewerVariant.ts | 1 + .../components/DashboardWidgetPlaceholder.tsx | 1 + .../widgets/components/WidgetRenderer.tsx | 7 ++- .../fields/components/FieldsWidget.tsx | 15 ++++- .../FieldsWidgetSectionContainer.tsx | 16 +----- .../widget-card/components/WidgetCard.tsx | 4 +- .../components/WidgetCardHeader.tsx | 21 +++++-- .../contexts/LayoutRenderingContext.tsx | 3 + 15 files changed, 189 insertions(+), 59 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/components/utils/getPageLayoutVerticalListViewerVariant.ts create mode 100644 packages/twenty-front/src/modules/page-layout/types/PageLayoutVerticalListViewerVariant.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer.tsx index 7586427243..e511cd331a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer.tsx @@ -1,13 +1,47 @@ +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; +import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; +import { PageLayoutType } from '~/generated/graphql'; -const StyledListItem = styled.div` +const StyledListItem = styled('div', { + shouldForwardProp: (prop) => + isPropValid(prop) && prop !== 'noHorizontalPadding', +})<{ noHorizontalPadding?: boolean }>` align-items: center; justify-content: space-between; gap: ${({ theme }) => theme.spacing(1)}; display: flex; height: ${({ theme }) => theme.spacing(10)}; - padding-left: ${({ theme }) => theme.spacing(3)}; - padding-right: ${({ theme }) => theme.spacing(2)}; + padding-left: ${({ theme, noHorizontalPadding }) => + noHorizontalPadding ? 0 : theme.spacing(3)}; + padding-right: ${({ theme, noHorizontalPadding }) => + noHorizontalPadding ? 0 : theme.spacing(2)}; `; -export { StyledListItem as RecordDetailRecordsListItemContainer }; +type RecordDetailRecordsListItemContainerProps = { + children: React.ReactNode; + className?: string; +}; + +/** + * TODO: Remove noHorizontalPadding logic once the traditional record show page is removed. + */ +export const RecordDetailRecordsListItemContainer = ({ + children, + className, +}: RecordDetailRecordsListItemContainerProps) => { + const layoutRenderingContext = useLayoutRenderingContext(); + + const isInRecordPageLayout = + layoutRenderingContext?.layoutType === PageLayoutType.RECORD_PAGE && + !layoutRenderingContext?.isLegacyRecordShowPage; + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/__stories__/RecordDetailRelationSection.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/__stories__/RecordDetailRelationSection.stories.tsx index b9d2d44964..f4ddd0f7b0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/__stories__/RecordDetailRelationSection.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/__stories__/RecordDetailRelationSection.stories.tsx @@ -12,7 +12,9 @@ import { getCompaniesMock } from '~/testing/mock-data/companies'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext'; import { RecordDetailRelationSection } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection'; +import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { ComponentDecorator } from 'twenty-ui/testing'; +import { PageLayoutType } from '~/generated/graphql'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { RightDrawerDecorator } from '~/testing/decorators/RightDrawerDecorator'; import { allMockPersonRecords } from '~/testing/mock-data/people'; @@ -34,29 +36,42 @@ const meta: Meta = { component: RecordDetailRelationSection, decorators: [ (Story) => ( - - name === 'people', - )!, - objectMetadataItem: mockedCompanyObjectMetadataItem, - }), - isRecordFieldReadOnly: false, - }} + - name === 'people', + )!, + objectMetadataItem: mockedCompanyObjectMetadataItem, + }), + isRecordFieldReadOnly: false, + }} > - - - - + + + + + + ), RightDrawerDecorator, ComponentDecorator, diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBox.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBox.tsx index 10e25de32b..5e728f8581 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBox.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBox.tsx @@ -1,5 +1,7 @@ +import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; +import { PageLayoutType } from '~/generated/graphql'; interface PropertyBoxProps { children: React.ReactNode; @@ -8,8 +10,9 @@ interface PropertyBoxProps { } const StyledPropertyBoxContainer = styled('div', { - shouldForwardProp: isPropValid, -})` + shouldForwardProp: (prop) => + isPropValid(prop) && prop !== 'noHorizontalPadding', +})<{ noHorizontalPadding?: boolean }>` align-self: stretch; border-radius: ${({ theme }) => theme.border.radius.sm}; display: flex; @@ -17,16 +20,33 @@ const StyledPropertyBoxContainer = styled('div', { gap: ${({ theme }) => theme.spacing(2)}; padding-top: ${({ theme }) => theme.spacing(3)}; padding-bottom: ${({ theme }) => theme.spacing(3)}; - padding-left: ${({ theme }) => theme.spacing(3)}; - padding-right: ${({ theme }) => theme.spacing(2)}; + padding-left: ${({ theme, noHorizontalPadding }) => + noHorizontalPadding ? 0 : theme.spacing(3)}; + padding-right: ${({ theme, noHorizontalPadding }) => + noHorizontalPadding ? 0 : theme.spacing(2)}; `; +/** + * TODO: Remove noHorizontalPadding logic once the traditional record show page is removed. + */ export const PropertyBox = ({ children, className, dataTestId, -}: PropertyBoxProps) => ( - - {children} - -); +}: PropertyBoxProps) => { + const layoutRenderingContext = useLayoutRenderingContext(); + + const isInRecordPageLayout = + layoutRenderingContext?.layoutType === PageLayoutType.RECORD_PAGE && + !layoutRenderingContext?.isLegacyRecordShowPage; + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx index bc477a1397..464bc82e98 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutDispatcher.tsx @@ -47,6 +47,8 @@ export const PageLayoutDispatcher = ({ }, layoutType: PageLayoutType.RECORD_PAGE, isInRightDrawer, + // TODO: Remove once the traditional record show page is removed. + isLegacyRecordShowPage: true, }} > ` background: ${({ theme, shouldUseWhiteBackground }) => shouldUseWhiteBackground @@ -12,7 +19,8 @@ const StyledVerticalListContainer = styled.div<{ : theme.background.secondary}; display: flex; flex-direction: column; - gap: ${({ theme }) => theme.spacing(2)}; + gap: ${({ theme, variant }) => + variant === 'side-column' ? 0 : theme.spacing(2)}; `; type PageLayoutVerticalListViewerProps = { @@ -23,10 +31,20 @@ export const PageLayoutVerticalListViewer = ({ widgets, }: PageLayoutVerticalListViewerProps) => { const { shouldUseWhiteBackground } = usePageLayoutShouldUseWhiteBackground(); + const { isInRightDrawer } = useLayoutRenderingContext(); + const isMobile = useIsMobile(); + const { isInPinnedTab } = useIsInPinnedTab(); + + const variant = getPageLayoutVerticalListViewerVariant({ + isInPinnedTab, + isMobile, + isInRightDrawer, + }); return ( {widgets.map((widget) => (
diff --git a/packages/twenty-front/src/modules/page-layout/components/utils/getPageLayoutVerticalListViewerVariant.ts b/packages/twenty-front/src/modules/page-layout/components/utils/getPageLayoutVerticalListViewerVariant.ts new file mode 100644 index 0000000000..8b06737691 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/components/utils/getPageLayoutVerticalListViewerVariant.ts @@ -0,0 +1,19 @@ +import { type PageLayoutVerticalListViewerVariant } from '@/page-layout/types/PageLayoutVerticalListViewerVariant'; + +type GetPageLayoutVerticalListViewerVariantParams = { + isInPinnedTab: boolean; + isMobile: boolean; + isInRightDrawer: boolean; +}; + +export const getPageLayoutVerticalListViewerVariant = ({ + isInPinnedTab, + isMobile, + isInRightDrawer, +}: GetPageLayoutVerticalListViewerVariantParams): PageLayoutVerticalListViewerVariant => { + if (isInPinnedTab || isMobile || isInRightDrawer) { + return 'side-column'; + } + + return 'default'; +}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts b/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts index 01a3a488c1..3dd9f2b870 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useTemporaryFieldsConfiguration.ts @@ -38,7 +38,7 @@ export const useTemporaryFieldsConfiguration = ( let otherPosition = 0; fieldsToDisplay.forEach((field) => { - if (field.type === FieldMetadataType.LINKS) { + if (field.isCustom === true) { otherFields.push({ fieldMetadataId: field.id, position: otherPosition++, diff --git a/packages/twenty-front/src/modules/page-layout/types/PageLayoutVerticalListViewerVariant.ts b/packages/twenty-front/src/modules/page-layout/types/PageLayoutVerticalListViewerVariant.ts new file mode 100644 index 0000000000..7f8081a8dc --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/types/PageLayoutVerticalListViewerVariant.ts @@ -0,0 +1 @@ +export type PageLayoutVerticalListViewerVariant = 'default' | 'side-column'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/DashboardWidgetPlaceholder.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/DashboardWidgetPlaceholder.tsx index a0c9c8c217..d7f49c461b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/DashboardWidgetPlaceholder.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/DashboardWidgetPlaceholder.tsx @@ -53,6 +53,7 @@ export const DashboardWidgetPlaceholder = () => { className="widget" > { const isRichTextWidget = widget.type === WidgetType.STANDALONE_RICH_TEXT; const hideRichTextHeader = isRichTextWidget && !isPageLayoutInEditMode; - const showHeader = layoutMode !== 'canvas' && !hideRichTextHeader; + const showHeader = + layoutMode !== 'canvas' && + !hideRichTextHeader && + // TODO: use a more generic approach after record page layout v1 release + widget.type !== WidgetType.FIELDS; const handleClick = () => { handleEditWidget({ @@ -140,6 +144,7 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { {showHeader && ( theme.border.radius.sm}; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(2)}; + padding-top: ${({ theme }) => theme.spacing(3)}; + padding-bottom: ${({ theme }) => theme.spacing(3)}; +`; + type FieldsWidgetProps = { widget: PageLayoutWidget; }; @@ -117,7 +126,7 @@ export const FieldsWidget = ({ widget }: FieldsWidgetProps) => { key={section.id} title={section.title} > - + {isPrefetchLoading ? ( ) : ( @@ -182,7 +191,7 @@ export const FieldsWidget = ({ widget }: FieldsWidgetProps) => { )} )} - + ))} diff --git a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx index eed1cf46a8..902329210c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidgetSectionContainer.tsx @@ -4,24 +4,12 @@ import { useState } from 'react'; import { IconChevronDown } from 'twenty-ui/display'; import { AnimatedExpandableContainer, Section } from 'twenty-ui/layout'; -const StyledFieldsWidgetSectionContainer = styled(Section)` - padding-top: ${({ theme }) => theme.spacing(3)}; - padding-bottom: 0; - width: auto; - - &:not(:first-of-type) { - padding-top: 0; - } -`; - const StyledHeader = styled.header` align-items: center; cursor: pointer; display: flex; height: 24px; justify-content: space-between; - padding-left: ${({ theme }) => theme.spacing(3)}; - padding-right: ${({ theme }) => theme.spacing(2)}; `; const StyledTitleLabel = styled.div` @@ -53,7 +41,7 @@ export const FieldsWidgetSectionContainer = ({ setIsExpanded((previousIsExpanded) => !previousIsExpanded); return ( - +
{title} {children} - +
); }; 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 9fa919fa61..6ddf53aba2 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 @@ -80,7 +80,9 @@ const StyledWidgetCard = styled.div<{ if (variant === 'side-column' && !isEditable) { return css` - padding: ${theme.spacing(2)}; + gap: ${theme.spacing(2)}; + padding: ${theme.spacing(3)}; + ${isLastWidget !== true && css` border-bottom: 1px solid ${theme.border.color.light}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx index aaf72f8820..19f8059ab7 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx @@ -2,18 +2,20 @@ import { WidgetActionRenderer } from '@/page-layout/widgets/components/WidgetAct import { widgetCardHoveredComponentFamilyState } from '@/page-layout/widgets/states/widgetCardHoveredComponentFamilyState'; import { type WidgetAction } from '@/page-layout/widgets/types/WidgetAction'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; -import { useTheme } from '@emotion/react'; +import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { type ReactNode } from 'react'; import { IconTrash, OverflowingTextWithTooltip } from 'twenty-ui/display'; import { IconButton } from 'twenty-ui/input'; +import { type WidgetCardVariant } from '@/page-layout/widgets/types/WidgetCardVariant'; import { WidgetGrip } from '@/page-layout/widgets/widget-card/components/WidgetGrip'; import { AnimatePresence, motion } from 'framer-motion'; import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; export type WidgetCardHeaderProps = { + variant: WidgetCardVariant; widgetId: string; isInEditMode: boolean; isEmpty?: boolean; @@ -32,14 +34,24 @@ const StyledWidgetCardHeader = styled.div` flex-shrink: 0; `; -const StyledTitleContainer = styled.div` +const StyledTitleContainer = styled.div<{ variant: WidgetCardVariant }>` color: ${({ theme }) => theme.font.color.primary}; flex: 1; font-size: ${({ theme }) => theme.font.size.md}; - padding-inline: ${({ theme }) => theme.spacing(1)}; font-weight: ${({ theme }) => theme.font.weight.medium}; user-select: none; overflow: hidden; + + ${({ theme, variant }) => { + switch (variant) { + case 'side-column': + return undefined; + default: + return css` + padding-inline: ${theme.spacing(1)}; + `; + } + }} `; const StyledRightContainer = styled.div` @@ -62,6 +74,7 @@ const StyledIconButtonContainer = styled(motion.div)` export const WidgetCardHeader = ({ widgetId, + variant, isEmpty = false, isInEditMode = false, isResizing = false, @@ -88,7 +101,7 @@ export const WidgetCardHeader = ({ /> )} - + diff --git a/packages/twenty-front/src/modules/ui/layout/contexts/LayoutRenderingContext.tsx b/packages/twenty-front/src/modules/ui/layout/contexts/LayoutRenderingContext.tsx index 28bd4965a1..fd9d159dd5 100644 --- a/packages/twenty-front/src/modules/ui/layout/contexts/LayoutRenderingContext.tsx +++ b/packages/twenty-front/src/modules/ui/layout/contexts/LayoutRenderingContext.tsx @@ -11,6 +11,9 @@ export type LayoutRenderingContextType = { layoutType: PageLayoutType; isInRightDrawer: boolean; + + /** TODO: Remove once the traditional record show page is removed. */ + isLegacyRecordShowPage?: boolean; }; export const [LayoutRenderingProvider, useLayoutRenderingContext] =