fix(twenty-front): new layout fast-follows — settings drawer, loading & command menu (#21389)

Second batch of new-layout fast-follows (master:
twentyhq/core-team-issues#2478). All changes verified live against a
running workspace.

## Settings drawer & header
- **twentyhq/core-team-issues#2489** — sidebar icons render as plain
16px icons, no background tiles.
- **twentyhq/core-team-issues#2488** — Advanced toggle spans the full
drawer width; yellow dot removed.
- **twentyhq/core-team-issues#2497** — page title stays centered in the
settings header (breadcrumb stays left).
- **twentyhq/core-team-issues#2490** — Exit Settings control aligned to
the workspace switcher (24px, matching padding/gap).
- **twentyhq/core-team-issues#2499** — 2px vertical gap restored between
collapsible drawer section items.
- **twentyhq/core-team-issues#2491** — settings drawer rhythm now
matches the main app (28px items, 2px gaps, 28px section headers).
- **twentyhq/core-team-issues#2492** — Home/Chat tab switch no longer
flickers: both tab subtrees stay mounted (a shared
`NavigationDrawerTabbedContent` toggles visibility instead of remounting
+ flashing the chat skeleton).

## Loading states
- **twentyhq/core-team-issues#2486** — metadata loading shows an empty
body (no dense skeleton rows).
- **twentyhq/core-team-issues#2487** — settings table keeps its layout
while loading, with the shimmer localized to the first row's first cell.

## Command menu & navigation
- **twentyhq/core-team-issues#2501** — navigation section header height
matches the nav item rhythm (28px).
- **twentyhq/core-team-issues#2502 (part 1)** — the page side-panel
toggle stays as the dots glyph while the command menu is open, instead
of morphing into a second close control.

## New-field flow
- **twentyhq/core-team-issues#2494** — the new-field stepper moved from
a breadcrumb dropdown into a centered secondary wizard bar (back chevron
+ Save on the configure step); breadcrumb stays clean and the object
label is the centered title.

## Descoped (substantive bugs already fixed)
- **twentyhq/core-team-issues#2500** — command-menu highlight right
gutter: the menu-item base measures full-width, so it's likely a
scrollbar gutter on the list, not the shared component. Left for a
focused follow-up.
- **twentyhq/core-team-issues#2502 part 2** — moving the command-menu
close from left to right is cosmetic (the duplicate-control bug is fixed
by part 1) and would touch the shared `SidePanelTopBar` used by
search/AI panels.

## Verification
typecheck (tsgo) + oxlint + oxfmt green for all changed files; each
change DOM-measured / screenshotted in the running app.
This commit is contained in:
Félix Malfait
2026-06-10 11:02:36 +02:00
committed by GitHub
parent 6525af5ba4
commit adba66caea
16 changed files with 161 additions and 245 deletions
@@ -1,135 +0,0 @@
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronDown } from 'twenty-ui-deprecated/display';
import { Button } from 'twenty-ui-deprecated/input';
import { MenuItem } from 'twenty-ui-deprecated/navigation';
import {
ThemeContext,
themeCssVariables,
} from 'twenty-ui-deprecated/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const StyledContainer = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
cursor: default;
display: flex;
font-size: ${themeCssVariables.font.size.md};
`;
const StyledButtonContainer = styled.div`
position: relative;
width: 100%;
`;
const StyledDownChevronContainer = styled.span`
align-items: center;
color: ${themeCssVariables.font.color.primary};
display: flex;
position: absolute;
right: ${themeCssVariables.spacing['1.5']};
top: 50%;
transform: translateY(-50%);
`;
const StyledMenuItemWrapper = styled.div<{ disabled?: boolean }>`
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
width: 100%;
`;
const StyledSpan = styled.span`
margin-left: ${themeCssVariables.spacing[2]};
`;
const StyledButtonWrapper = styled.div`
button {
color: ${themeCssVariables.font.color.primary};
padding-right: ${themeCssVariables.spacing[6]};
}
`;
export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
const { theme } = useContext(ThemeContext);
const dropdownId = `settings-object-new-field-breadcrumb-dropdown`;
const { closeDropdown } = useCloseDropdown();
const navigate = useNavigateSettings();
const location = useLocation();
const { objectNamePlural = '' } = useParams();
const [searchParams] = useSearchParams();
const fieldType = searchParams.get('fieldType') as SettingsFieldType;
const isConfigureStep = location.pathname.includes('/configure');
const handleClick = (step: 'select' | 'configure') => {
if (step === 'configure' && isDefined(fieldType)) {
navigate(
SettingsPath.ObjectNewFieldConfigure,
{ objectNamePlural },
{ fieldType },
);
} else {
navigate(
SettingsPath.ObjectNewFieldSelect,
{ objectNamePlural },
fieldType ? { fieldType } : undefined,
);
}
closeDropdown(dropdownId);
};
return (
<StyledContainer>
{t`New Field`} <StyledSpan>-</StyledSpan>
<Dropdown
dropdownPlacement="bottom-start"
dropdownId={dropdownId}
clickableComponent={
<StyledButtonContainer>
<StyledDownChevronContainer>
<IconChevronDown size={theme.icon.size.md} />
</StyledDownChevronContainer>
{isConfigureStep ? (
<StyledButtonWrapper>
<Button variant="tertiary" title={t`2. Configure`} />
</StyledButtonWrapper>
) : (
<StyledButtonWrapper>
<Button variant="tertiary" title={t`1. Type`} />
</StyledButtonWrapper>
)}
</StyledButtonContainer>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<StyledMenuItemWrapper>
<MenuItem
text={t`1. Type`}
onClick={() => handleClick('select')}
selected={!isConfigureStep}
/>
</StyledMenuItemWrapper>
<StyledMenuItemWrapper disabled={!isDefined(fieldType)}>
<MenuItem
text={t`2. Configure`}
onClick={() => handleClick('configure')}
selected={isConfigureStep}
disabled={!isDefined(fieldType)}
/>
</StyledMenuItemWrapper>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
</StyledContainer>
);
};