feat(settings): move settings chrome into a single rounded card (#21131)
## What Replaces `SubMenuTopBarContainer` with a settings-specific `SettingsPageLayout` that puts the whole page chrome — breadcrumb, centered title, actions, an optional secondary bar (tabs or wizard step), and the 760px body — inside **one rounded card**, with `SidePanelForDesktop` as a sibling. Title, tabs and body content share one centered vertical axis at every card width. Supersedes #21122. One PR, no feature flag. ## New components (`@/settings/components/layout/`) - **SettingsPageLayout** — owns the rounded card + side-panel sibling, `useCommandMenuHotKeys`, mobile command menu - **SettingsPageHeader** — breadcrumb · centered title · actions in a symmetric `1fr auto 1fr` grid (symmetric padding throughout) - **SettingsSecondaryBar** — the secondary row, bracketed by top + bottom borders - **SettingsTabBar** — centered tabs reusing `activeTabIdComponentState` + `TabListFromUrlOptionalEffect` for URL-hash sync (does not touch the shared `TabList`) - **SettingsWizardStepBar** — back arrow · "N. Label" · optional trailing slot ## Migrations - Bulk rename across ~80 call sites (`SubMenuTopBarContainer` → `SettingsPageLayout`); old component deleted. - 5 tab pages (AI, APIs & Webhooks, Applications, Members, Role) + the Data Model object-detail page render their tabs in `secondaryBar` (object-detail keeps "See records" / "New Field" in the header actions). - The 2 role object-level steps render the wizard step bar with working back navigation. - Accounts consolidated into **General / Emails / Calendars** tabs; standalone `SettingsAccountsEmails` / `SettingsAccountsCalendars` pages + routes + stories removed. `SettingsPath.AccountsEmails` / `AccountsCalendars` now resolve to `accounts#emails` / `accounts#calendars`, so existing `getSettingsPath()` links deep-link to the right tab via the existing hash sync — no call-site changes. ## Verification - `nx typecheck twenty-front` and `nx lint twenty-front` both clean. - Browser (logged-in workspace): title / tab / body / card centers align on a single axis at multiple widths — width-invariant, so alignment holds when the AI side panel (a sibling) shrinks the card. Rounded card with even gaps on all four sides; tab row bracketed by two 1px lines; no-tab pages render header → body with no lines; wizard back navigation works; `…/accounts#emails` opens the Emails tab. The shared `PageHeader` and `TabList` are untouched. The settings side panel itself isn't wired to open yet — that's a follow-up PR.
This commit is contained in:
-84
@@ -1,84 +0,0 @@
|
||||
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
|
||||
import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel';
|
||||
import {
|
||||
Breadcrumb,
|
||||
type BreadcrumbProps,
|
||||
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type JSX, type ReactNode } from 'react';
|
||||
import { PageHeader } from './PageHeader';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type SubMenuTopBarContainerProps = {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
title?: string | JSX.Element;
|
||||
reserveTitleSpace?: boolean;
|
||||
actionButton?: ReactNode;
|
||||
className?: string;
|
||||
links: BreadcrumbProps['links'];
|
||||
tag?: JSX.Element;
|
||||
};
|
||||
|
||||
const SETTINGS_CONTENT_MAX_WIDTH = 760;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
// flex: 1 + min-height: 0 are required for PagePanel's overflow chain — the
|
||||
// child must participate in the flex height calc rather than collapse to content.
|
||||
const StyledBodyContentWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
max-width: ${SETTINGS_CONTENT_MAX_WIDTH}px;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.span<{ reserveTitleSpace?: boolean }>`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.lg};
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
line-height: 1.2;
|
||||
margin: ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}
|
||||
${themeCssVariables.spacing[2]};
|
||||
min-height: ${({ reserveTitleSpace }) =>
|
||||
reserveTitleSpace ? themeCssVariables.spacing[5] : 'none'};
|
||||
`;
|
||||
|
||||
export const SubMenuTopBarContainer = ({
|
||||
children,
|
||||
title,
|
||||
tag,
|
||||
reserveTitleSpace,
|
||||
actionButton,
|
||||
className,
|
||||
links,
|
||||
}: SubMenuTopBarContainerProps) => {
|
||||
return (
|
||||
<StyledContainer className={className}>
|
||||
<PageHeader title={<Breadcrumb links={links} />}>
|
||||
{actionButton}
|
||||
</PageHeader>
|
||||
<MainContainerLayoutWithSidePanel>
|
||||
<StyledBodyContentWrapper>
|
||||
<InformationBannerWrapper />
|
||||
{(isDefined(title) || reserveTitleSpace === true) && (
|
||||
<StyledTitle reserveTitleSpace={reserveTitleSpace}>
|
||||
{title}
|
||||
{tag}
|
||||
</StyledTitle>
|
||||
)}
|
||||
{children}
|
||||
</StyledBodyContentWrapper>
|
||||
</MainContainerLayoutWithSidePanel>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -164,7 +164,7 @@ export const Settings: Story = {
|
||||
<NavigationDrawerSectionTitle label="Workspace" />
|
||||
<NavigationDrawerItem
|
||||
label="General"
|
||||
to={getSettingsPath(SettingsPath.Workspace)}
|
||||
to={getSettingsPath(SettingsPath.General)}
|
||||
Icon={IconSettings}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
|
||||
Reference in New Issue
Block a user