647c32ff3e
## 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'` |
120 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
};
|