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'` |
This commit is contained in:
Charles Bochet
2026-03-05 14:39:01 +01:00
committed by GitHub
parent 7293d4c1f8
commit 647c32ff3e
432 changed files with 1243 additions and 1433 deletions
@@ -14,8 +14,7 @@ import {
TooltipDelay,
useIcons,
} from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { type RoleWithPartialMembers } from '@/settings/roles/types/RoleWithPartialMembers';
@@ -62,7 +61,6 @@ type SettingsRolesTableRowProps = {
export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(role.icon ?? 'IconUser');
@@ -12,8 +12,7 @@ import {
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { type Agent, type ApiKeyForRole } from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { formatDateString } from '~/utils/string/formatDateString';
@@ -55,10 +54,10 @@ type SettingsRoleAssignmentTableRowProps = {
export const SettingsRoleAssignmentTableRow = ({
roleTarget,
}: SettingsRoleAssignmentTableRowProps) => {
const { theme } = useContext(ThemeContext);
const currentWorkspaceMembers = useAtomStateValue(
currentWorkspaceMembersState,
);
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const { dateFormat, timeZone } = useContext(UserContext);
const dateLocale = useAtomStateValue(dateLocaleState);
@@ -10,8 +10,7 @@ import { useSearchParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { H2Title, IconSearch, useIcons } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const StyledTypeSelectContainer = styled.div`
@@ -6,13 +6,12 @@ import { SettingsRolePermissionsObjectLevelUpdateFieldsValueForObject } from '@/
import { OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS } from '@/settings/roles/role-permissions/object-level-permissions/constants/ObjectLevelPermissionTableGridAutoColumns';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNameTableCell = styled(TableCell)`
color: ${themeCssVariables.font.color.primary};
@@ -43,8 +42,8 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
isEditable = true,
fromAgentId,
}: SettingsRolePermissionsObjectLevelTableRowProps) => {
const { getIcon } = useIcons();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const Icon = getIcon(objectMetadataItem.icon);
@@ -66,7 +65,9 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
<StyledNameTableCell>
{!!Icon && (
<Icon
style={{ minWidth: theme.icon.size.md }}
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
@@ -13,8 +13,7 @@ import { styled } from '@linaria/react';
import { useContext, useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { v4 } from 'uuid';
import {
type FieldPermission,
@@ -161,7 +160,9 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
<StyledNameTableCell>
{!!Icon && (
<Icon
style={{ minWidth: theme.icon.size.md }}
style={{
minWidth: theme.icon.size.md,
}}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
@@ -1,9 +1,8 @@
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { IconReload, IconX } from 'twenty-ui/display';
import { Checkbox } from 'twenty-ui/input';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { AnimatedRotate } from 'twenty-ui/utilities';
export type OverridableCheckboxType = 'default' | 'override' | 'no_cta';
@@ -24,8 +24,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
import { type CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
import { createRecordLevelPermissionVariablePicker } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionVariablePicker';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div`
align-items: stretch;
@@ -99,7 +98,6 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionValueInput =
recordFilterId,
}: SettingsRolePermissionsObjectLevelRecordLevelPermissionValueInputProps) => {
const { theme } = useContext(ThemeContext);
const { objectMetadataItem } = useContext(AdvancedFilterContext);
const currentRecordFilters = useAtomComponentStateValue(
@@ -1,10 +1,9 @@
/* @license Enterprise */
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { IconVariablePlus } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
import { SettingsRolePermissionsObjectLevelRecordLevelPermissionMeValueSelect } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionMeValueSelect';
@@ -2,10 +2,9 @@ import {
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG,
type SettingsRoleObjectPermissionKey,
} from '@/settings/roles/role-permissions/objects-permissions/constants/SettingsRoleObjectPermissionIconConfig';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
type PermissionIconProps = {
permission: SettingsRoleObjectPermissionKey;
@@ -39,11 +38,10 @@ const StyledIcon = styled.div<{ isRevoked?: boolean }>`
`;
export const PermissionIcon = ({ permission, state }: PermissionIconProps) => {
const { theme } = useContext(ThemeContext);
const { Icon, IconForbidden } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
const { theme } = useContext(ThemeContext);
const isRevoked = state === 'revoked';
return (
@@ -4,11 +4,10 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { Checkbox } from 'twenty-ui/input';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { v4 } from 'uuid';
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
@@ -1,13 +1,12 @@
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { H2Title, IconKey, IconRobot, IconUsers } from 'twenty-ui/display';
import { Checkbox } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledCheckboxContainer = styled.div<{ disabled: boolean }>`
@@ -52,7 +51,6 @@ export const SettingsRoleApplicability = ({
isEditable,
}: SettingsRoleApplicabilityProps) => {
const { theme } = useContext(ThemeContext);
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const options = [