feat(twenty-front): generalize the page primary/secondary bars (flat redesign) (#21308)

Replaces #21279 and #21282 with one clean PR from `main`.

Generalizes the settings primary-bar / secondary-bar card chrome to the
record index, record show and standalone pages via a shared
`PageCardLayout` + `PageCardHeader` (the side panel sits as a sibling of
the content card), and applies the new flat design direction: square
corners on the card, side panel and loading skeletons.

Iterating toward the new design (Figma node 102282-221623); the
confirmed direction and the explicit "remove rounded corners" change are
in, remaining designer specifics to follow.
This commit is contained in:
Félix Malfait
2026-06-08 22:47:05 +02:00
committed by GitHub
parent a48c158a66
commit bfefcd3755
29 changed files with 528 additions and 517 deletions
@@ -1,51 +1,24 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { styled } from '@linaria/react';
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
import { useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import {
ThemeContext,
themeCssVariables,
} from 'twenty-ui-deprecated/theme-constants';
const StyledRoot = styled.div<{ isMobile: boolean }>`
display: flex;
flex: 1;
min-height: 0;
min-width: 0;
padding: ${({ isMobile }) =>
isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
`;
const StyledCard = styled.div`
background: ${themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.md};
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
overflow: hidden;
width: 100%;
`;
import { ThemeContext } from 'twenty-ui-deprecated/theme-constants';
export const SettingsSkeletonLoader = () => {
const isMobile = useIsMobile();
const { theme } = useContext(ThemeContext);
return (
<StyledRoot isMobile={isMobile}>
<StyledCard>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<SettingsPageHeader
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<PageCardLayout
header={
<PageCardHeader
links={[
{
children: (
@@ -63,11 +36,13 @@ export const SettingsSkeletonLoader = () => {
/>
}
/>
<SettingsPageContainer>
<SettingsSectionSkeletonLoader />
</SettingsPageContainer>
</SkeletonTheme>
</StyledCard>
</StyledRoot>
}
showInformationBanner={false}
>
<SettingsPageContainer>
<SettingsSectionSkeletonLoader />
</SettingsPageContainer>
</PageCardLayout>
</SkeletonTheme>
);
};
@@ -1,87 +0,0 @@
import { useNavigationDrawerExpanded } from '@/navigation/hooks/useNavigationDrawerExpanded';
import { SIDE_PANEL_TOP_BAR_HEIGHT } from '@/side-panel/constants/SidePanelTopBarHeight';
import {
Breadcrumb,
type BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
type SettingsPageHeaderProps = {
links: BreadcrumbProps['links'];
title?: ReactNode;
tag?: ReactNode;
actionButton?: ReactNode;
};
// minmax(0, 1fr) side tracks (not 1fr) let a long breadcrumb truncate instead of
// pushing the centered title off its shared axis with the tabs and body.
const StyledHeader = styled.div`
align-items: center;
background-color: ${themeCssVariables.background.secondary};
border-bottom: 1px solid ${themeCssVariables.border.color.medium};
box-sizing: border-box;
display: grid;
gap: ${themeCssVariables.spacing[2]};
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px;
padding: 0 ${themeCssVariables.spacing[3]};
width: 100%;
`;
const StyledLeft = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[1]};
min-width: 0;
overflow: hidden;
`;
const StyledTitle = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.primary};
display: flex;
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.semiBold};
gap: ${themeCssVariables.spacing[2]};
min-width: 0;
text-align: center;
`;
const StyledRight = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
justify-content: flex-end;
min-width: 0;
`;
export const SettingsPageHeader = ({
links,
title,
tag,
actionButton,
}: SettingsPageHeaderProps) => {
const isMobile = useIsMobile();
const isNavigationDrawerExpanded = useNavigationDrawerExpanded();
return (
<StyledHeader>
<StyledLeft>
{!isNavigationDrawerExpanded && (
<NavigationDrawerCollapseButton direction="right" />
)}
<Breadcrumb links={links} />
</StyledLeft>
<StyledTitle>
{!isMobile && isDefined(title) && title}
{!isMobile && tag}
</StyledTitle>
<StyledRight>{actionButton}</StyledRight>
</StyledHeader>
);
};
@@ -1,15 +1,9 @@
import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile';
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader';
import { SettingsSecondaryBar } from '@/settings/components/layout/SettingsSecondaryBar';
import { SidePanelForDesktop } from '@/side-panel/components/SidePanelForDesktop';
import { PageCardHeader } from '@/ui/layout/page/components/PageCardHeader';
import { PageCardLayout } from '@/ui/layout/page/components/PageCardLayout';
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { styled } from '@linaria/react';
import { type JSX, type ReactNode } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui-deprecated/theme-constants';
type SettingsPageLayoutProps = {
links: BreadcrumbProps['links'];
@@ -20,44 +14,6 @@ type SettingsPageLayoutProps = {
tag?: JSX.Element;
};
const StyledRoot = styled.div<{ isMobile: boolean }>`
display: flex;
flex: 1;
flex-direction: row;
min-height: 0;
min-width: 0;
padding: ${({ isMobile }) =>
isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]};
`;
const StyledMainCardWrapper = styled.div`
display: flex;
flex: 1 1 0;
min-width: 0;
width: 0;
`;
const StyledCard = styled.div`
background: ${themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.md};
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
overflow: hidden;
width: 100%;
`;
const StyledBodyContent = styled.div`
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
width: 100%;
`;
export const SettingsPageLayout = ({
links,
title,
@@ -65,31 +21,22 @@ export const SettingsPageLayout = ({
secondaryBar,
children,
tag,
}: SettingsPageLayoutProps) => {
const isMobile = useIsMobile();
useCommandMenuHotKeys();
return (
<StyledRoot isMobile={isMobile}>
<StyledMainCardWrapper>
<StyledCard>
<SettingsPageHeader
links={links}
title={title}
tag={tag}
actionButton={actionButton}
/>
{isDefined(secondaryBar) && (
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
)}
<StyledBodyContent>
<InformationBannerWrapper />
{children}
</StyledBodyContent>
</StyledCard>
</StyledMainCardWrapper>
{isMobile ? <CommandMenuForMobile /> : <SidePanelForDesktop />}
</StyledRoot>
);
};
}: SettingsPageLayoutProps) => (
<PageCardLayout
header={
<PageCardHeader
links={links}
title={title}
tag={tag}
actionButton={actionButton}
/>
}
secondaryBar={
isDefined(secondaryBar) ? (
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
) : undefined
}
>
{children}
</PageCardLayout>
);