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:
@@ -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';
|
||||
|
||||
+2
-1
@@ -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'>
|
||||
|
||||
+4
-7
@@ -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<{
|
||||
|
||||
+18
-24
@@ -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.
|
||||
|
||||
+5
-3
@@ -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;
|
||||
|
||||
|
||||
+4
-4
@@ -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 && (
|
||||
|
||||
+5
-6
@@ -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,
|
||||
|
||||
+2
-3
@@ -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}
|
||||
|
||||
+1
-1
@@ -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> = {
|
||||
|
||||
+2
-3
@@ -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;
|
||||
|
||||
+7
-8
@@ -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>
|
||||
|
||||
+8
-10
@@ -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>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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
Reference in New Issue
Block a user