From 02aa086866ca1d97871e9786f12d9aa5a158bfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 9 Jun 2026 16:10:36 +0200 Subject: [PATCH] fix(twenty-front): new layout fast-follows (#21360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fast-follows for the new layout / flat redesign (master: twentyhq/core-team-issues#2478). ## Changes - **Main navbar 48px** (twentyhq/core-team-issues#2479) — `SIDE_PANEL_TOP_BAR_HEIGHT` 40 → 48, so `PageCardHeader` matches the Figma target. The side panel top bar shares this constant and stays aligned. - **Content panel 12px radius** (twentyhq/core-team-issues#2480) — `PageCardLayout` card gets a full border + 12px radius and an 8px inset (`spacing[2]`) so it floats on the shell instead of square full-bleed. - **Square three-dots button** (twentyhq/core-team-issues#2481) — added a `square` option to `AnimatedButton`; the page-header side-panel toggle now renders a 24×24 square icon button instead of a 32×24 pill. - **Table checkbox sizing** (twentyhq/core-team-issues#2482) — restored `box-sizing: content-box` on the checkbox box. Its border is declared outside the label size, so the global `border-box` reset (#21349) was shrinking it (14px → 12px). Same fix pattern as #21349. - **Tertiary navbar background** (twentyhq/core-team-issues#2483) — left navbar / app shell use `background/tertiary` instead of the noisy surface (`DefaultLayout`, `UserOrMetadataLoader`). - **Skeleton loading** (twentyhq/core-team-issues#2484) — metadata + content loading now match the new layout: tertiary shell, 12px rounded content panel, 48px navbar, sparse bars, empty body (removed the dense full-width rows). Left-panel skeleton bars use `quaternary` so they stay visible on the tertiary shell. ## Verification - typecheck (tsgo) + oxlint + oxfmt pass for all changed files. - Verified live against a running workspace: measured navbar = 48px, three-dots = 24×24, checkbox box-sizing = content-box (14px), card radius = 12px, shell background = tertiary (no noisy image). Content-loading skeleton matches the target. --- .../components/LeftPanelSkeletonLoader.tsx | 2 +- .../components/PageContentSkeletonLoader.tsx | 26 ++++++------------- .../components/UserOrMetadataLoader.tsx | 2 +- .../components/SidePanelToggleButton.tsx | 1 + .../constants/SidePanelTopBarHeight.ts | 2 +- .../layout/page/components/DefaultLayout.tsx | 2 +- .../layout/page/components/PageCardLayout.tsx | 1 + .../button/components/AnimatedButton.tsx | 21 ++++++++++++--- .../src/input/components/Checkbox.tsx | 2 ++ 9 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx index 86cb23461f..badd24e1d7 100644 --- a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx @@ -66,7 +66,7 @@ export const LeftPanelSkeletonLoader = () => { diff --git a/packages/twenty-front/src/loading/components/PageContentSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/PageContentSkeletonLoader.tsx index 1389d4604d..46d6751b03 100644 --- a/packages/twenty-front/src/loading/components/PageContentSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/PageContentSkeletonLoader.tsx @@ -1,20 +1,9 @@ import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader'; import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout'; -import { styled } from '@linaria/react'; import { type ReactNode, useContext } from 'react'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; -import { - ThemeContext, - themeCssVariables, -} from 'twenty-ui-deprecated/theme-constants'; - -const StyledBody = styled.div` - display: flex; - flex-direction: column; - gap: ${themeCssVariables.spacing[2]}; - padding: ${themeCssVariables.spacing[3]}; -`; +import { ThemeContext } from 'twenty-ui-deprecated/theme-constants'; type PageContentSkeletonLoaderProps = { secondaryBar?: ReactNode; @@ -41,17 +30,18 @@ export const PageContentSkeletonLoader = ({ height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} /> } + actionButton={ + + } /> } secondaryBar={secondaryBar} showInformationBanner={false} > - - - + {null} ); diff --git a/packages/twenty-front/src/loading/components/UserOrMetadataLoader.tsx b/packages/twenty-front/src/loading/components/UserOrMetadataLoader.tsx index 57e67406c8..e6963ba872 100644 --- a/packages/twenty-front/src/loading/components/UserOrMetadataLoader.tsx +++ b/packages/twenty-front/src/loading/components/UserOrMetadataLoader.tsx @@ -12,7 +12,7 @@ import { LeftPanelSkeletonLoader } from '~/loading/components/LeftPanelSkeletonL import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader'; const StyledContainer = styled.div` - background: ${themeCssVariables.background.noisy}; + background: ${themeCssVariables.background.tertiary}; box-sizing: border-box; display: flex; flex-direction: row; diff --git a/packages/twenty-front/src/modules/side-panel/components/SidePanelToggleButton.tsx b/packages/twenty-front/src/modules/side-panel/components/SidePanelToggleButton.tsx index 603219ddbe..a2e3b19d5a 100644 --- a/packages/twenty-front/src/modules/side-panel/components/SidePanelToggleButton.tsx +++ b/packages/twenty-front/src/modules/side-panel/components/SidePanelToggleButton.tsx @@ -152,6 +152,7 @@ export const SidePanelToggleButton = () => { dataClickOutsideId={PAGE_HEADER_SIDE_PANEL_BUTTON_CLICK_OUTSIDE_ID} dataTestId="page-header-side-panel-button" size={isMobile ? 'medium' : 'small'} + square variant="secondary" accent="default" ariaLabel={ariaLabel} diff --git a/packages/twenty-front/src/modules/side-panel/constants/SidePanelTopBarHeight.ts b/packages/twenty-front/src/modules/side-panel/constants/SidePanelTopBarHeight.ts index 574749a328..b07ac85c0b 100644 --- a/packages/twenty-front/src/modules/side-panel/constants/SidePanelTopBarHeight.ts +++ b/packages/twenty-front/src/modules/side-panel/constants/SidePanelTopBarHeight.ts @@ -1 +1 @@ -export const SIDE_PANEL_TOP_BAR_HEIGHT = 40; +export const SIDE_PANEL_TOP_BAR_HEIGHT = 48; diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx index aa1efd4c8e..20c09c5fc1 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx @@ -25,7 +25,7 @@ import { AnimatePresence, LayoutGroup } from 'framer-motion'; import { Outlet } from 'react-router-dom'; import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants'; const StyledLayout = styled.div` - background: ${themeCssVariables.background.noisy}; + background: ${themeCssVariables.background.tertiary}; display: flex; flex-direction: column; height: 100dvh; diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx index 27bb0c1460..dcc58e0162 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardLayout.tsx @@ -28,6 +28,7 @@ const StyledMainCardWrapper = styled.div` const StyledCard = styled.div` background: ${themeCssVariables.background.primary}; border-left: 1px solid ${themeCssVariables.border.color.medium}; + border-radius: 12px 0 0 12px; border-right: 1px solid ${themeCssVariables.border.color.medium}; box-sizing: border-box; display: flex; diff --git a/packages/twenty-ui-deprecated/src/input/button/components/AnimatedButton.tsx b/packages/twenty-ui-deprecated/src/input/button/components/AnimatedButton.tsx index b49596203d..d439d99d03 100644 --- a/packages/twenty-ui-deprecated/src/input/button/components/AnimatedButton.tsx +++ b/packages/twenty-ui-deprecated/src/input/button/components/AnimatedButton.tsx @@ -20,6 +20,8 @@ export type AnimatedButtonProps = ButtonProps & Pick & { animatedSvg: React.ReactNode; soonLabel?: string; + // Renders a square icon-only button (width matches the size-based height). + square?: boolean; }; type AnimatedButtonDynamicStyles = { @@ -315,7 +317,7 @@ const StyledButton = styled.button< | 'target' | 'dataClickOutsideId' | 'dataGloballyPreventClickOutside' - > + > & { square?: boolean } >` align-items: center; background: var(--abtn-bg); @@ -354,14 +356,23 @@ const StyledButton = styled.button< font-size: ${themeCssVariables.font.size.md}; gap: ${themeCssVariables.spacing[1]}; height: ${({ size }) => (size === 'small' ? '24px' : '32px')}; - justify-content: ${({ justify }) => justify ?? ''}; - padding: 0 ${themeCssVariables.spacing[2]}; + justify-content: ${({ justify, square }) => + square ? 'center' : (justify ?? '')}; + padding: ${({ square }) => + square ? '0' : `0 ${themeCssVariables.spacing[2]}`}; transition: background 0.1s ease; white-space: nowrap; - width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; + width: ${({ fullWidth, square, size }) => + square + ? size === 'small' + ? '24px' + : '32px' + : fullWidth + ? '100%' + : 'auto'}; &:focus { outline: none; @@ -428,6 +439,7 @@ export const AnimatedButton = ({ inverted = false, size = 'medium', accent = 'default', + square = false, position = 'standalone', soon = false, disabled = false, @@ -476,6 +488,7 @@ export const AnimatedButton = ({ as={ButtonComponent} fullWidth={fullWidth} size={size} + square={square} position={position} disabled={isDisabled} justify={justify} diff --git a/packages/twenty-ui-deprecated/src/input/components/Checkbox.tsx b/packages/twenty-ui-deprecated/src/input/components/Checkbox.tsx index dee424ddc9..80d84010e0 100644 --- a/packages/twenty-ui-deprecated/src/input/components/Checkbox.tsx +++ b/packages/twenty-ui-deprecated/src/input/components/Checkbox.tsx @@ -159,6 +159,8 @@ const StyledCheckboxContainer = styled.div` border-radius: var(--checkbox-border-radius); border-style: solid; border-width: var(--checkbox-border-width); + // The border sits outside the declared label size, so size against the content box. + box-sizing: content-box; content: ''; cursor: var(--checkbox-cursor); display: inline-block;