Files
twenty/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuEditModeBar.tsx
T
Charles Bochet 647c32ff3e Deprecate runtime theme objects in favor of CSS variables (#18402)
## Summary

- **Eliminate `ICON_SIZES` / `ICON_STROKES` constants**: all icon
dimensions are now resolved at runtime via
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.X)`, ensuring
values always come from computed CSS variables
- **No more consumer imports from `twenty-ui/theme`**: moved
`ColorSchemeContext`, `ColorSchemeProvider`, `ThemeColor`,
`MAIN_COLOR_NAMES`, `getNextThemeColor`, `AnimationDuration` to
`twenty-ui/theme-constants`
- **Remove `ThemeContext` / `ThemeContextProvider` / `ThemeProvider` /
`ThemeType`**: replaced across ~300 files with `themeCssVariables` (for
CSS contexts) or `resolveThemeVariable` / `resolveThemeVariableAsNumber`
(for JS runtime values)
- **Simplify provider chain**: only `ColorSchemeProvider` remains — it
toggles `light`/`dark` class on `document.documentElement` and provides
`colorScheme` via React context
- **Fix pre-existing test failures**: `useIcons.test.ts`
(non-configurable ES module spy) and
`turnRecordFilterGroupIntoGqlOperationFilter.test.ts`
(`Omit<RecordFilter, 'id'>` type mismatch)

### Theme access pattern (before → after)

| Context | Before | After |
|---------|--------|-------|
| CSS (Linaria) | `${({ theme }) => theme.font.color.primary}` |
`${themeCssVariables.font.color.primary}` |
| JS runtime (icon size, animation) | `theme.icon.size.md` /
`ICON_SIZES.md` |
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.md)` |
| Color scheme check | `theme.name === 'dark'` |
`useContext(ColorSchemeContext).colorScheme === 'dark'` |
2026-03-05 14:39:01 +01:00

120 lines
4.3 KiB
TypeScript

import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useSaveNavigationMenuItemsDraft } from '@/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft';
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useContext, useState } from 'react';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconCheck, useIcons } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledContainer = styled.div`
align-items: center;
background: ${themeCssVariables.color.blue};
box-sizing: border-box;
color: ${themeCssVariables.font.color.inverted};
display: flex;
justify-content: space-between;
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
width: 100%;
`;
const StyledTitle = styled.span`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
export const NavigationMenuEditModeBar = () => {
const { theme } = useContext(ThemeContext);
const { t } = useLingui();
const { getIcon } = useIcons();
const [isSaving, setIsSaving] = useState(false);
const { closeCommandMenu } = useCommandMenu();
const commandMenuPage = useAtomStateValue(commandMenuPageState);
const { enqueueErrorSnackBar } = useSnackBar();
const setNavigationMenuItemsDraft = useSetAtomState(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetAtomState(
selectedNavigationMenuItemInEditModeState,
);
const setIsNavigationMenuInEditMode = useSetAtomState(
isNavigationMenuInEditModeState,
);
const { saveDraft } = useSaveNavigationMenuItemsDraft();
const { isDirty } = useNavigationMenuItemsDraftState();
const cancelEditMode = () => {
setNavigationMenuItemsDraft(null);
setSelectedNavigationMenuItemInEditMode(null);
setIsNavigationMenuInEditMode(false);
const isNavItemPageOpen =
commandMenuPage === CommandMenuPages.NavigationMenuAddItem ||
commandMenuPage === CommandMenuPages.NavigationMenuItemEdit;
if (isNavItemPageOpen) {
closeCommandMenu();
}
};
const isNavigationMenuInEditMode = useAtomStateValue(
isNavigationMenuInEditModeState,
);
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
);
const showNavigationMenuEditModeBar =
isNavigationMenuItemEditingEnabled && isNavigationMenuInEditMode;
const handleSave = async () => {
if (!isDirty) return;
setIsSaving(true);
try {
await saveDraft();
cancelEditMode();
closeCommandMenu();
} catch {
enqueueErrorSnackBar({
message: t`Failed to save navigation layout`,
});
} finally {
setIsSaving(false);
}
};
const IconPaint = getIcon('IconPaint');
if (!showNavigationMenuEditModeBar) {
return null;
}
return (
<StyledContainer>
<StyledTitle>
<IconPaint size={theme.icon.size.md} />
{t`Layout customization`}
</StyledTitle>
<SaveAndCancelButtons
onSave={handleSave}
onCancel={cancelEditMode}
isSaveDisabled={!isDirty || isSaving}
isLoading={isSaving}
inverted
saveIcon={IconCheck}
/>
</StyledContainer>
);
};