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
@@ -2,9 +2,9 @@ import { styled } from '@linaria/react';
import { Avatar } from '@ui/display/avatar/components/Avatar';
import { type AvatarType } from '@ui/display/avatar/types/AvatarType';
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { type Nullable } from '@ui/utilities';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { type Nullable } from '@ui/utilities';
import { isDefined } from 'twenty-shared/utils';
const StyledIconWithBackgroundContainer = styled.div<{
@@ -50,6 +50,7 @@ export const AvatarOrIcon = ({
onClick,
}: AvatarOrIconProps) => {
const { theme } = useContext(ThemeContext);
if (!isDefined(Icon)) {
return (
<Avatar
@@ -1,11 +1,11 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip';
import { type ThemeColor, ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { isDefined } from 'twenty-shared/utils';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
const StyledTag = styled.h3<{
color: TagColor;
@@ -7,7 +7,7 @@ import {
type CatalogStory,
ComponentDecorator,
} from '@ui/testing';
import { MAIN_COLOR_NAMES, type ThemeColor } from '@ui/theme';
import { type ThemeColor, MAIN_COLOR_NAMES } from '@ui/theme';
import { Tag } from '../Tag';
@@ -8,7 +8,7 @@ import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPr
import { type AvatarSize } from '@ui/display/avatar/types/AvatarSize';
import { type AvatarType } from '@ui/display/avatar/types/AvatarType';
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { stringToThemeColorP3String } from '@ui/utilities';
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
import { type Nullable } from 'twenty-shared/types';
@@ -85,6 +85,7 @@ export const Avatar = ({
backgroundColor,
}: AvatarProps) => {
const { theme } = useContext(ThemeContext);
const [invalidAvatarUrls, setInvalidAvatarUrls] = useAtom(
invalidAvatarUrlsAtomV2,
);
@@ -115,16 +116,16 @@ export const Avatar = ({
: (color ??
stringToThemeColorP3String({
string: placeholderColorSeed ?? '',
theme,
variant: 12,
theme,
}));
const fixedBackgroundColor = isPlaceholderFirstCharEmpty
? theme.background.transparent.light
: (backgroundColor ??
stringToThemeColorP3String({
string: placeholderColorSeed ?? '',
theme,
variant: 4,
theme,
}));
const showBackgroundColor = showPlaceholder;
@@ -148,7 +149,7 @@ export const Avatar = ({
size={theme.icon.size.xl}
/>
) : showPlaceholder ? (
<StyledPlaceholderChar fontWeight={theme.font.weight.medium}>
<StyledPlaceholderChar fontWeight={500}>
{placeholderChar}
</StyledPlaceholderChar>
) : (
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { motion } from 'framer-motion';
import { ThemeContext } from '@ui/theme';
import { useContext } from 'react';
import { ThemeContext } from '@ui/theme-constants';
export type AnimatedCheckmarkProps = React.ComponentProps<
typeof motion.path
@@ -18,6 +18,7 @@ export const AnimatedCheckmark = ({
size = 28,
}: AnimatedCheckmarkProps) => {
const { theme } = useContext(ThemeContext);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -3,8 +3,7 @@ import React, { useContext } from 'react';
import { styled } from '@linaria/react';
import { IconCheck } from '@ui/display/icon/components/TablerIcons';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
const StyledContainer = styled.div`
align-items: center;
@@ -1,8 +1,8 @@
import { styled } from '@linaria/react';
import { isDefined } from 'twenty-shared/utils';
import { type ThemeColor } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { isDefined } from 'twenty-shared/utils';
export type ColorSampleVariant = 'default' | 'pipeline';
@@ -1,7 +1,8 @@
import IconAddressBookRaw from '@assets/icons/address-book.svg?react';
import { useContext } from 'react';
import IconAddressBookRaw from '@assets/icons/address-book.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconAddressBookProps = Pick<
IconComponentProps,
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconAnthropicRaw from '@assets/icons/anthropic.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconBrandAnthropicProps = Pick<IconComponentProps, 'size' | 'color'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconBrandGroqRaw from '@assets/icons/groq.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconBrandGroqProps = Pick<IconComponentProps, 'size' | 'color'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconBrandMistralRaw from '@assets/icons/mistral.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconBrandMistralProps = Pick<IconComponentProps, 'size' | 'color'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconBrandXaiRaw from '@assets/icons/xai.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconBrandXaiProps = Pick<IconComponentProps, 'size' | 'color'>;
@@ -4,7 +4,7 @@ import { styled } from '@linaria/react';
import { IconChartBar } from '@ui/display/icon/components/TablerIcons';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
const StyledRotatedIconWrapper = styled.div`
display: inline-flex;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconGmailRaw from '@assets/icons/gmail.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconGmailProps = Pick<IconComponentProps, 'size'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconGoogleRaw from '@assets/icons/google.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconGoogleProps = Pick<IconComponentProps, 'size'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconGoogleCalendarRaw from '@assets/icons/google-calendar.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconGoogleCalendarProps = Pick<IconComponentProps, 'size'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconLockRaw from '@assets/icons/lock.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconLockCustomProps = Pick<IconComponentProps, 'size'>;
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import IconMicrosoftRaw from '@assets/icons/microsoft.svg?react';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
interface IconMicrosoftProps {
size?: number | string;
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import IconMicrosoftCalendarRaw from '@assets/icons/microsoft-calendar.svg?react';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
interface IconMicrosoftCalendarProps {
size?: number | string;
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import IconMicrosoftOutlookRaw from '@assets/icons/microsoft-outlook.svg?react';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
interface IconMicrosoftOutlookProps {
size?: number | string;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconRelationManyToOneRaw from '@assets/icons/many-to-one.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconRelationManyToOneProps = Pick<IconComponentProps, 'size' | 'stroke'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconTrashXOffRaw from '@assets/icons/trash-x-off.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconTrashXOffProps = Pick<IconComponentProps, 'size' | 'stroke'>;
@@ -2,7 +2,7 @@ import { useContext } from 'react';
import IconTwentyStarRaw from '@assets/icons/twenty-star.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconTwentyStarProps = Pick<IconComponentProps, 'size' | 'stroke'>;
@@ -1,14 +1,15 @@
import { useContext } from 'react';
import IconTwentyStarFilledRaw from '@assets/icons/twenty-star-filled.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { THEME_COMMON } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IconTwentyStarFilledProps = Pick<IconComponentProps, 'size' | 'stroke'>;
const iconStrokeMd = THEME_COMMON.icon.stroke.md;
export const IconTwentyStarFilled = (props: IconTwentyStarFilledProps) => {
const { theme } = useContext(ThemeContext);
const size = props.size ?? 24;
const stroke = props.stroke ?? iconStrokeMd;
const stroke = props.stroke ?? theme.icon.stroke.md;
return (
<IconTwentyStarFilledRaw height={size} width={size} strokeWidth={stroke} />
@@ -1,8 +1,10 @@
import IllustrationIconArrayRaw from '@assets/icons/illustration-array.svg?react';
import { useContext } from 'react';
import IllustrationIconArrayRaw from '@assets/icons/illustration-array.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconArrayProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconArray = (props: IllustrationIconArrayProps) => {
@@ -1,8 +1,10 @@
import IllustrationIconCalendarEventRaw from '@assets/icons/illustration-calendar-event.svg?react';
import { useContext } from 'react';
import IllustrationIconCalendarEventRaw from '@assets/icons/illustration-calendar-event.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconCalendarEventProps = Pick<IconComponentProps, 'size'>;
export const IllustrationIconCalendarEvent = (
@@ -1,8 +1,9 @@
import IllustrationIconCalendarTimeRaw from '@assets/icons/illustration-calendar-time.svg?react';
import { useContext } from 'react';
import IllustrationIconCalendarTimeRaw from '@assets/icons/illustration-calendar-time.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconCalendarTimeProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconCurrencyRaw from '@assets/icons/illustration-currency.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconCurrencyProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconFileRaw from '@assets/icons/illustration-file.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconFileProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconJsonRaw from '@assets/icons/illustration-json.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconJsonProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconLinkRaw from '@assets/icons/illustration-link.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconLinkProps = Pick<IconComponentProps, 'size'>;
@@ -1,8 +1,9 @@
import IllustrationIconMailRaw from '@assets/icons/illustration-mail.svg?react';
import { useContext } from 'react';
import IllustrationIconMailRaw from '@assets/icons/illustration-mail.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconMailProps = Pick<IconComponentProps, 'size'>;
@@ -1,8 +1,9 @@
import IllustrationIconManyToManyRaw from '@assets/icons/illustration-many-to-many.svg?react';
import { useContext } from 'react';
import IllustrationIconManyToManyRaw from '@assets/icons/illustration-many-to-many.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconManyToManyProps = Pick<IconComponentProps, 'size'>;
@@ -1,8 +1,9 @@
import IllustrationIconMapRaw from '@assets/icons/illustration-map.svg?react';
import { useContext } from 'react';
import IllustrationIconMapRaw from '@assets/icons/illustration-map.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconMapProps = Pick<IconComponentProps, 'size'>;
@@ -1,8 +1,9 @@
import IllustrationIconNumbersRaw from '@assets/icons/illustration-numbers.svg?react';
import { useContext } from 'react';
import IllustrationIconNumbersRaw from '@assets/icons/illustration-numbers.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconNumbersProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconOneToManyRaw from '@assets/icons/illustration-one-to-many.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconOneToManyProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconOneToOneRaw from '@assets/icons/illustration-one-to-one.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconOneToOneProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconPhoneRaw from '@assets/icons/illustration-phone.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconPhoneProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconSettingRaw from '@assets/icons/illustration-setting.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconSettingProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconStarRaw from '@assets/icons/illustration-star.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconStarProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconTagRaw from '@assets/icons/illustration-tag.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconTagProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconTagsRaw from '@assets/icons/illustration-tags.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconTagsProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconTextRaw from '@assets/icons/illustration-text.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconTextProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconToggleRaw from '@assets/icons/illustration-toggle.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconToggleProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconUidRaw from '@assets/icons/illustration-uid.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconUidProps = Pick<IconComponentProps, 'size'>;
@@ -1,9 +1,9 @@
import { useContext } from 'react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import IllustrationIconUserRaw from '@assets/icons/illustration-user.svg?react';
import { IllustrationIconWrapper } from '@ui/display/icon/components/IllustrationIconWrapper';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
type IllustrationIconUserProps = Pick<IconComponentProps, 'size'>;
@@ -1,5 +1,5 @@
import { renderHook } from '@testing-library/react';
import * as jotai from 'jotai';
import { Provider } from 'jotai';
import {
Icon123,
@@ -8,17 +8,20 @@ import {
} from '@ui/display/icon/components/TablerIcons';
import { useIcons } from '@ui/display/icon/hooks/useIcons';
const mockedStateIcons = {
IconUser,
Icon123,
IconBuildingSkyscraper,
};
jest.mock('jotai', () => ({
...jest.requireActual('jotai'),
useAtomValue: () => mockedStateIcons,
}));
describe('useIcons', () => {
const mockedStateIcons = {
IconUser,
Icon123,
IconBuildingSkyscraper,
};
jest
.spyOn(jotai, 'useAtomValue')
.mockImplementationOnce(() => mockedStateIcons);
const { result } = renderHook(() => useIcons(), {
wrapper: jotai.Provider,
wrapper: Provider,
});
it('returns default icon when no icon key is provided', () => {
@@ -1,9 +1,9 @@
import { styled } from '@linaria/react';
import { IconInfoCircle } from '@ui/display/icon/components/TablerIcons';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { Button } from '@ui/input/button/components/Button/Button';
import React from 'react';
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
export type InfoAccent = 'blue' | 'danger';
@@ -69,10 +69,12 @@ export const Info = ({
onClick,
to,
}: InfoProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledInfo accent={accent}>
<StyledTextContainer>
<IconInfoCircle size={ICON_SIZES.md} />
<IconInfoCircle size={theme.icon.size.md} />
{text}
</StyledTextContainer>
{buttonTitle && to && (
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { MAIN_COLOR_NAMES, type ThemeColor } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { Loader } from '@ui/feedback/loader/components/Loader';
import { type ThemeColor, MAIN_COLOR_NAMES } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
const parseThemeColor = (color: string): ThemeColor =>
(MAIN_COLOR_NAMES as string[]).includes(color)
@@ -4,7 +4,7 @@ import {
type CatalogStory,
ComponentDecorator,
} from '@ui/testing';
import { MAIN_COLOR_NAMES, type ThemeColor } from '@ui/theme';
import { type ThemeColor, MAIN_COLOR_NAMES } from '@ui/theme';
import { expect, fn, userEvent, within } from 'storybook/test';
import { Status } from '../Status';
@@ -3,11 +3,11 @@ import { type ReactNode, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { THEME_COMMON } from '@ui/theme';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from '@ui/theme-constants';
import { AppTooltip, TooltipDelay } from './AppTooltip';
const spacing4 = THEME_COMMON.spacing(4);
const spacing4 = themeCssVariables.spacing[4];
const StyledOverflowingMultilineText = styled.div<{
isContentOverflowing: boolean;
@@ -1,7 +1,7 @@
import { styled } from '@linaria/react';
import { motion } from 'framer-motion';
import { type ThemeColor } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { motion } from 'framer-motion';
const StyledLoaderContainer = styled.div<{
color?: ThemeColor;
@@ -12,4 +12,5 @@ export * from './json-visualizer';
export * from './layout';
export * from './navigation';
export * from './theme';
export * from './theme-constants';
export * from './utilities';
@@ -2,11 +2,11 @@ import { styled } from '@linaria/react';
import { useIsMobile } from '@ui/utilities';
import { getOsShortcutSeparator } from '@ui/utilities/device/getOsShortcutSeparator';
import { type MotionProps, motion } from 'framer-motion';
import React, { useMemo } from 'react';
import React, { useContext, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Pill } from '@ui/components/Pill/Pill';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import {
type ButtonAccent,
type ButtonPosition,
@@ -446,6 +446,7 @@ export const AnimatedButton = ({
dataGloballyPreventClickOutside,
soonLabel = 'Soon',
}: AnimatedButtonProps) => {
const { theme } = useContext(ThemeContext);
const isMobile = useIsMobile();
const isDisabled = soon || disabled;
@@ -492,7 +493,7 @@ export const AnimatedButton = ({
{Icon && (
<StyledIconContainer>
<motion.div animate={animate} transition={transition}>
<Icon size={ICON_SIZES.sm} />
<Icon size={theme.icon.size.sm} />
</motion.div>
</StyledIconContainer>
)}
@@ -4,9 +4,9 @@ import {
type LightIconButtonAccent,
type LightIconButtonSize,
} from '@ui/input/button/components/LightIconButton';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { motion, type MotionProps } from 'framer-motion';
import { type ComponentProps, type MouseEvent } from 'react';
import { type ComponentProps, type MouseEvent, useContext } from 'react';
export type AnimatedLightIconButtonProps = {
className?: string;
@@ -107,6 +107,8 @@ export const AnimatedLightIconButton = ({
onClick,
title,
}: AnimatedLightIconButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
data-testid={testId}
@@ -122,7 +124,9 @@ export const AnimatedLightIconButton = ({
>
<StyledIconContainer animate={animate} transition={transition}>
{Icon && (
<Icon size={size === 'medium' ? ICON_SIZES.md : ICON_SIZES.sm} />
<Icon
size={size === 'medium' ? theme.icon.size.md : theme.icon.size.sm}
/>
)}
</StyledIconContainer>
</StyledButton>
@@ -2,7 +2,8 @@ import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { Loader } from '@ui/feedback';
import { baseTransitionTiming } from '@ui/input/button/components/Button/constant';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
const StyledIcon = styled.div<{
isLoading: boolean;
@@ -41,17 +42,21 @@ export const ButtonIcon = ({
}: {
Icon?: IconComponent;
isLoading?: boolean;
}) => (
<StyledIconWrapper>
{isLoading && (
<StyledLoader>
<Loader />
</StyledLoader>
)}
{Icon && (
<StyledIcon isLoading={!!isLoading}>
<Icon size={ICON_SIZES.sm} />
</StyledIcon>
)}
</StyledIconWrapper>
);
}) => {
const { theme } = useContext(ThemeContext);
return (
<StyledIconWrapper>
{isLoading && (
<StyledLoader>
<Loader />
</StyledLoader>
)}
{Icon && (
<StyledIcon isLoading={!!isLoading}>
<Icon size={theme.icon.size.sm} />
</StyledIcon>
)}
</StyledIconWrapper>
);
};
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
import { Link } from 'react-router-dom';
@@ -115,6 +114,7 @@ export const FloatingButton = ({
to,
}: FloatingButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
disabled={disabled}
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import React, { useContext } from 'react';
export type FloatingIconButtonSize = 'small' | 'medium';
@@ -118,6 +117,7 @@ export const FloatingIconButton = ({
isActive,
}: FloatingIconButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
disabled={disabled}
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { GRAY_SCALE_LIGHT, ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import React, { useContext, useMemo } from 'react';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
export type IconButtonSize = 'medium' | 'small';
export type IconButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
@@ -79,7 +78,7 @@ const computeIconButtonDynamicStyles = (
boxShadow: focusOverride
? `0 0 0 3px ${themeCssVariables.accent.tertiary}`
: 'none',
color: GRAY_SCALE_LIGHT.gray1,
color: themeCssVariables.grayScale.gray1,
opacity: disabled ? 0.24 : 1,
hoverBackground: disabled
? themeCssVariables.color.blue
@@ -100,7 +99,7 @@ const computeIconButtonDynamicStyles = (
boxShadow: focusOverride
? `0 0 0 3px ${themeCssVariables.color.red3}`
: 'none',
color: GRAY_SCALE_LIGHT.gray1,
color: themeCssVariables.grayScale.gray1,
opacity: disabled ? 0.24 : 1,
hoverBackground: disabled
? themeCssVariables.color.red
@@ -292,6 +291,7 @@ export const IconButton = ({
to,
}: IconButtonProps) => {
const { theme } = useContext(ThemeContext);
const dynamicStyles = useMemo(() => {
const styles = computeIconButtonDynamicStyles(
variant,
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import React, { useContext } from 'react';
export type InsideButtonProps = {
@@ -1,7 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { type MouseEvent } from 'react';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type MouseEvent, useContext } from 'react';
export type LightButtonAccent = 'secondary' | 'tertiary';
@@ -89,6 +89,8 @@ export const LightButton = ({
type = 'button',
onClick,
}: LightButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
onClick={onClick}
@@ -99,7 +101,7 @@ export const LightButton = ({
className={className}
active={active}
>
{!!Icon && <Icon size={ICON_SIZES.md} />}
{!!Icon && <Icon size={theme.icon.size.md} />}
{title}
</StyledButton>
);
@@ -1,7 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { type ComponentProps, type MouseEvent } from 'react';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type ComponentProps, type MouseEvent, useContext } from 'react';
export type LightIconButtonAccent = 'secondary' | 'tertiary';
export type LightIconButtonSize = 'small' | 'medium';
@@ -105,6 +105,8 @@ export const LightIconButton = ({
onClick,
title,
}: LightIconButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
data-testid={testId}
@@ -119,7 +121,9 @@ export const LightIconButton = ({
title={title}
>
{Icon && (
<Icon size={size === 'medium' ? ICON_SIZES.md : ICON_SIZES.sm} />
<Icon
size={size === 'medium' ? theme.icon.size.md : theme.icon.size.sm}
/>
)}
</StyledButton>
);
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import React, { type FunctionComponent, useContext } from 'react';
export type MainButtonVariant = 'primary' | 'secondary';
@@ -106,6 +105,7 @@ export const MainButton = ({
className,
}: MainButtonProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledButton
className={className}
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
export type RoundedIconButtonSize = 'small' | 'medium';
@@ -1,6 +1,6 @@
import { Pill } from '@ui/components/Pill/Pill';
import { Avatar, type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { type ReactElement, useContext } from 'react';
import { StyledTabHover } from './StyledTabBase';
@@ -29,6 +29,7 @@ export const TabContent = ({
className,
}: TabContentProps) => {
const { theme } = useContext(ThemeContext);
const iconColor = active
? theme.font.color.primary
: disabled
@@ -5,8 +5,7 @@ import { ResizeHandle } from '@ui/layout/resize-handle/components/ResizeHandle';
import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/code-editor/constants/BaseCodeEditorThemeId';
import { useResizeHandle } from '@ui/layout/resize-handle/hooks/useResizeHandle';
import { getBaseCodeEditorTheme } from '@ui/input/code-editor/theme/utils/getBaseCodeEditorTheme';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { type KeyboardEvent, useContext, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -189,9 +188,7 @@ export const CodeEditor = ({
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme({
theme,
}),
getBaseCodeEditorTheme(theme),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
@@ -1,4 +1,4 @@
import { type ThemeType } from '@ui/theme';
import { type ThemeType } from '@ui/theme-constants';
import { type editor } from 'monaco-editor';
import { isDefined } from 'twenty-shared/utils';
@@ -31,11 +31,9 @@ const convertColorToHex = (color: string): string => {
return color;
};
export const getBaseCodeEditorTheme = ({
theme,
}: {
theme: ThemeType;
}): editor.IStandaloneThemeData => {
export const getBaseCodeEditorTheme = (
theme: ThemeType,
): editor.IStandaloneThemeData => {
return {
base: 'vs',
inherit: true,
@@ -45,12 +43,18 @@ export const getBaseCodeEditorTheme = ({
foreground: convertColorToHex(theme.code.text.gray),
fontStyle: 'bold',
},
{ token: 'keyword', foreground: convertColorToHex(theme.code.text.sky) },
{
token: 'keyword',
foreground: convertColorToHex(theme.code.text.sky),
},
{
token: 'delimiter',
foreground: convertColorToHex(theme.code.text.gray),
},
{ token: 'string', foreground: convertColorToHex(theme.code.text.pink) },
{
token: 'string',
foreground: convertColorToHex(theme.code.text.pink),
},
{
token: 'comment',
foreground: convertColorToHex(theme.code.text.orange),
@@ -1,8 +1,6 @@
import { styled } from '@linaria/react';
import { Checkmark } from '@ui/display/checkmark/components/Checkmark';
import { type ColorScheme } from '@ui/input/types/ColorScheme';
import { GRAY_SCALE_DARK, GRAY_SCALE_LIGHT } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import {
AnimatePresence,
type AnimationControls,
@@ -10,6 +8,9 @@ import {
useAnimation,
} from 'framer-motion';
import React from 'react';
import { GRAY_SCALE_DARK } from '@ui/theme/constants/GrayScaleDark';
import { GRAY_SCALE_LIGHT } from '@ui/theme/constants/GrayScaleLight';
import { themeCssVariables } from '@ui/theme-constants';
const StyledColorSchemeBackground = styled.div<
Pick<ColorSchemeCardProps, 'variant'>
@@ -1,16 +1,15 @@
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { ComponentDecorator } from '@ui/testing';
import { useContext } from 'react';
import { ThemeContext, type ThemeType } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ColorSchemeCard } from '../ColorSchemeCard';
const StyledContainer = styled.div<{ theme: ThemeType }>`
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
> * + * {
margin-left: ${({ theme }) => theme.spacing(4)};
margin-left: ${themeCssVariables.spacing['4']};
}
`;
@@ -19,10 +18,8 @@ const meta: Meta<typeof ColorSchemeCard> = {
component: ColorSchemeCard,
decorators: [
(Story) => {
const { theme } = useContext(ThemeContext);
return (
<StyledContainer theme={theme}>
<StyledContainer>
<Story />
</StyledContainer>
);
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { IconFilter, IconSearch } from '@ui/display';
import { IconButton } from '@ui/input/button/components/IconButton';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type ChangeEvent, type ReactNode, useContext, useState } from 'react';
export type SearchInputProps = {
@@ -4,7 +4,7 @@ import {
type CatalogStory,
ComponentDecorator,
} from '@ui/testing';
import { MAIN_COLORS_LIGHT } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { useState } from 'react';
import { Toggle, type ToggleSize } from '../Toggle';
@@ -88,7 +88,7 @@ export const Catalog: CatalogStory<Story, typeof InteractiveToggle> = {
if (color === 'default') {
return {};
}
return { color: MAIN_COLORS_LIGHT.yellow };
return { color: themeCssVariables.color.yellow };
},
},
],
@@ -2,7 +2,8 @@ import { styled } from '@linaria/react';
import { VisibilityHidden } from '@ui/accessibility';
import { IconChevronDown } from '@ui/display';
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
import { motion } from 'framer-motion';
const StyledButton = styled.button<{
@@ -38,6 +39,7 @@ export const JsonArrow = ({
onClick: () => void;
variant?: 'blue' | 'red';
}) => {
const { theme } = useContext(ThemeContext);
const { arrowButtonCollapsedLabel, arrowButtonExpandedLabel } =
useJsonTreeContextOrThrow();
@@ -59,7 +61,7 @@ export const JsonArrow = ({
animate={{ rotate: isOpen ? 0 : -90 }}
transition={{ duration: 0.3 }}
>
<IconChevronDown size={ICON_SIZES.md} color={iconColor} />
<IconChevronDown size={theme.icon.size.md} color={iconColor} />
</motion.div>
</StyledButton>
);
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display';
import { type JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
const StyledLabelContainer = styled.span<{
@@ -1,21 +1,21 @@
import { styled } from '@linaria/react';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { ComponentDecorator } from '@ui/testing';
import { useContext, useState } from 'react';
import { ThemeContext, type ThemeType } from '@ui/theme';
import { useState } from 'react';
import { themeCssVariables } from '@ui/theme-constants';
import { AnimatedExpandableContainer } from '../components/AnimatedExpandableContainer';
const StyledButton = styled.button<{ theme: ThemeType }>`
padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(4)};
background-color: ${({ theme }) => theme.color.blue10};
color: ${({ theme }) => theme.font.color.primary};
const StyledButton = styled.button`
padding: ${themeCssVariables.spacing['2']} ${themeCssVariables.spacing['4']};
background-color: ${themeCssVariables.color.blue10};
color: ${themeCssVariables.font.color.primary};
border: none;
border-radius: ${({ theme }) => theme.spacing(1)};
border-radius: ${themeCssVariables.spacing['1']};
cursor: pointer;
margin-bottom: ${({ theme }) => theme.spacing(3)};
margin-bottom: ${themeCssVariables.spacing['3']};
&:hover {
background-color: ${({ theme }) => theme.color.blue8};
background-color: ${themeCssVariables.color.blue8};
}
`;
@@ -24,8 +24,8 @@ const StyledButtonWrapper = styled.div<{ dimension: 'width' | 'height' }>`
width: ${({ dimension }) => (dimension === 'height' ? '600px' : 'auto')};
`;
const StyledExpandableWrapper = styled.div<{ theme: ThemeType }>`
background-color: ${({ theme }) => theme.background.primary};
const StyledExpandableWrapper = styled.div`
background-color: ${themeCssVariables.background.primary};
height: 100%;
width: 100%;
`;
@@ -33,17 +33,16 @@ const StyledExpandableWrapper = styled.div<{ theme: ThemeType }>`
const StyledContent = styled.div<{
dimension: 'width' | 'height';
mode: 'scroll-height' | 'fit-content';
theme: ThemeType;
}>`
padding: ${({ theme }) => theme.spacing(3)};
padding: ${themeCssVariables.spacing['3']};
height: ${({ dimension, mode }) =>
dimension === 'height' && mode === 'scroll-height' ? '200px' : 'auto'};
width: ${({ dimension }) => (dimension === 'width' ? '400px' : 'auto')};
p {
color: ${({ theme }) => theme.font.color.primary};
margin-bottom: ${({ theme }) => theme.spacing(2)};
font-size: ${({ theme }) => theme.font.size.md};
color: ${themeCssVariables.font.color.primary};
margin-bottom: ${themeCssVariables.spacing['2']};
font-size: ${themeCssVariables.font.size.md};
}
`;
@@ -63,12 +62,11 @@ const AnimatedExpandableContainerWithButton = ({
isExpanded: initialIsExpanded,
...args
}: AnimatedExpandableContainerWithButtonProps) => {
const { theme } = useContext(ThemeContext);
const [isExpanded, setIsExpanded] = useState(initialIsExpanded);
return (
<StyledButtonWrapper dimension={args.dimension}>
<StyledButton theme={theme} onClick={() => setIsExpanded(!isExpanded)}>
<StyledButton onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? 'Collapse' : 'Expand'}
</StyledButton>
<AnimatedExpandableContainer
@@ -77,12 +75,8 @@ const AnimatedExpandableContainerWithButton = ({
mode={args.mode}
animationDurations={args.animationDurations}
>
<StyledExpandableWrapper theme={theme}>
<StyledContent
dimension={args.dimension}
mode={args.mode}
theme={theme}
>
<StyledExpandableWrapper>
<StyledContent dimension={args.dimension} mode={args.mode}>
<p>
This is some content inside the AnimatedExpandableContainer. It
will animate smoothly when expanding or collapsing.
@@ -4,7 +4,7 @@ import { type AnimationDurations } from '@ui/layout/animated-expandable-containe
import { type AnimationMode } from '@ui/layout/animated-expandable-container/types/AnimationMode';
import { type AnimationSize } from '@ui/layout/animated-expandable-container/types/AnimationSize';
import { getExpandableAnimationConfig } from '@ui/layout/animated-expandable-container/utils/getExpandableAnimationConfig';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { AnimatePresence, motion } from 'framer-motion';
import { type ReactNode, useContext, useRef, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -32,11 +32,13 @@ export const AnimatedExpandableContainer = ({
const contentRef = useRef<HTMLDivElement>(null);
const [size, setSize] = useState<AnimationSize>(0);
const normalDuration = theme.animation.duration.normal;
const actualDurations: AnimationDurationObject =
animationDurations === 'default'
? {
opacity: theme.animation.duration.normal,
size: theme.animation.duration.normal,
opacity: normalDuration,
size: normalDuration,
}
: animationDurations;
@@ -3,7 +3,7 @@ import { BACKGROUND } from '@ui/layout/animated-placeholder/constants/Background
import { DARK_BACKGROUND } from '@ui/layout/animated-placeholder/constants/DarkBackground';
import { DARK_MOVING_IMAGE } from '@ui/layout/animated-placeholder/constants/DarkMovingImage';
import { MOVING_IMAGE } from '@ui/layout/animated-placeholder/constants/MovingImage';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { animate, motion, useMotionValue, useTransform } from 'framer-motion';
import { useContext, useEffect } from 'react';
@@ -38,7 +38,7 @@ interface AnimatedPlaceholderProps {
}
export const AnimatedPlaceholder = ({ type }: AnimatedPlaceholderProps) => {
const { theme } = useContext(ThemeContext);
const { colorScheme } = useContext(ThemeContext);
const x = useMotionValue(window.innerWidth / 2);
const y = useMotionValue(window.innerHeight / 2);
@@ -84,13 +84,13 @@ export const AnimatedPlaceholder = ({ type }: AnimatedPlaceholderProps) => {
return (
<StyledContainer>
<StyledBackgroundImage
src={theme.name === 'dark' ? DARK_BACKGROUND[type] : BACKGROUND[type]}
src={colorScheme === 'dark' ? DARK_BACKGROUND[type] : BACKGROUND[type]}
alt=""
type={type}
/>
<motion.img
src={
theme.name === 'dark' ? DARK_MOVING_IMAGE[type] : MOVING_IMAGE[type]
colorScheme === 'dark' ? DARK_MOVING_IMAGE[type] : MOVING_IMAGE[type]
}
alt=""
style={{
@@ -3,8 +3,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import React, { useContext, useRef } from 'react';
import { createPortal } from 'react-dom';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext } from '@ui/theme';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type ModalOverlay } from '../types/ModalOverlay';
import { type ModalPadding } from '../types/ModalPadding';
@@ -128,9 +127,9 @@ export const Modal = ({
onBackdropMouseDown,
modalRef: externalRef,
}: ModalProps) => {
const { theme } = useContext(ThemeContext);
const internalRef = useRef<HTMLDivElement>(null);
const resolvedRef = externalRef ?? internalRef;
const { theme } = useContext(ThemeContext);
const handleBackdropMouseDown = (e: React.MouseEvent) => {
e.stopPropagation();
@@ -158,7 +157,9 @@ export const Modal = ({
layout
overlay={overlay}
variants={modalAnimation}
transition={{ duration: theme.animation.duration.normal }}
transition={{
duration: theme.animation.duration.normal,
}}
isMobile={isMobile}
gap={gap}
smallBorderRadius={smallBorderRadius}
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { IconPoint } from '@ui/display';
import { Toggle } from '@ui/input';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext, useId } from 'react';
const StyledContainer = styled.div`
@@ -46,21 +45,19 @@ export const AdvancedSettingsToggle = ({
setIsAdvancedModeEnabled,
label = 'Advanced:',
}: AdvancedSettingsToggleProps) => {
const { theme } = useContext(ThemeContext);
const onChange = (newValue: boolean) => {
setIsAdvancedModeEnabled(newValue);
};
const instanceId = useId();
const { theme } = useContext(ThemeContext);
const yellowColor = theme.color.yellow;
return (
<StyledContainer>
<StyledIconContainer>
<IconPoint
size={12}
color={theme.color.yellow}
fill={theme.color.yellow}
/>
<IconPoint size={12} color={yellowColor} fill={yellowColor} />
</StyledIconContainer>
<StyledToggleContainer htmlFor={instanceId}>
<StyledText>{label}</StyledText>
@@ -68,7 +65,7 @@ export const AdvancedSettingsToggle = ({
<Toggle
id={instanceId}
onChange={onChange}
color={theme.color.yellow}
color={yellowColor}
value={isAdvancedModeEnabled}
/>
</StyledToggleContainer>
@@ -1,8 +1,8 @@
import { useContext } from 'react';
import { IconBrandGithub } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { ClickToActionLink } from '@ui/navigation/link/components/ClickToActionLink';
import { GITHUB_LINK } from '../constants/GithubLink';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
interface GithubVersionLinkProps {
version: string;
@@ -6,11 +6,8 @@ import {
TooltipPosition,
type IconComponent,
} from '@ui/display';
import {
ICON_SIZES,
ICON_STROKES,
themeCssVariables,
} from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
const StyledMenuPicker = styled.button<{
@@ -137,6 +134,8 @@ export const MenuPicker = ({
tooltipDelay = TooltipDelay.noDelay,
tooltipOffset = 5,
}: MenuPickerProps) => {
const { theme } = useContext(ThemeContext);
return (
<>
<StyledMenuPicker
@@ -151,7 +150,7 @@ export const MenuPicker = ({
aria-label={label}
>
<StyledIconContainer>
<Icon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
</StyledIconContainer>
{isDefined(label) && showLabel && (
@@ -3,8 +3,8 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
import { IconChartPie, TooltipDelay } from '@ui/display';
import { MenuPicker } from '@ui/navigation/menu/components/MenuPicker';
import { ComponentDecorator } from '@ui/testing';
import { type ReactNode, useContext } from 'react';
import { ThemeContext, type ThemeType } from '@ui/theme';
import { type ReactNode } from 'react';
import { themeCssVariables } from '@ui/theme-constants';
const meta: Meta<typeof MenuPicker> = {
title: 'UI/Navigation/Menu/MenuPicker',
@@ -50,16 +50,15 @@ export const WithoutLabel: Story = {
},
};
const StyledTitle = styled.h4<{ theme: ThemeType }>`
const StyledTitle = styled.h4`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
color: ${themeCssVariables.font.color.tertiary};
font-size: 12px;
margin-bottom: 8px;
`;
const SectionTitle = ({ children }: { children?: ReactNode }) => {
const { theme } = useContext(ThemeContext);
return <StyledTitle theme={theme}>{children}</StyledTitle>;
return <StyledTitle>{children}</StyledTitle>;
};
export const AllStates: Story = {
@@ -11,7 +11,7 @@ import {
import { styled } from '@linaria/react';
import { MenuItemHotKeys } from '@ui/navigation/menu/menu-item/components/MenuItemHotKeys';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { motion } from 'framer-motion';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import {
@@ -134,7 +134,9 @@ export const MenuItem = ({
{hasSubMenu && !disabled && (
<StyledSubMenuIcon
animate={{ rotate: isSubMenuOpened ? 90 : 0 }}
transition={{ duration: theme.animation.duration.normal }}
transition={{
duration: theme.animation.duration.normal,
}}
>
<IconChevronRight
size={theme.icon.size.sm}
@@ -4,9 +4,9 @@ import { Tag } from '@ui/components';
import { type IconComponent } from '@ui/display';
import { Checkbox } from '@ui/input/components/Checkbox';
import { MenuItemLeftContent } from '@ui/navigation/menu/menu-item/internals/components/MenuItemLeftContent';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
import { type ThemeColor } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
const StyledLeftContentWithCheckboxContainer = styled.div`
align-items: center;
@@ -1,7 +1,6 @@
import { useContext } from 'react';
import { IconChevronRight, type IconComponent } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import {
StyledMenuItemBase,
@@ -8,8 +8,7 @@ import {
type IconComponent,
} from '@ui/display';
import { type ReactNode, useContext } from 'react';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import {
StyledMenuItemBase,
@@ -1,4 +1,4 @@
import { useContext, type ReactNode } from 'react';
import { type ReactNode, useContext } from 'react';
import {
StyledMenuItemIconCheck,
@@ -9,8 +9,7 @@ import {
import { styled } from '@linaria/react';
import { OverflowingTextWithTooltip } from '@ui/display';
import { ThemeContext } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { StyledMenuItemSelect } from './MenuItemSelect';
type MenuItemSelectAvatarProps = {
@@ -1,5 +1,3 @@
import { useContext } from 'react';
import {
StyledMenuItemIconCheck,
StyledMenuItemLabel,
@@ -7,7 +5,9 @@ import {
} from '../internals/components/StyledMenuItemBase';
import { ColorSample, type ColorSampleVariant } from '@ui/display';
import { type ThemeColor, ThemeContext } from '@ui/theme';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import {
DEFAULT_COLOR_LABELS,
type ColorLabels,
@@ -1,5 +1,3 @@
import { useContext } from 'react';
import {
StyledMenuItemIconCheck,
StyledMenuItemLeftContent,
@@ -7,7 +5,9 @@ import {
import { Tag } from '@ui/components';
import { type IconComponent } from '@ui/display';
import { type ThemeColor, ThemeContext } from '@ui/theme';
import { type ThemeColor } from '@ui/theme';
import { ThemeContext } from '@ui/theme-constants';
import { useContext } from 'react';
import { StyledMenuItemSelect } from './MenuItemSelect';
type MenuItemSelectTagProps = {
@@ -34,6 +34,7 @@ export const MenuItemSelectTag = ({
LeftIcon,
}: MenuItemSelectTagProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledMenuItemSelect
onClick={onClick}
@@ -8,7 +8,7 @@ import {
type CatalogStory,
ComponentDecorator,
} from '@ui/testing';
import { MAIN_COLOR_NAMES, type ThemeColor } from '@ui/theme';
import { type ThemeColor, MAIN_COLOR_NAMES } from '@ui/theme';
import { MenuItemSelectColor } from '../MenuItemSelectColor';
const meta: Meta<typeof MenuItemSelectColor> = {
@@ -1,8 +1,7 @@
import { useContext } from 'react';
import { type IconComponent } from '@ui/display';
import { useContext } from 'react';
import { ThemeContext } from '@ui/theme-constants';
import { MenuItemIconBoxContainer } from './MenuItemIconBoxContainer';
import { ThemeContext } from '@ui/theme';
export type MenuItemIconProps = {
Icon: IconComponent | null | undefined;
@@ -1,10 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent, IconGripVertical } from '@ui/display';
import {
ICON_SIZES,
ICON_STROKES,
themeCssVariables,
} from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
import { MenuItemIconBoxContainer } from './MenuItemIconBoxContainer';
const StyledIconSwapContainer = styled.div`
@@ -39,6 +36,8 @@ export const MenuItemIconWithGripSwap = ({
withIconContainer = false,
gripIconColor,
}: MenuItemIconWithGripSwapProps) => {
const { theme } = useContext(ThemeContext);
if (!LeftIcon) {
return null;
}
@@ -46,12 +45,12 @@ export const MenuItemIconWithGripSwap = ({
const iconContent = (
<StyledIconSwapContainer>
<StyledDefaultIcon className="grip-swap-default-icon">
<LeftIcon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
<LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
</StyledDefaultIcon>
<StyledHoverIcon className="grip-swap-hover-icon">
<IconGripVertical
size={ICON_SIZES.md}
stroke={ICON_STROKES.sm}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={gripIconColor}
/>
</StyledHoverIcon>
@@ -1,5 +1,5 @@
import { isNonEmptyString, isString } from '@sniptt/guards';
import { type ReactNode } from 'react';
import { type ReactNode, useContext } from 'react';
import { styled } from '@linaria/react';
import {
@@ -7,11 +7,7 @@ import {
IconGripVertical,
OverflowingTextWithTooltip,
} from '@ui/display';
import {
ICON_SIZES,
ICON_STROKES,
themeCssVariables,
} from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { type MenuItemDraggableGripMode } from '../../types/MenuItemDraggableGripMode';
import { MenuItemIcon } from './MenuItemIcon';
import { MenuItemIconBoxContainer } from './MenuItemIconBoxContainer';
@@ -59,6 +55,8 @@ export const MenuItemLeftContent = ({
gripMode = 'never',
disabled = false,
}: MenuItemLeftContentProps) => {
const { theme } = useContext(ThemeContext);
const gripIconColor = withIconContainer
? themeCssVariables.font.color.tertiary
: disabled
@@ -72,8 +70,8 @@ export const MenuItemLeftContent = ({
<MenuItemIconBoxContainer>
<StyledDraggableItem>
<IconGripVertical
size={ICON_SIZES.md}
stroke={ICON_STROKES.sm}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={gripIconColor}
/>
</StyledDraggableItem>
@@ -81,8 +79,8 @@ export const MenuItemLeftContent = ({
) : (
<StyledDraggableItem>
<IconGripVertical
size={ICON_SIZES.md}
stroke={ICON_STROKES.sm}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={gripIconColor}
/>
</StyledDraggableItem>
@@ -1,6 +1,7 @@
import { styled } from '@linaria/react';
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
import { ThemeContext, themeCssVariables } from '@ui/theme-constants';
import { useContext } from 'react';
const StyledIconButton = styled.div<{ isActive?: boolean }>`
align-items: center;
@@ -30,8 +31,15 @@ export const NavigationBarItem = ({
Icon,
isActive,
onClick,
}: NavigationBarItemProps) => (
<StyledIconButton isActive={isActive} onClick={onClick}>
<Icon color={themeCssVariables.grayScale.gray10} size={ICON_SIZES.lg} />
</StyledIconButton>
);
}: NavigationBarItemProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledIconButton isActive={isActive} onClick={onClick}>
<Icon
color={themeCssVariables.grayScale.gray10}
size={theme.icon.size.lg}
/>
</StyledIconButton>
);
};
@@ -1,17 +1,15 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext, type ThemeType } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
const StyledLayout = styled.div<{
width?: number;
backgroundColor?: string | undefined;
height: number | 'fit-content';
theme: ThemeType;
}>`
background: ${({ theme, backgroundColor }) =>
backgroundColor ?? theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.light};
background: ${({ backgroundColor }) =>
backgroundColor ?? themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.light};
border-radius: 5px;
display: flex;
@@ -42,14 +40,11 @@ export const ComponentStorybookLayout = ({
height,
children,
}: ComponentStorybookLayoutProps) => {
const { theme } = useContext(ThemeContext);
return (
<StyledLayout
width={width}
backgroundColor={backgroundColor}
height={isDefined(height) ? height : 'fit-content'}
theme={theme}
>
{children}
</StyledLayout>
@@ -1,36 +1,36 @@
import { styled } from '@linaria/react';
import { isNumber, isString } from '@sniptt/guards';
import { type Decorator } from '@storybook/react-vite';
import { type ComponentProps, type JSX, useContext } from 'react';
import { ThemeContext, type ThemeType } from '@ui/theme';
import { type ComponentProps, type JSX } from 'react';
import { themeCssVariables } from '@ui/theme-constants';
const StyledColumnTitle = styled.h1<{ theme: ThemeType }>`
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(2)};
const StyledColumnTitle = styled.h1`
font-size: ${themeCssVariables.font.size.lg};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin: ${themeCssVariables.spacing[2]};
`;
const StyledRowsTitle = styled.h2<{ theme: ThemeType }>`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(2)};
const StyledRowsTitle = styled.h2`
color: ${themeCssVariables.font.color.secondary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin: ${themeCssVariables.spacing[2]};
width: 100px;
`;
const StyledRowTitle = styled.h3<{ theme: ThemeType }>`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(2)};
const StyledRowTitle = styled.h3`
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin: ${themeCssVariables.spacing[2]};
width: 100px;
`;
const StyledElementTitle = styled.span<{ theme: ThemeType }>`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(1)};
const StyledElementTitle = styled.span`
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.semiBold};
margin-bottom: ${themeCssVariables.spacing[1]};
text-align: center;
text-transform: uppercase;
`;
@@ -40,23 +40,23 @@ const StyledContainer = styled.div`
flex-direction: row;
`;
const StyledColumnContainer = styled.div<{ theme: ThemeType }>`
const StyledColumnContainer = styled.div`
display: flex;
flex-direction: column;
padding: ${({ theme }) => theme.spacing(2)};
padding: ${themeCssVariables.spacing[2]};
`;
const StyledRowsContainer = styled.div<{ theme: ThemeType }>`
const StyledRowsContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledRowContainer = styled.div<{ theme: ThemeType }>`
const StyledRowContainer = styled.div`
display: flex;
flex: 1;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${themeCssVariables.spacing[2]};
`;
const StyledElementContainer = styled.div<{ width: number }>`
@@ -64,11 +64,11 @@ const StyledElementContainer = styled.div<{ width: number }>`
min-width: ${({ width }) => (width ? `${width}px` : 'auto')};
`;
const StyledCellContainer = styled.div<{ theme: ThemeType }>`
const StyledCellContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
padding: ${({ theme }) => theme.spacing(2)};
padding: ${themeCssVariables.spacing[2]};
`;
const emptyDimension = {
@@ -98,8 +98,6 @@ export type CatalogOptions = {
};
export const CatalogDecorator: Decorator = (Story, context) => {
const { theme } = useContext(ThemeContext);
const {
catalog: { dimensions = [], options = {} } = {
dimensions: [],
@@ -121,31 +119,27 @@ export const CatalogDecorator: Decorator = (Story, context) => {
return (
<StyledContainer>
{dimension4.values.map((value4: any, index4: number) => (
<StyledColumnContainer key={`d4-${index4}`} theme={theme}>
<StyledColumnTitle theme={theme}>
<StyledColumnContainer key={`d4-${index4}`}>
<StyledColumnTitle>
{dimension4.labels?.(value4) ??
(isStringOrNumber(value4) ? value4 : '')}
</StyledColumnTitle>
{dimension3.values.map((value3: any, index3: number) => (
<StyledRowsContainer key={`d3-${index3}`} theme={theme}>
<StyledRowsTitle theme={theme}>
<StyledRowsContainer key={`d3-${index3}`}>
<StyledRowsTitle>
{dimension3.labels?.(value3) ??
(isStringOrNumber(value3) ? value3 : '')}
</StyledRowsTitle>
{dimension2.values.map((value2: any, index2: number) => (
<StyledRowContainer key={`d2-${index2}`} theme={theme}>
<StyledRowTitle theme={theme}>
<StyledRowContainer key={`d2-${index2}`}>
<StyledRowTitle>
{dimension2.labels?.(value2) ??
(isStringOrNumber(value2) ? value2 : '')}
</StyledRowTitle>
{dimension1.values.map((value1: any, index1: number) => {
return (
<StyledCellContainer
key={`d1-${index1}`}
id={value1}
theme={theme}
>
<StyledElementTitle theme={theme}>
<StyledCellContainer key={`d1-${index1}`} id={value1}>
<StyledElementTitle>
{dimension1.labels?.(value1) ??
(isStringOrNumber(value1) ? value1 : '')}
</StyledElementTitle>
@@ -1,6 +1,6 @@
import { type Decorator } from '@storybook/react-vite';
import { GRAY_SCALE_LIGHT, MAIN_COLORS_LIGHT } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
@@ -9,11 +9,11 @@ const getBackgroundColor = (inverted: boolean, accent: string) => {
switch (accent) {
case 'default':
return GRAY_SCALE_LIGHT.gray11;
return themeCssVariables.grayScale.gray11;
case 'danger':
return MAIN_COLORS_LIGHT.red;
return themeCssVariables.color.red;
case 'blue':
return MAIN_COLORS_LIGHT.blue;
return themeCssVariables.color.blue;
default:
return undefined;
}
@@ -0,0 +1,119 @@
import { createContext, useLayoutEffect, useState } from 'react';
import { themeCssVariables } from './themeCssVariables';
type StringLeaves<T> = {
[K in keyof T]: T[K] extends string ? string : StringLeaves<T[K]>;
};
type DeepMerge<T, U> = {
[K in keyof T]: K extends keyof U
? U[K] extends Record<string, unknown>
? T[K] extends Record<string, unknown>
? DeepMerge<T[K], U[K]>
: U[K]
: U[K]
: T[K];
};
// CSS variables that resolve to pure numbers at runtime
type NumericOverrides = {
icon: {
size: { sm: number; md: number; lg: number; xl: number };
stroke: { sm: number; md: number; lg: number };
};
animation: {
duration: { instant: number; fast: number; normal: number; slow: number };
};
text: {
lineHeight: { lg: number; md: number };
iconSizeMedium: number;
iconSizeSmall: number;
iconStrikeLight: number;
iconStrikeMedium: number;
iconStrikeBold: number;
};
spacingMultiplicator: number;
lastLayerZIndex: number;
};
export type ThemeType = DeepMerge<
StringLeaves<typeof themeCssVariables>,
NumericOverrides
>;
export type ThemeContextType = {
theme: ThemeType;
colorScheme: 'light' | 'dark';
};
const computeThemeFromCss = (): ThemeType => {
const root = document?.documentElement;
if (!root) {
return themeCssVariables as unknown as ThemeType;
}
const computedStyle = getComputedStyle(root);
const resolve = (obj: Record<string, unknown>): Record<string, unknown> => {
const result: Record<string, unknown> = {};
for (const key of Object.keys(obj)) {
const value = obj[key];
if (typeof value === 'string' && value.startsWith('var(')) {
const varName = value.slice(4, -1);
const raw = computedStyle.getPropertyValue(varName).trim();
const num = Number(raw);
result[key] = raw !== '' && !isNaN(num) ? num : raw;
} else if (typeof value === 'object' && value !== null) {
result[key] = resolve(value as Record<string, unknown>);
} else {
result[key] = value;
}
}
return result;
};
return resolve(
themeCssVariables as unknown as Record<string, unknown>,
) as unknown as ThemeType;
};
const applyColorSchemeClass = (colorScheme: 'light' | 'dark') => {
const root = document?.documentElement;
if (!root?.classList) return;
root.classList.toggle('dark', colorScheme === 'dark');
root.classList.toggle('light', colorScheme === 'light');
};
export const ThemeContext = createContext<ThemeContextType>({
theme: themeCssVariables as unknown as ThemeType,
colorScheme: 'light',
});
export const ThemeProvider = ({
children,
colorScheme,
}: {
children: React.ReactNode;
colorScheme: 'light' | 'dark';
}) => {
const [theme, setTheme] = useState<ThemeType>(() => {
applyColorSchemeClass(colorScheme);
return computeThemeFromCss();
});
useLayoutEffect(() => {
applyColorSchemeClass(colorScheme);
setTheme(computeThemeFromCss());
}, [colorScheme]);
return (
<ThemeContext.Provider value={{ theme, colorScheme }}>
{children}
</ThemeContext.Provider>
);
};
@@ -10,7 +10,7 @@ describe('getNextThemeColor', () => {
const currentColor: ThemeColor = MAIN_COLOR_NAMES[0];
const nextColor: ThemeColor = MAIN_COLOR_NAMES[1];
expect(getNextThemeColor(currentColor)).toBe(nextColor);
expect(getNextThemeColor(MAIN_COLOR_NAMES, currentColor)).toBe(nextColor);
});
it('returns the first color when reaching the end', () => {
@@ -18,11 +18,11 @@ describe('getNextThemeColor', () => {
MAIN_COLOR_NAMES[MAIN_COLOR_NAMES.length - 1];
const nextColor: ThemeColor = MAIN_COLOR_NAMES[0];
expect(getNextThemeColor(currentColor)).toBe(nextColor);
expect(getNextThemeColor(MAIN_COLOR_NAMES, currentColor)).toBe(nextColor);
});
it('returns the first color when currentColorIsUndefined', () => {
const firstColor: ThemeColor = MAIN_COLOR_NAMES[0];
expect(getNextThemeColor(undefined)).toBe(firstColor);
expect(getNextThemeColor(MAIN_COLOR_NAMES, undefined)).toBe(firstColor);
});
});
@@ -1,18 +1,3 @@
// CSS custom properties don't work in media queries, so MOBILE_VIEWPORT
// must be a static number rather than a var(--...) reference.
export const MOBILE_VIEWPORT = 768;
// Numeric icon size/stroke constants for components that require pixel values
// (e.g. icon size props) rather than CSS variable strings.
export const ICON_SIZES = {
sm: 14,
md: 16,
lg: 20,
xl: 24,
} as const;
export const ICON_STROKES = {
sm: 1.6,
md: 2,
lg: 2.5,
} as const;
@@ -0,0 +1,15 @@
import { type ThemeColor } from '@ui/theme/constants/MainColorNames';
export const getNextThemeColor = (
colorNames: ThemeColor[],
currentColor?: ThemeColor,
): ThemeColor => {
if (currentColor === null || currentColor === undefined) {
return colorNames[0];
}
const currentColorIndex = colorNames.findIndex(
(color) => color === currentColor,
);
const nextColorIndex = (currentColorIndex + 1) % colorNames.length;
return colorNames[nextColorIndex];
};

Some files were not shown because too many files have changed in this diff Show More