[DevXP] Simplify twenty-ui theme system: replace auto-generated files with static CSS variables (#18389)

## Summary

Now that Twenty has fully migrated from Emotion to Linaria, the theme
system has been simplified to remove unnecessary complexity that existed
only to support the old runtime injection pattern.

### What changed

- **Deleted** `generateThemeConstants.ts` script and the entire
`generated/` directory — no more auto-generation
- **Added** `theme-light.css` and `theme-dark.css`: static CSS files
with 991 custom properties each, scoped under `.light` and `.dark`
selectors respectively
- **Moved** `themeCssVariables.ts` out of `generated/` and hand-maintain
it as a static `as const` object of `var(--t-*)` references (Linaria can
statically evaluate these at build time)
- **Extracted** numeric constants (`MOBILE_VIEWPORT`, `ICON_SIZES`,
`ICON_STROKES`) into a new `constants.ts` — CSS variables can't be used
in media queries or as numeric icon size props
- **Simplified** `ThemeContextProvider`: removed
`ThemeCssVariableInjectorEffect` entirely; now uses a single
`useLayoutEffect` to toggle `.light`/`.dark` class on `<html>`
- **Added** `class="light"` to `index.html` as default to prevent FOUC
before React hydration

### Why

The previous setup maintained a dual system: JS theme objects
(`THEME_LIGHT`/`THEME_DARK`) used at runtime, plus a generation script
that produced CSS variable entry arrays, which were then injected into
the DOM by `ThemeCssVariableInjectorEffect`. With Linaria, theme values
only need to be CSS custom properties — the JS objects were redundant.
This PR removes ~250 lines of infrastructure while keeping the same
theming capabilities.
This commit is contained in:
Charles Bochet
2026-03-04 23:30:25 +01:00
committed by GitHub
parent 4c001778c2
commit c41a8e2b23
61 changed files with 4580 additions and 3244 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" translate="no">
<html lang="en" translate="no" class="light">
<head>
<meta charset="UTF-8" />
+2
View File
@@ -3,6 +3,8 @@ import ReactDOM from 'react-dom/client';
import { App } from '@/app/components/App';
import 'react-loading-skeleton/dist/skeleton.css';
import 'twenty-ui/style.css';
import 'twenty-ui/theme-light.css';
import 'twenty-ui/theme-dark.css';
import './index.css';
const root = ReactDOM.createRoot(
@@ -18,12 +18,12 @@ import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-ac
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor';
import { t } from '@lingui/core/macro';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { CommandMenuPages, CoreObjectNameSingular } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { ICON_SIZES, ICON_STROKES } from 'twenty-ui/theme-constants';
import { CommandMenuPageInfoLayout } from './CommandMenuPageInfoLayout';
export const CommandMenuWorkflowStepInfo = ({
@@ -31,7 +31,6 @@ export const CommandMenuWorkflowStepInfo = ({
}: {
commandMenuPageInstanceId: string;
}) => {
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const commandMenuPage = useAtomStateValue(commandMenuPageState);
@@ -121,14 +120,8 @@ export const CommandMenuWorkflowStepInfo = ({
: getActionIcon(stepDefinition.definition.type);
const headerIconColor = isTrigger
? getTriggerIconColor({
theme,
triggerType: stepDefinition.definition.type,
})
: getActionIconColorOrThrow({
theme,
actionType: stepDefinition.definition.type,
});
? getTriggerIconColor(stepDefinition.definition.type)
: getActionIconColorOrThrow(stepDefinition.definition.type);
const headerType = isTrigger ? t`Trigger` : t`Action`;
@@ -176,7 +169,7 @@ export const CommandMenuWorkflowStepInfo = ({
<CommandMenuPageInfoLayout
icon={
headerIcon ? (
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
<Icon size={ICON_SIZES.md} stroke={ICON_STROKES.sm} />
) : undefined
}
iconColor={headerIconColor}
@@ -21,15 +21,13 @@ import { CommandMenuPages, CoreObjectNameSingular } from 'twenty-shared/types';
import { useRunWorkflowRunOpeningInCommandMenuSideEffects } from '@/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects';
import { t } from '@lingui/core/macro';
import { useStore } from 'jotai';
import { useCallback, useContext } from 'react';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { v4 } from 'uuid';
import { ThemeContext } from 'twenty-ui/theme';
export const useOpenRecordInCommandMenu = () => {
const store = useStore();
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const { navigateCommandMenu } = useCommandMenu();
@@ -168,10 +166,9 @@ export const useOpenRecordInCommandMenu = () => {
? getIcon(objectMetadataItem.icon)
: getIcon('IconList');
const IconColor = getIconColorForObjectType({
objectType: objectMetadataItem.nameSingular,
theme,
});
const IconColor = getIconColorForObjectType(
objectMetadataItem.nameSingular,
);
const objectLabelSingular = objectMetadataItem.labelSingular;
@@ -210,7 +207,6 @@ export const useOpenRecordInCommandMenu = () => {
navigateCommandMenu,
openNewRecordTitleCell,
runWorkflowRunOpeningInCommandMenuSideEffects,
theme,
store,
],
);
@@ -5,9 +5,8 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { styled } from '@linaria/react';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type ThemeColor, ThemeContext } from 'twenty-ui/theme';
import { type ThemeColor } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
type ChartColorGradientOptionProps = {
colorOption: {
@@ -33,8 +32,8 @@ export const ChartColorGradientOption = ({
onSelectColor,
}: ChartColorGradientOptionProps) => {
const colorName = colorOption.colorName as ThemeColor;
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const colorSamples = (
<StyledColorSamplesContainer>
@@ -6,10 +6,9 @@ import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { ColorSample } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type ThemeColor, ThemeContext } from 'twenty-ui/theme';
import { type ThemeColor } from 'twenty-ui/theme';
import { getMainColorNameFromPaletteColorName } from 'twenty-ui/utilities';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
type ChartColorPaletteOptionProps = {
selectedItemId: string | null;
@@ -28,9 +27,7 @@ export const ChartColorPaletteOption = ({
currentColor,
onSelectColor,
}: ChartColorPaletteOptionProps) => {
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const paletteColors = Array.from(
{ length: CHART_SETTINGS_PALETTE_COLOR_GROUP_COUNT },
@@ -15,8 +15,6 @@ import { useLingui } from '@lingui/react/macro';
import { IconFunction } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
export type WorkflowActionSelection = {
type: WorkflowActionType;
@@ -32,7 +30,6 @@ export const CommandMenuWorkflowSelectAction = ({
const isDraftEmailEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_DRAFT_EMAIL_ENABLED,
);
const { theme } = useContext(ThemeContext);
const { t } = useLingui();
@@ -114,10 +111,7 @@ export const CommandMenuWorkflowSelectAction = ({
withIconContainer={true}
LeftIcon={() => (
<IconFunction
color={getActionIconColorOrThrow({
theme,
actionType: 'LOGIC_FUNCTION',
})}
color={getActionIconColorOrThrow('LOGIC_FUNCTION')}
size={16}
/>
)}
@@ -2,8 +2,6 @@ import { type WorkflowActionType } from '@/workflow/types/Workflow';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useIcons } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
type Action = {
defaultLabel: string;
@@ -18,7 +16,6 @@ export const WorkflowActionMenuItems = ({
actions: Action[];
onClick: (actionType: WorkflowActionType) => void;
}) => {
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
return (
@@ -31,13 +28,7 @@ export const WorkflowActionMenuItems = ({
withIconContainer={true}
key={action.type}
LeftIcon={() => (
<Icon
color={getActionIconColorOrThrow({
theme,
actionType: action.type,
})}
size={16}
/>
<Icon color={getActionIconColorOrThrow(action.type)} size={16} />
)}
text={action.defaultLabel}
onClick={() => onClick(action.type)}
@@ -1,8 +1,7 @@
import { isNonEmptyString } from '@sniptt/guards';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { Avatar, useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
@@ -23,7 +22,6 @@ export const NavigationMenuItemIcon = ({
}: {
navigationMenuItem: ProcessedNavigationMenuItem;
}) => {
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
@@ -75,14 +73,14 @@ export const NavigationMenuItemIcon = ({
!isRecord &&
isNonEmptyString(effectiveColor);
const iconStyle = useStyledIcon
? getNavigationMenuItemIconStyleFromColor(theme, effectiveColor)
? getNavigationMenuItemIconStyleFromColor(effectiveColor)
: null;
const iconColorToUse = iconStyle
? iconStyle.iconColor
: StandardIcon
? IconColor
: theme.font.color.secondary;
: themeCssVariables.font.color.secondary;
const avatar = (
<Avatar
@@ -1,6 +1,5 @@
import { useContext } from 'react';
import type { IconComponent } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { ICON_SIZES, ICON_STROKES } from 'twenty-ui/theme-constants';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { getNavigationMenuItemIconStyleFromColor } from '@/navigation-menu-item/utils/get-navigation-menu-item-icon-style-from-color';
@@ -14,16 +13,15 @@ export const NavigationMenuItemStyleIcon = ({
Icon,
color,
}: NavigationMenuItemStyleIconProps) => {
const { theme } = useContext(ThemeContext);
const style = getNavigationMenuItemIconStyleFromColor(theme, color);
const style = getNavigationMenuItemIconStyleFromColor(color);
return (
<StyledNavigationMenuItemIconContainer
$backgroundColor={style.backgroundColor}
$borderColor={style.borderColor}
>
<Icon
size={theme.icon.size.md}
stroke={theme.icon.stroke.md}
size={ICON_SIZES.md}
stroke={ICON_STROKES.md}
color={style.iconColor}
/>
</StyledNavigationMenuItemIconContainer>
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import type { IconComponent } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
import { ICON_STROKES, themeCssVariables } from 'twenty-ui/theme-constants';
import { getNavigationMenuItemIconStyleFromColor } from '@/navigation-menu-item/utils/get-navigation-menu-item-icon-style-from-color';
@@ -57,11 +56,7 @@ export const ObjectIconWithViewOverlay = ({
ViewIcon,
objectColor,
}: ObjectIconWithViewOverlayProps) => {
const { theme } = useContext(ThemeContext);
const objectStyle = getNavigationMenuItemIconStyleFromColor(
theme,
objectColor,
);
const objectStyle = getNavigationMenuItemIconStyleFromColor(objectColor);
return (
<StyledCompositeContainer>
@@ -71,15 +66,15 @@ export const ObjectIconWithViewOverlay = ({
>
<ObjectIcon
size="14px"
stroke={theme.icon.stroke.md}
stroke={ICON_STROKES.md}
color={objectStyle.iconColor}
/>
</StyledObjectIconWrapper>
<StyledViewOverlay $backgroundColor={theme.grayScale.gray4}>
<StyledViewOverlay $backgroundColor={themeCssVariables.grayScale.gray4}>
<ViewIcon
size="10px"
stroke={theme.icon.stroke.lg}
color={theme.grayScale.gray10}
stroke={ICON_STROKES.lg}
color={themeCssVariables.grayScale.gray10}
/>
</StyledViewOverlay>
</StyledCompositeContainer>
@@ -1,11 +1,15 @@
import type { ThemeColor, ThemeType } from 'twenty-ui/theme';
import type { ThemeColor } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const getColorFromTheme = (
theme: ThemeType,
themeColor: ThemeColor,
shade: number,
): string => {
const colorMap = theme.color as unknown as Record<string, string>;
const colorMap = themeCssVariables.color as unknown as Record<string, string>;
const tagText = themeCssVariables.tag.text as unknown as Record<
string,
string
>;
const key = `${themeColor}${shade}`;
return colorMap[key] ?? theme.tag.text[themeColor];
return colorMap[key] ?? tagText[themeColor];
};
@@ -1,9 +1,8 @@
import type { ThemeColor, ThemeType } from 'twenty-ui/theme';
import type { ThemeColor } from 'twenty-ui/theme';
import { getColorFromTheme } from '@/navigation-menu-item/utils/get-color-from-theme.util';
import { COLOR_SHADE_BORDER } from '@/navigation-menu-item/utils/NavigationMenuItemIconColorShadeBorder.constant';
export const getNavigationMenuItemIconBorderColor = (
theme: ThemeType,
themeColor: ThemeColor,
): string => getColorFromTheme(theme, themeColor, COLOR_SHADE_BORDER);
): string => getColorFromTheme(themeColor, COLOR_SHADE_BORDER);
@@ -1,5 +1,3 @@
import type { ThemeType } from 'twenty-ui/theme';
import { DEFAULT_NAV_ITEM_ICON_COLOR } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultIconColor.constant';
import { getColorFromTheme } from '@/navigation-menu-item/utils/get-color-from-theme.util';
import { COLOR_SHADE_BACKGROUND } from '@/navigation-menu-item/utils/NavigationMenuItemIconColorShadeBackground.constant';
@@ -9,17 +7,12 @@ import { type NavigationMenuItemIconStyle } from '@/navigation-menu-item/utils/n
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
export const getNavigationMenuItemIconStyleFromColor = (
theme: ThemeType,
color: string | null | undefined,
): NavigationMenuItemIconStyle => {
const themeColor = parseThemeColor(color ?? DEFAULT_NAV_ITEM_ICON_COLOR);
return {
backgroundColor: getColorFromTheme(
theme,
themeColor,
COLOR_SHADE_BACKGROUND,
),
iconColor: getColorFromTheme(theme, themeColor, COLOR_SHADE_ICON),
borderColor: getColorFromTheme(theme, themeColor, COLOR_SHADE_BORDER),
backgroundColor: getColorFromTheme(themeColor, COLOR_SHADE_BACKGROUND),
iconColor: getColorFromTheme(themeColor, COLOR_SHADE_ICON),
borderColor: getColorFromTheme(themeColor, COLOR_SHADE_BORDER),
};
};
@@ -1,18 +1,9 @@
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
import { getIconForObjectType } from '@/object-metadata/utils/getIconForObjectType';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
export const useGetStandardObjectIcon = (objectNameSingular: string) => {
const { theme } = useContext(ThemeContext);
const { Icon, IconColor } = {
Icon: getIconForObjectType(objectNameSingular),
IconColor: getIconColorForObjectType({
objectType: objectNameSingular,
theme,
}),
};
const Icon = getIconForObjectType(objectNameSingular);
const IconColor = getIconColorForObjectType(objectNameSingular);
return { Icon, IconColor };
};
@@ -1,17 +1,11 @@
import { type ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const getIconColorForObjectType = ({
objectType,
theme,
}: {
objectType: string;
theme: ThemeType;
}): string => {
export const getIconColorForObjectType = (objectType: string): string => {
switch (objectType) {
case 'note':
return theme.color.yellow;
return themeCssVariables.color.yellow;
case 'task':
return theme.color.blue;
return themeCssVariables.color.blue;
default:
return 'currentColor';
}
@@ -23,11 +23,10 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { styled } from '@linaria/react';
import { useContext, useMemo, useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useDebouncedCallback } from 'use-debounce';
import { BarChartLayout } from '~/generated-metadata/graphql';
import { ThemeContext } from 'twenty-ui/theme';
type GraphWidgetBarChartProps = {
colorMode: GraphColorMode;
@@ -84,8 +83,7 @@ export const GraphWidgetBarChart = ({
customFormatter,
onSliceClick,
}: GraphWidgetBarChartProps) => {
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const [chartWidth, setChartWidth] = useState<number>(0);
const [chartHeight, setChartHeight] = useState<number>(0);
@@ -18,8 +18,6 @@ import {
} from '@nivo/radial-bar';
import { isDefined } from 'twenty-shared/utils';
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
type GraphWidgetGaugeChartProps = {
data: GaugeChartData;
@@ -71,8 +69,7 @@ export const GraphWidgetGaugeChart = ({
customFormatter,
onGaugeClick,
}: GraphWidgetGaugeChartProps) => {
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const formatOptions: GraphValueFormatOptions = {
displayType,
@@ -34,10 +34,9 @@ import {
type Point,
type SliceTooltipProps,
} from '@nivo/line';
import { useCallback, useContext, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useDebouncedCallback } from 'use-debounce';
import { ThemeContext } from 'twenty-ui/theme';
type CrosshairLayerProps = LineCustomSvgLayerProps<LineSeries>;
type PointLabelsLayerProps = LineCustomSvgLayerProps<LineSeries>;
@@ -92,8 +91,7 @@ export const GraphWidgetLineChart = ({
customFormatter,
onSliceClick,
}: GraphWidgetLineChartProps) => {
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const chartTheme = useLineChartTheme();
const containerRef = useRef<HTMLDivElement>(null);
const [chartWidth, setChartWidth] = useState(0);
@@ -207,7 +205,7 @@ export const GraphWidgetLineChart = ({
<CustomPointLabelsLayer
points={layerProps.points}
formatValue={(value) => formatGraphValue(value, formatOptions)}
offset={theme.spacingMultiplicator * 2}
offset={8}
groupMode={groupMode}
omitNullValues={omitNullValues}
enablePointLabel={enablePointLabel}
@@ -1,4 +1,5 @@
import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphWidgetLegend';
import { CHART_MOTION_CONFIG } from '@/page-layout/widgets/graph/constants/ChartMotionConfig';
import { CustomArcsLayer } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/CustomArcsLayer';
@@ -23,7 +24,6 @@ import {
import {
type MouseEvent as ReactMouseEvent,
useCallback,
useContext,
useMemo,
useRef,
} from 'react';
@@ -32,7 +32,6 @@ import {
type PieChartConfiguration,
type PieChartDataItem,
} from '~/generated-metadata/graphql';
import { ThemeContext } from 'twenty-ui/theme';
type GraphWidgetPieChartProps = {
data: PieChartDataItemWithColor[];
@@ -89,8 +88,7 @@ export const GraphWidgetPieChart = ({
showDataLabels = false,
showCenterMetric = true,
}: GraphWidgetPieChartProps) => {
const { theme } = useContext(ThemeContext);
const colorRegistry = createGraphColorRegistry(theme);
const colorRegistry = createGraphColorRegistry();
const containerRef = useRef<HTMLDivElement>(null);
const setGraphWidgetPieTooltip = useSetAtomComponentState(
graphWidgetPieTooltipComponentState,
@@ -140,7 +138,7 @@ export const GraphWidgetPieChart = ({
const chartData = hasNoData ? emptyStateData : enrichedData;
const chartColors = hasNoData
? [theme.background.tertiary]
? [themeCssVariables.background.tertiary]
: enrichedData.map((item) => item.colorScheme.solid);
const pieChartPadAngle = hasNoData || enrichedData.length <= 1 ? 0 : 0.4;
@@ -205,13 +203,13 @@ export const GraphWidgetPieChart = ({
}}
arcLinkLabelsDiagonalLength={10}
arcLinkLabelsStraightLength={10}
arcLinkLabelsTextColor={theme.font.color.light}
arcLinkLabelsColor={theme.font.color.extraLight}
arcLinkLabelsTextColor={themeCssVariables.font.color.light}
arcLinkLabelsColor={themeCssVariables.font.color.extraLight}
theme={{
labels: {
text: {
fontSize: theme.font.size.sm,
fontWeight: theme.font.weight.medium,
fontSize: themeCssVariables.font.size.sm,
fontWeight: themeCssVariables.font.weight.medium,
},
},
}}
@@ -1,458 +1,455 @@
import { type ThemeType } from 'twenty-ui/theme';
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const createGraphColorRegistry = (
theme: ThemeType,
): GraphColorRegistry => ({
export const createGraphColorRegistry = (): GraphColorRegistry => ({
blue: {
name: 'blue',
solid: theme.color.blue8,
solid: themeCssVariables.color.blue8,
variations: [
theme.color.blue1,
theme.color.blue2,
theme.color.blue3,
theme.color.blue4,
theme.color.blue5,
theme.color.blue6,
theme.color.blue7,
theme.color.blue8,
theme.color.blue9,
theme.color.blue10,
theme.color.blue11,
theme.color.blue12,
themeCssVariables.color.blue1,
themeCssVariables.color.blue2,
themeCssVariables.color.blue3,
themeCssVariables.color.blue4,
themeCssVariables.color.blue5,
themeCssVariables.color.blue6,
themeCssVariables.color.blue7,
themeCssVariables.color.blue8,
themeCssVariables.color.blue9,
themeCssVariables.color.blue10,
themeCssVariables.color.blue11,
themeCssVariables.color.blue12,
],
},
purple: {
name: 'purple',
solid: theme.color.purple8,
solid: themeCssVariables.color.purple8,
variations: [
theme.color.purple1,
theme.color.purple2,
theme.color.purple3,
theme.color.purple4,
theme.color.purple5,
theme.color.purple6,
theme.color.purple7,
theme.color.purple8,
theme.color.purple9,
theme.color.purple10,
theme.color.purple11,
theme.color.purple12,
themeCssVariables.color.purple1,
themeCssVariables.color.purple2,
themeCssVariables.color.purple3,
themeCssVariables.color.purple4,
themeCssVariables.color.purple5,
themeCssVariables.color.purple6,
themeCssVariables.color.purple7,
themeCssVariables.color.purple8,
themeCssVariables.color.purple9,
themeCssVariables.color.purple10,
themeCssVariables.color.purple11,
themeCssVariables.color.purple12,
],
},
turquoise: {
name: 'turquoise',
solid: theme.color.turquoise8,
solid: themeCssVariables.color.turquoise8,
variations: [
theme.color.turquoise1,
theme.color.turquoise2,
theme.color.turquoise3,
theme.color.turquoise4,
theme.color.turquoise5,
theme.color.turquoise6,
theme.color.turquoise7,
theme.color.turquoise8,
theme.color.turquoise9,
theme.color.turquoise10,
theme.color.turquoise11,
theme.color.turquoise12,
themeCssVariables.color.turquoise1,
themeCssVariables.color.turquoise2,
themeCssVariables.color.turquoise3,
themeCssVariables.color.turquoise4,
themeCssVariables.color.turquoise5,
themeCssVariables.color.turquoise6,
themeCssVariables.color.turquoise7,
themeCssVariables.color.turquoise8,
themeCssVariables.color.turquoise9,
themeCssVariables.color.turquoise10,
themeCssVariables.color.turquoise11,
themeCssVariables.color.turquoise12,
],
},
orange: {
name: 'orange',
solid: theme.color.orange8,
solid: themeCssVariables.color.orange8,
variations: [
theme.color.orange1,
theme.color.orange2,
theme.color.orange3,
theme.color.orange4,
theme.color.orange5,
theme.color.orange6,
theme.color.orange7,
theme.color.orange8,
theme.color.orange9,
theme.color.orange10,
theme.color.orange11,
theme.color.orange12,
themeCssVariables.color.orange1,
themeCssVariables.color.orange2,
themeCssVariables.color.orange3,
themeCssVariables.color.orange4,
themeCssVariables.color.orange5,
themeCssVariables.color.orange6,
themeCssVariables.color.orange7,
themeCssVariables.color.orange8,
themeCssVariables.color.orange9,
themeCssVariables.color.orange10,
themeCssVariables.color.orange11,
themeCssVariables.color.orange12,
],
},
pink: {
name: 'pink',
solid: theme.color.pink8,
solid: themeCssVariables.color.pink8,
variations: [
theme.color.pink1,
theme.color.pink2,
theme.color.pink3,
theme.color.pink4,
theme.color.pink5,
theme.color.pink6,
theme.color.pink7,
theme.color.pink8,
theme.color.pink9,
theme.color.pink10,
theme.color.pink11,
theme.color.pink12,
themeCssVariables.color.pink1,
themeCssVariables.color.pink2,
themeCssVariables.color.pink3,
themeCssVariables.color.pink4,
themeCssVariables.color.pink5,
themeCssVariables.color.pink6,
themeCssVariables.color.pink7,
themeCssVariables.color.pink8,
themeCssVariables.color.pink9,
themeCssVariables.color.pink10,
themeCssVariables.color.pink11,
themeCssVariables.color.pink12,
],
},
yellow: {
name: 'yellow',
solid: theme.color.yellow8,
solid: themeCssVariables.color.yellow8,
variations: [
theme.color.yellow1,
theme.color.yellow2,
theme.color.yellow3,
theme.color.yellow4,
theme.color.yellow5,
theme.color.yellow6,
theme.color.yellow7,
theme.color.yellow8,
theme.color.yellow9,
theme.color.yellow10,
theme.color.yellow11,
theme.color.yellow12,
themeCssVariables.color.yellow1,
themeCssVariables.color.yellow2,
themeCssVariables.color.yellow3,
themeCssVariables.color.yellow4,
themeCssVariables.color.yellow5,
themeCssVariables.color.yellow6,
themeCssVariables.color.yellow7,
themeCssVariables.color.yellow8,
themeCssVariables.color.yellow9,
themeCssVariables.color.yellow10,
themeCssVariables.color.yellow11,
themeCssVariables.color.yellow12,
],
},
red: {
name: 'red',
solid: theme.color.red8,
solid: themeCssVariables.color.red8,
variations: [
theme.color.red1,
theme.color.red2,
theme.color.red3,
theme.color.red4,
theme.color.red5,
theme.color.red6,
theme.color.red7,
theme.color.red8,
theme.color.red9,
theme.color.red10,
theme.color.red11,
theme.color.red12,
themeCssVariables.color.red1,
themeCssVariables.color.red2,
themeCssVariables.color.red3,
themeCssVariables.color.red4,
themeCssVariables.color.red5,
themeCssVariables.color.red6,
themeCssVariables.color.red7,
themeCssVariables.color.red8,
themeCssVariables.color.red9,
themeCssVariables.color.red10,
themeCssVariables.color.red11,
themeCssVariables.color.red12,
],
},
green: {
name: 'green',
solid: theme.color.green8,
solid: themeCssVariables.color.green8,
variations: [
theme.color.green1,
theme.color.green2,
theme.color.green3,
theme.color.green4,
theme.color.green5,
theme.color.green6,
theme.color.green7,
theme.color.green8,
theme.color.green9,
theme.color.green10,
theme.color.green11,
theme.color.green12,
themeCssVariables.color.green1,
themeCssVariables.color.green2,
themeCssVariables.color.green3,
themeCssVariables.color.green4,
themeCssVariables.color.green5,
themeCssVariables.color.green6,
themeCssVariables.color.green7,
themeCssVariables.color.green8,
themeCssVariables.color.green9,
themeCssVariables.color.green10,
themeCssVariables.color.green11,
themeCssVariables.color.green12,
],
},
sky: {
name: 'sky',
solid: theme.color.sky8,
solid: themeCssVariables.color.sky8,
variations: [
theme.color.sky1,
theme.color.sky2,
theme.color.sky3,
theme.color.sky4,
theme.color.sky5,
theme.color.sky6,
theme.color.sky7,
theme.color.sky8,
theme.color.sky9,
theme.color.sky10,
theme.color.sky11,
theme.color.sky12,
themeCssVariables.color.sky1,
themeCssVariables.color.sky2,
themeCssVariables.color.sky3,
themeCssVariables.color.sky4,
themeCssVariables.color.sky5,
themeCssVariables.color.sky6,
themeCssVariables.color.sky7,
themeCssVariables.color.sky8,
themeCssVariables.color.sky9,
themeCssVariables.color.sky10,
themeCssVariables.color.sky11,
themeCssVariables.color.sky12,
],
},
gray: {
name: 'gray',
solid: theme.color.gray8,
solid: themeCssVariables.color.gray8,
variations: [
theme.color.gray1,
theme.color.gray2,
theme.color.gray3,
theme.color.gray4,
theme.color.gray5,
theme.color.gray6,
theme.color.gray7,
theme.color.gray8,
theme.color.gray9,
theme.color.gray10,
theme.color.gray11,
theme.color.gray12,
themeCssVariables.color.gray1,
themeCssVariables.color.gray2,
themeCssVariables.color.gray3,
themeCssVariables.color.gray4,
themeCssVariables.color.gray5,
themeCssVariables.color.gray6,
themeCssVariables.color.gray7,
themeCssVariables.color.gray8,
themeCssVariables.color.gray9,
themeCssVariables.color.gray10,
themeCssVariables.color.gray11,
themeCssVariables.color.gray12,
],
},
tomato: {
name: 'tomato',
solid: theme.color.tomato8,
solid: themeCssVariables.color.tomato8,
variations: [
theme.color.tomato1,
theme.color.tomato2,
theme.color.tomato3,
theme.color.tomato4,
theme.color.tomato5,
theme.color.tomato6,
theme.color.tomato7,
theme.color.tomato8,
theme.color.tomato9,
theme.color.tomato10,
theme.color.tomato11,
theme.color.tomato12,
themeCssVariables.color.tomato1,
themeCssVariables.color.tomato2,
themeCssVariables.color.tomato3,
themeCssVariables.color.tomato4,
themeCssVariables.color.tomato5,
themeCssVariables.color.tomato6,
themeCssVariables.color.tomato7,
themeCssVariables.color.tomato8,
themeCssVariables.color.tomato9,
themeCssVariables.color.tomato10,
themeCssVariables.color.tomato11,
themeCssVariables.color.tomato12,
],
},
ruby: {
name: 'ruby',
solid: theme.color.ruby8,
solid: themeCssVariables.color.ruby8,
variations: [
theme.color.ruby1,
theme.color.ruby2,
theme.color.ruby3,
theme.color.ruby4,
theme.color.ruby5,
theme.color.ruby6,
theme.color.ruby7,
theme.color.ruby8,
theme.color.ruby9,
theme.color.ruby10,
theme.color.ruby11,
theme.color.ruby12,
themeCssVariables.color.ruby1,
themeCssVariables.color.ruby2,
themeCssVariables.color.ruby3,
themeCssVariables.color.ruby4,
themeCssVariables.color.ruby5,
themeCssVariables.color.ruby6,
themeCssVariables.color.ruby7,
themeCssVariables.color.ruby8,
themeCssVariables.color.ruby9,
themeCssVariables.color.ruby10,
themeCssVariables.color.ruby11,
themeCssVariables.color.ruby12,
],
},
crimson: {
name: 'crimson',
solid: theme.color.crimson8,
solid: themeCssVariables.color.crimson8,
variations: [
theme.color.crimson1,
theme.color.crimson2,
theme.color.crimson3,
theme.color.crimson4,
theme.color.crimson5,
theme.color.crimson6,
theme.color.crimson7,
theme.color.crimson8,
theme.color.crimson9,
theme.color.crimson10,
theme.color.crimson11,
theme.color.crimson12,
themeCssVariables.color.crimson1,
themeCssVariables.color.crimson2,
themeCssVariables.color.crimson3,
themeCssVariables.color.crimson4,
themeCssVariables.color.crimson5,
themeCssVariables.color.crimson6,
themeCssVariables.color.crimson7,
themeCssVariables.color.crimson8,
themeCssVariables.color.crimson9,
themeCssVariables.color.crimson10,
themeCssVariables.color.crimson11,
themeCssVariables.color.crimson12,
],
},
plum: {
name: 'plum',
solid: theme.color.plum8,
solid: themeCssVariables.color.plum8,
variations: [
theme.color.plum1,
theme.color.plum2,
theme.color.plum3,
theme.color.plum4,
theme.color.plum5,
theme.color.plum6,
theme.color.plum7,
theme.color.plum8,
theme.color.plum9,
theme.color.plum10,
theme.color.plum11,
theme.color.plum12,
themeCssVariables.color.plum1,
themeCssVariables.color.plum2,
themeCssVariables.color.plum3,
themeCssVariables.color.plum4,
themeCssVariables.color.plum5,
themeCssVariables.color.plum6,
themeCssVariables.color.plum7,
themeCssVariables.color.plum8,
themeCssVariables.color.plum9,
themeCssVariables.color.plum10,
themeCssVariables.color.plum11,
themeCssVariables.color.plum12,
],
},
violet: {
name: 'violet',
solid: theme.color.violet8,
solid: themeCssVariables.color.violet8,
variations: [
theme.color.violet1,
theme.color.violet2,
theme.color.violet3,
theme.color.violet4,
theme.color.violet5,
theme.color.violet6,
theme.color.violet7,
theme.color.violet8,
theme.color.violet9,
theme.color.violet10,
theme.color.violet11,
theme.color.violet12,
themeCssVariables.color.violet1,
themeCssVariables.color.violet2,
themeCssVariables.color.violet3,
themeCssVariables.color.violet4,
themeCssVariables.color.violet5,
themeCssVariables.color.violet6,
themeCssVariables.color.violet7,
themeCssVariables.color.violet8,
themeCssVariables.color.violet9,
themeCssVariables.color.violet10,
themeCssVariables.color.violet11,
themeCssVariables.color.violet12,
],
},
iris: {
name: 'iris',
solid: theme.color.iris8,
solid: themeCssVariables.color.iris8,
variations: [
theme.color.iris1,
theme.color.iris2,
theme.color.iris3,
theme.color.iris4,
theme.color.iris5,
theme.color.iris6,
theme.color.iris7,
theme.color.iris8,
theme.color.iris9,
theme.color.iris10,
theme.color.iris11,
theme.color.iris12,
themeCssVariables.color.iris1,
themeCssVariables.color.iris2,
themeCssVariables.color.iris3,
themeCssVariables.color.iris4,
themeCssVariables.color.iris5,
themeCssVariables.color.iris6,
themeCssVariables.color.iris7,
themeCssVariables.color.iris8,
themeCssVariables.color.iris9,
themeCssVariables.color.iris10,
themeCssVariables.color.iris11,
themeCssVariables.color.iris12,
],
},
cyan: {
name: 'cyan',
solid: theme.color.cyan8,
solid: themeCssVariables.color.cyan8,
variations: [
theme.color.cyan1,
theme.color.cyan2,
theme.color.cyan3,
theme.color.cyan4,
theme.color.cyan5,
theme.color.cyan6,
theme.color.cyan7,
theme.color.cyan8,
theme.color.cyan9,
theme.color.cyan10,
theme.color.cyan11,
theme.color.cyan12,
themeCssVariables.color.cyan1,
themeCssVariables.color.cyan2,
themeCssVariables.color.cyan3,
themeCssVariables.color.cyan4,
themeCssVariables.color.cyan5,
themeCssVariables.color.cyan6,
themeCssVariables.color.cyan7,
themeCssVariables.color.cyan8,
themeCssVariables.color.cyan9,
themeCssVariables.color.cyan10,
themeCssVariables.color.cyan11,
themeCssVariables.color.cyan12,
],
},
jade: {
name: 'jade',
solid: theme.color.jade8,
solid: themeCssVariables.color.jade8,
variations: [
theme.color.jade1,
theme.color.jade2,
theme.color.jade3,
theme.color.jade4,
theme.color.jade5,
theme.color.jade6,
theme.color.jade7,
theme.color.jade8,
theme.color.jade9,
theme.color.jade10,
theme.color.jade11,
theme.color.jade12,
themeCssVariables.color.jade1,
themeCssVariables.color.jade2,
themeCssVariables.color.jade3,
themeCssVariables.color.jade4,
themeCssVariables.color.jade5,
themeCssVariables.color.jade6,
themeCssVariables.color.jade7,
themeCssVariables.color.jade8,
themeCssVariables.color.jade9,
themeCssVariables.color.jade10,
themeCssVariables.color.jade11,
themeCssVariables.color.jade12,
],
},
grass: {
name: 'grass',
solid: theme.color.grass8,
solid: themeCssVariables.color.grass8,
variations: [
theme.color.grass1,
theme.color.grass2,
theme.color.grass3,
theme.color.grass4,
theme.color.grass5,
theme.color.grass6,
theme.color.grass7,
theme.color.grass8,
theme.color.grass9,
theme.color.grass10,
theme.color.grass11,
theme.color.grass12,
themeCssVariables.color.grass1,
themeCssVariables.color.grass2,
themeCssVariables.color.grass3,
themeCssVariables.color.grass4,
themeCssVariables.color.grass5,
themeCssVariables.color.grass6,
themeCssVariables.color.grass7,
themeCssVariables.color.grass8,
themeCssVariables.color.grass9,
themeCssVariables.color.grass10,
themeCssVariables.color.grass11,
themeCssVariables.color.grass12,
],
},
mint: {
name: 'mint',
solid: theme.color.mint8,
solid: themeCssVariables.color.mint8,
variations: [
theme.color.mint1,
theme.color.mint2,
theme.color.mint3,
theme.color.mint4,
theme.color.mint5,
theme.color.mint6,
theme.color.mint7,
theme.color.mint8,
theme.color.mint9,
theme.color.mint10,
theme.color.mint11,
theme.color.mint12,
themeCssVariables.color.mint1,
themeCssVariables.color.mint2,
themeCssVariables.color.mint3,
themeCssVariables.color.mint4,
themeCssVariables.color.mint5,
themeCssVariables.color.mint6,
themeCssVariables.color.mint7,
themeCssVariables.color.mint8,
themeCssVariables.color.mint9,
themeCssVariables.color.mint10,
themeCssVariables.color.mint11,
themeCssVariables.color.mint12,
],
},
lime: {
name: 'lime',
solid: theme.color.lime8,
solid: themeCssVariables.color.lime8,
variations: [
theme.color.lime1,
theme.color.lime2,
theme.color.lime3,
theme.color.lime4,
theme.color.lime5,
theme.color.lime6,
theme.color.lime7,
theme.color.lime8,
theme.color.lime9,
theme.color.lime10,
theme.color.lime11,
theme.color.lime12,
themeCssVariables.color.lime1,
themeCssVariables.color.lime2,
themeCssVariables.color.lime3,
themeCssVariables.color.lime4,
themeCssVariables.color.lime5,
themeCssVariables.color.lime6,
themeCssVariables.color.lime7,
themeCssVariables.color.lime8,
themeCssVariables.color.lime9,
themeCssVariables.color.lime10,
themeCssVariables.color.lime11,
themeCssVariables.color.lime12,
],
},
bronze: {
name: 'bronze',
solid: theme.color.bronze8,
solid: themeCssVariables.color.bronze8,
variations: [
theme.color.bronze1,
theme.color.bronze2,
theme.color.bronze3,
theme.color.bronze4,
theme.color.bronze5,
theme.color.bronze6,
theme.color.bronze7,
theme.color.bronze8,
theme.color.bronze9,
theme.color.bronze10,
theme.color.bronze11,
theme.color.bronze12,
themeCssVariables.color.bronze1,
themeCssVariables.color.bronze2,
themeCssVariables.color.bronze3,
themeCssVariables.color.bronze4,
themeCssVariables.color.bronze5,
themeCssVariables.color.bronze6,
themeCssVariables.color.bronze7,
themeCssVariables.color.bronze8,
themeCssVariables.color.bronze9,
themeCssVariables.color.bronze10,
themeCssVariables.color.bronze11,
themeCssVariables.color.bronze12,
],
},
gold: {
name: 'gold',
solid: theme.color.gold8,
solid: themeCssVariables.color.gold8,
variations: [
theme.color.gold1,
theme.color.gold2,
theme.color.gold3,
theme.color.gold4,
theme.color.gold5,
theme.color.gold6,
theme.color.gold7,
theme.color.gold8,
theme.color.gold9,
theme.color.gold10,
theme.color.gold11,
theme.color.gold12,
themeCssVariables.color.gold1,
themeCssVariables.color.gold2,
themeCssVariables.color.gold3,
themeCssVariables.color.gold4,
themeCssVariables.color.gold5,
themeCssVariables.color.gold6,
themeCssVariables.color.gold7,
themeCssVariables.color.gold8,
themeCssVariables.color.gold9,
themeCssVariables.color.gold10,
themeCssVariables.color.gold11,
themeCssVariables.color.gold12,
],
},
brown: {
name: 'brown',
solid: theme.color.brown8,
solid: themeCssVariables.color.brown8,
variations: [
theme.color.brown1,
theme.color.brown2,
theme.color.brown3,
theme.color.brown4,
theme.color.brown5,
theme.color.brown6,
theme.color.brown7,
theme.color.brown8,
theme.color.brown9,
theme.color.brown10,
theme.color.brown11,
theme.color.brown12,
themeCssVariables.color.brown1,
themeCssVariables.color.brown2,
themeCssVariables.color.brown3,
themeCssVariables.color.brown4,
themeCssVariables.color.brown5,
themeCssVariables.color.brown6,
themeCssVariables.color.brown7,
themeCssVariables.color.brown8,
themeCssVariables.color.brown9,
themeCssVariables.color.brown10,
themeCssVariables.color.brown11,
themeCssVariables.color.brown12,
],
},
amber: {
name: 'amber',
solid: theme.color.amber8,
solid: themeCssVariables.color.amber8,
variations: [
theme.color.amber1,
theme.color.amber2,
theme.color.amber3,
theme.color.amber4,
theme.color.amber5,
theme.color.amber6,
theme.color.amber7,
theme.color.amber8,
theme.color.amber9,
theme.color.amber10,
theme.color.amber11,
theme.color.amber12,
themeCssVariables.color.amber1,
themeCssVariables.color.amber2,
themeCssVariables.color.amber3,
themeCssVariables.color.amber4,
themeCssVariables.color.amber5,
themeCssVariables.color.amber6,
themeCssVariables.color.amber7,
themeCssVariables.color.amber8,
themeCssVariables.color.amber9,
themeCssVariables.color.amber10,
themeCssVariables.color.amber11,
themeCssVariables.color.amber12,
],
},
});
@@ -1,5 +1,5 @@
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import type { ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type WorkflowDiagramColors = {
background: string;
@@ -15,10 +15,8 @@ export type WorkflowDiagramNodeColors = {
};
export const getWorkflowDiagramColors = ({
theme,
runStatus,
}: {
theme: ThemeType;
runStatus?: WorkflowRunStepStatus;
}): WorkflowDiagramNodeColors => {
switch (runStatus) {
@@ -26,18 +24,18 @@ export const getWorkflowDiagramColors = ({
case 'RUNNING': {
return {
selected: {
background: theme.color.yellow2,
borderColor: theme.color.yellow,
color: theme.tag.text.yellow,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.yellow,
background: themeCssVariables.color.yellow2,
borderColor: themeCssVariables.color.yellow,
color: themeCssVariables.tag.text.yellow,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.yellow,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.yellow,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.yellow,
background: themeCssVariables.background.secondary,
borderColor: themeCssVariables.border.color.strong,
color: themeCssVariables.tag.text.yellow,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.yellow,
},
};
}
@@ -45,18 +43,18 @@ export const getWorkflowDiagramColors = ({
case 'FAILED_SAFELY': {
return {
selected: {
background: theme.color.red2,
borderColor: theme.color.red,
color: theme.tag.text.red,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.red,
background: themeCssVariables.color.red2,
borderColor: themeCssVariables.color.red,
color: themeCssVariables.tag.text.red,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.red,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.red,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.red,
background: themeCssVariables.background.secondary,
borderColor: themeCssVariables.border.color.strong,
color: themeCssVariables.tag.text.red,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.red,
},
};
}
@@ -64,36 +62,36 @@ export const getWorkflowDiagramColors = ({
case 'SUCCESS': {
return {
selected: {
background: theme.color.turquoise2,
borderColor: theme.color.turquoise,
color: theme.tag.text.green,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.turquoise,
background: themeCssVariables.color.turquoise2,
borderColor: themeCssVariables.color.turquoise,
color: themeCssVariables.tag.text.green,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.turquoise,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.tag.text.green,
titleColor: theme.font.color.primary,
tagBackground: theme.tag.background.turquoise,
background: themeCssVariables.background.secondary,
borderColor: themeCssVariables.border.color.strong,
color: themeCssVariables.tag.text.green,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.tag.background.turquoise,
},
};
}
default: {
return {
selected: {
background: theme.color.blue2,
borderColor: theme.color.blue,
color: theme.tag.text.blue,
titleColor: theme.font.color.primary,
tagBackground: theme.border.color.strong,
background: themeCssVariables.color.blue2,
borderColor: themeCssVariables.color.blue,
color: themeCssVariables.tag.text.blue,
titleColor: themeCssVariables.font.color.primary,
tagBackground: themeCssVariables.border.color.strong,
},
unselected: {
background: theme.background.secondary,
borderColor: theme.border.color.strong,
color: theme.font.color.tertiary,
titleColor: theme.font.color.light,
tagBackground: theme.border.color.strong,
background: themeCssVariables.background.secondary,
borderColor: themeCssVariables.border.color.strong,
color: themeCssVariables.font.color.tertiary,
titleColor: themeCssVariables.font.color.light,
tagBackground: themeCssVariables.border.color.strong,
},
};
}
@@ -17,11 +17,9 @@ import { useLingui } from '@lingui/react/macro';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme';
export const WorkflowDiagramEmptyTriggerEditable = ({ id }: { id: string }) => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const { openWorkflowTriggerTypeInCommandMenu } = useWorkflowCommandMenu();
@@ -62,7 +60,6 @@ export const WorkflowDiagramEmptyTriggerEditable = ({ id }: { id: string }) => {
return (
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
theme={theme}
onClick={handleClick}
selected={selected}
>
@@ -75,7 +72,7 @@ export const WorkflowDiagramEmptyTriggerEditable = ({ id }: { id: string }) => {
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle theme={theme} selected={selected}>
<WorkflowNodeTitle selected={selected}>
{t`Add a Trigger`}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
@@ -19,12 +19,10 @@ import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
export const WorkflowDiagramEmptyTriggerReadonly = ({ id }: { id: string }) => {
const { getIcon } = useIcons();
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const workflowVisualizerWorkflowId = useAtomComponentStateValue(
workflowVisualizerWorkflowIdComponentState,
@@ -75,7 +73,6 @@ export const WorkflowDiagramEmptyTriggerReadonly = ({ id }: { id: string }) => {
return (
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
theme={theme}
onClick={handleClick}
selected={selected}
>
@@ -88,7 +85,7 @@ export const WorkflowDiagramEmptyTriggerReadonly = ({ id }: { id: string }) => {
</WorkflowNodeLabel>
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle theme={theme} selected={selected}>
<WorkflowNodeTitle selected={selected}>
{t`Add a Trigger`}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
@@ -4,8 +4,7 @@ import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/Node
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { styled } from '@linaria/react';
import { Handle, Position, type HandleProps } from '@xyflow/react';
import { useContext, useMemo, type CSSProperties } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
import { useMemo, type CSSProperties } from 'react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const HANDLE_SCALE_ON_HOVER = 1.5;
@@ -70,8 +69,6 @@ export const WorkflowDiagramHandleSource = ({
disableHoverEffect,
runStatus,
}: WorkflowDiagramHandleSourceProps) => {
const { theme } = useContext(ThemeContext);
const dynamicStyles = useMemo(() => {
const isRight = position === Position.Right;
const transform = isRight ? 'translate(50%, -50%)' : 'translate(-50%, 50%)';
@@ -82,15 +79,15 @@ export const WorkflowDiagramHandleSource = ({
let borderColor: string;
if (selected) {
const colors = getWorkflowDiagramColors({ theme, runStatus });
const colors = getWorkflowDiagramColors({ runStatus });
bg = colors.selected.background;
borderColor = colors.selected.borderColor;
} else {
bg = theme.background.primary;
bg = themeCssVariables.background.primary;
borderColor =
hovered && disableHoverEffect !== true
? theme.font.color.light
: theme.border.color.strong;
? themeCssVariables.font.color.light
: themeCssVariables.border.color.strong;
}
const styles: Record<string, string> = {
@@ -102,7 +99,7 @@ export const WorkflowDiagramHandleSource = ({
};
if (disableHoverEffect !== true) {
const hoverColors = getWorkflowDiagramColors({ theme });
const hoverColors = getWorkflowDiagramColors({});
styles['--handle-hover-bg'] = hoverColors.selected.background;
styles['--handle-hover-border-color'] = hoverColors.selected.borderColor;
styles['--handle-hover-transform'] =
@@ -110,7 +107,7 @@ export const WorkflowDiagramHandleSource = ({
}
return styles as CSSProperties;
}, [position, selected, hovered, disableHoverEffect, runStatus, type, theme]);
}, [position, selected, hovered, disableHoverEffect, runStatus, type]);
return (
<StyledHandle
@@ -22,9 +22,8 @@ import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/s
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { Position } from '@xyflow/react';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme';
const StyledAddStepButtonContainer = styled.div<{
shouldDisplay: boolean;
@@ -52,7 +51,6 @@ export const WorkflowDiagramStepNodeEditableContent = ({
onClick?: () => void;
}) => {
const { i18n } = useLingui();
const { theme } = useContext(ThemeContext);
const [isHovered, setIsHovered] = useState(false);
@@ -97,7 +95,6 @@ export const WorkflowDiagramStepNodeEditableContent = ({
<>
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
theme={theme}
onClick={onClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
@@ -118,7 +115,6 @@ export const WorkflowDiagramStepNodeEditableContent = ({
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle
theme={theme}
highlight={nodeTitleHighlighted}
selected={selected}
>
@@ -25,7 +25,6 @@ import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomStat
import { useContext } from 'react';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
export const WorkflowDiagramStepNodeReadonly = ({
id,
@@ -35,7 +34,6 @@ export const WorkflowDiagramStepNodeReadonly = ({
data: WorkflowDiagramStepNodeData;
}) => {
const { getIcon } = useIcons();
const { theme } = useContext(ThemeContext);
const workflowVisualizerWorkflowId = useAtomComponentStateValue(
workflowVisualizerWorkflowIdComponentState,
@@ -90,7 +88,6 @@ export const WorkflowDiagramStepNodeReadonly = ({
<>
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
theme={theme}
onClick={handleClick}
selected={selected}
>
@@ -107,7 +104,6 @@ export const WorkflowDiagramStepNodeReadonly = ({
</WorkflowNodeLabelWithCounterPart>
<WorkflowNodeTitle
theme={theme}
highlight={nodeTitleHighlighted}
selected={selected}
>
@@ -1,11 +1,9 @@
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { styled } from '@linaria/react';
import type { ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNodeContainer = styled.div<{
theme: ThemeType;
runStatus?: WorkflowRunStepStatus;
isConnectable?: boolean;
selected: boolean;
@@ -24,30 +22,30 @@ const StyledNodeContainer = styled.div<{
position: relative;
transition: border-color 0.1s;
background: ${({ theme, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
background: ${({ runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ runStatus });
return selected ? colors.selected.background : colors.unselected.background;
}};
border-color: ${({ theme, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
border-color: ${({ runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ runStatus });
return selected
? colors.selected.borderColor
: colors.unselected.borderColor;
}};
&:hover {
background: ${({ theme, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
background: ${({ runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ runStatus });
const bg = selected
? colors.selected.background
: colors.unselected.background;
return `linear-gradient(0deg, ${themeCssVariables.background.transparent.lighter} 0%, ${themeCssVariables.background.transparent.lighter} 100%), ${bg}`;
}};
border-color: ${({ theme, runStatus, selected, isConnectable }) => {
border-color: ${({ runStatus, selected, isConnectable }) => {
if (isConnectable === true) return themeCssVariables.color.blue;
const colors = getWorkflowDiagramColors({ theme, runStatus });
const colors = getWorkflowDiagramColors({ runStatus });
return selected
? colors.selected.borderColor
: colors.unselected.borderColor;
@@ -1,10 +1,7 @@
import { useContext } from 'react';
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { styled } from '@linaria/react';
import { Label } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme';
type WorkflowNodeLabelProps = {
runStatus?: WorkflowRunStepStatus;
@@ -28,8 +25,7 @@ export const WorkflowNodeLabel = ({
children,
className,
}: WorkflowNodeLabelProps) => {
const { theme } = useContext(ThemeContext);
const colors = getWorkflowDiagramColors({ theme, runStatus });
const colors = getWorkflowDiagramColors({ runStatus });
const labelColor = selected ? colors.selected.color : colors.unselected.color;
return (
@@ -1,10 +1,9 @@
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { styled } from '@linaria/react';
import type { ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledNodeTitle = styled.div<{
theme: ThemeType;
highlight?: boolean;
runStatus?: WorkflowRunStepStatus;
selected: boolean;
@@ -13,8 +12,8 @@ const StyledNodeTitle = styled.div<{
-webkit-line-clamp: 1;
align-self: stretch;
box-sizing: border-box;
color: ${({ theme, highlight, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ theme, runStatus });
color: ${({ highlight, runStatus, selected }) => {
const colors = getWorkflowDiagramColors({ runStatus });
if (highlight === true) return colors.selected.titleColor;
return selected ? colors.selected.titleColor : colors.unselected.titleColor;
}};
@@ -32,7 +32,6 @@ import { StepStatus } from 'twenty-shared/workflow';
import { IconCheck, IconX, useIcons } from 'twenty-ui/display';
import { Loader } from 'twenty-ui/feedback';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ThemeContext, type ThemeType } from 'twenty-ui/theme';
const StyledNodeLabelWithCounterPart = styled(WorkflowNodeLabelWithCounterPart)`
column-gap: ${themeCssVariables.spacing[2]};
@@ -60,11 +59,10 @@ const StyledColorIcon = styled.div<{
`;
const StyledIterationCounter = styled.div<{
theme: ThemeType;
runStatus?: WorkflowRunStepStatus;
}>`
color: ${({ theme, runStatus }) =>
getWorkflowDiagramColors({ theme, runStatus }).unselected.color};
color: ${({ runStatus }) =>
getWorkflowDiagramColors({ runStatus }).unselected.color};
font-size: ${themeCssVariables.font.size.sm};
font-weight: ${themeCssVariables.font.weight.medium};
`;
@@ -82,7 +80,6 @@ export const WorkflowRunDiagramStepNode = ({
data: WorkflowRunDiagramStepNodeData;
}) => {
const { getIcon } = useIcons();
const { theme } = useContext(ThemeContext);
const workflowVisualizerWorkflowId = useAtomComponentStateValue(
workflowVisualizerWorkflowIdComponentState,
@@ -137,7 +134,6 @@ export const WorkflowRunDiagramStepNode = ({
<>
<WorkflowNodeContainer
data-click-outside-id={WORKFLOW_DIAGRAM_STEP_NODE_BASE_CLICK_OUTSIDE_ID}
theme={theme}
runStatus={data.runStatus}
onClick={handleClick}
selected={selected}
@@ -155,10 +151,7 @@ export const WorkflowRunDiagramStepNode = ({
<StyledRightPartContainer>
{iterationCount > 0 && (
<StyledIterationCounter
theme={theme}
runStatus={data.runStatus}
>
<StyledIterationCounter runStatus={data.runStatus}>
{iterationCount}
</StyledIterationCounter>
)}
@@ -166,8 +159,13 @@ export const WorkflowRunDiagramStepNode = ({
{(data.runStatus === StepStatus.SUCCESS ||
data.runStatus === StepStatus.STOPPED) && (
<StyledStatusIconsContainer>
<StyledColorIcon color={theme.tag.background.turquoise}>
<IconCheck color={theme.tag.text.turquoise} size={14} />
<StyledColorIcon
color={themeCssVariables.tag.background.turquoise}
>
<IconCheck
color={themeCssVariables.tag.text.turquoise}
size={14}
/>
</StyledColorIcon>
</StyledStatusIconsContainer>
)}
@@ -175,8 +173,8 @@ export const WorkflowRunDiagramStepNode = ({
{(data.runStatus === StepStatus.FAILED ||
data.runStatus === StepStatus.FAILED_SAFELY) && (
<StyledStatusIconsContainer>
<StyledColorIcon color={theme.tag.background.red}>
<IconX color={theme.tag.text.red} size={14} />
<StyledColorIcon color={themeCssVariables.tag.background.red}>
<IconX color={themeCssVariables.tag.text.red} size={14} />
</StyledColorIcon>
</StyledStatusIconsContainer>
)}
@@ -190,11 +188,7 @@ export const WorkflowRunDiagramStepNode = ({
</StyledRightPartContainer>
</StyledNodeLabelWithCounterPart>
<WorkflowNodeTitle
theme={theme}
runStatus={data.runStatus}
selected={selected}
>
<WorkflowNodeTitle runStatus={data.runStatus} selected={selected}>
{data.name}
</WorkflowNodeTitle>
</WorkflowNodeRightPart>
@@ -1,10 +1,6 @@
import { type WorkflowActionType } from '@/workflow/types/Workflow';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme';
export const useActionIconColorOrThrow = (actionType: WorkflowActionType) => {
const { theme } = useContext(ThemeContext);
return getActionIconColorOrThrow({ theme, actionType });
};
export const useActionIconColorOrThrow = (
actionType: WorkflowActionType,
): string => getActionIconColorOrThrow(actionType);
@@ -1,309 +1,68 @@
import { type WorkflowActionType } from '@/workflow/types/Workflow';
import { COLOR_LIGHT, GRAY_SCALE_LIGHT, type ThemeType } from 'twenty-ui/theme';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
const mockTheme: ThemeType = {
color: {
orange: COLOR_LIGHT.orange,
pink: COLOR_LIGHT.pink,
red: COLOR_LIGHT.red,
},
font: {
color: {
tertiary: GRAY_SCALE_LIGHT.gray9,
},
},
} as ThemeType;
import { themeCssVariables } from 'twenty-ui/theme-constants';
describe('getActionIconColorOrThrow', () => {
describe('action types that return red color', () => {
const coreActionTypes: WorkflowActionType[] = [
it('returns red for CODE, HTTP_REQUEST, SEND_EMAIL, DRAFT_EMAIL, LOGIC_FUNCTION', () => {
const redActions: WorkflowActionType[] = [
'CODE',
'HTTP_REQUEST',
'SEND_EMAIL',
'DRAFT_EMAIL',
'LOGIC_FUNCTION',
];
coreActionTypes.forEach((actionType) => {
it(`should return red color for ${actionType} action type`, () => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
expect(result).toBe(mockTheme.color.red);
});
redActions.forEach((actionType) => {
expect(getActionIconColorOrThrow(actionType)).toBe(
themeCssVariables.color.red,
);
});
});
describe('action types that return tertiary font color', () => {
const recordActionTypes: WorkflowActionType[] = [
it('returns tertiary font color for record actions', () => {
const recordActions: WorkflowActionType[] = [
'CREATE_RECORD',
'UPDATE_RECORD',
'DELETE_RECORD',
'UPSERT_RECORD',
'FIND_RECORDS',
];
recordActionTypes.forEach((actionType) => {
it(`should return tertiary font color for ${actionType} action type`, () => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
expect(result).toBe(mockTheme.font.color.tertiary);
});
recordActions.forEach((actionType) => {
expect(getActionIconColorOrThrow(actionType)).toBe(
themeCssVariables.font.color.tertiary,
);
});
});
describe('action types that return orange color', () => {
it('should return orange color for FORM action type', () => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'FORM',
});
it('returns orange for FORM', () => {
expect(getActionIconColorOrThrow('FORM')).toBe(
themeCssVariables.color.orange,
);
});
expect(result).toBe(mockTheme.color.orange);
it('returns green12 for ITERATOR, EMPTY, FILTER, IF_ELSE, DELAY', () => {
const greenActions: WorkflowActionType[] = [
'ITERATOR',
'EMPTY',
'FILTER',
'IF_ELSE',
'DELAY',
];
greenActions.forEach((actionType) => {
expect(getActionIconColorOrThrow(actionType)).toBe(
themeCssVariables.color.green12,
);
});
});
describe('action types that return pink color', () => {
it('should return pink color for AI_AGENT action type', () => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'AI_AGENT',
});
expect(result).toBe(mockTheme.color.pink);
});
it('returns pink for AI_AGENT', () => {
expect(getActionIconColorOrThrow('AI_AGENT')).toBe(
themeCssVariables.color.pink,
);
});
describe('FILTER action type', () => {
it('should return green color for FILTER action type', () => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'FILTER',
});
expect(result).toBe(mockTheme.color.green12);
});
});
describe('theme object handling', () => {
it('should use the provided theme colors correctly', () => {
const customTheme: ThemeType = {
color: {
red: COLOR_LIGHT.red,
orange: COLOR_LIGHT.orange,
pink: COLOR_LIGHT.turquoise,
},
font: {
color: {
tertiary: GRAY_SCALE_LIGHT.gray11,
},
},
} as ThemeType;
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'CODE',
}),
).toBe(COLOR_LIGHT.red);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'SEND_EMAIL',
}),
).toBe(COLOR_LIGHT.red);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'AI_AGENT',
}),
).toBe(COLOR_LIGHT.turquoise);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'CREATE_RECORD',
}),
).toBe(GRAY_SCALE_LIGHT.gray11);
});
});
describe('type safety and exhaustive checking', () => {
it('should handle all valid action types without throwing unreachable errors', () => {
const validActionTypes: WorkflowActionType[] = [
'CODE',
'HTTP_REQUEST',
'CREATE_RECORD',
'UPDATE_RECORD',
'DELETE_RECORD',
'FIND_RECORDS',
'FORM',
'SEND_EMAIL',
'AI_AGENT',
];
validActionTypes.forEach((actionType) => {
expect(() => {
getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
}).not.toThrow();
});
});
it('should return consistent color values for the same action type', () => {
const actionType: WorkflowActionType = 'CODE';
const result1 = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
const result2 = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
expect(result1).toBe(result2);
expect(result1).toBe(mockTheme.color.red);
});
});
describe('color grouping logic', () => {
it('should group CODE and HTTP_REQUEST actions with red color', () => {
const orangeActions: WorkflowActionType[] = ['CODE', 'HTTP_REQUEST'];
orangeActions.forEach((actionType) => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
expect(result).toBe(mockTheme.color.red);
});
});
it('should group record-related actions with tertiary font color', () => {
const recordActions: WorkflowActionType[] = [
'CREATE_RECORD',
'UPDATE_RECORD',
'DELETE_RECORD',
'FIND_RECORDS',
];
recordActions.forEach((actionType) => {
const result = getActionIconColorOrThrow({
theme: mockTheme,
actionType,
});
expect(result).toBe(mockTheme.font.color.tertiary);
});
});
it('should have unique colors for different action categories', () => {
const tertiaryResult = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'CREATE_RECORD',
});
expect(tertiaryResult).toBe(mockTheme.font.color.tertiary);
});
it('should return red color for SEND_EMAIL action type', () => {
expect(
getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'SEND_EMAIL',
}),
).toBe(mockTheme.color.red);
});
it('should return pink color for AI_AGENT action type', () => {
expect(
getActionIconColorOrThrow({ theme: mockTheme, actionType: 'AI_AGENT' }),
).toBe(mockTheme.color.pink);
});
it('should use the provided theme colors correctly', () => {
const customTheme: ThemeType = {
color: {
red: COLOR_LIGHT.red,
orange: COLOR_LIGHT.orange,
pink: COLOR_LIGHT.turquoise,
},
font: {
color: {
tertiary: GRAY_SCALE_LIGHT.gray11,
},
},
} as ThemeType;
expect(
getActionIconColorOrThrow({ theme: customTheme, actionType: 'CODE' }),
).toBe(COLOR_LIGHT.red);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'SEND_EMAIL',
}),
).toBe(COLOR_LIGHT.red);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'AI_AGENT',
}),
).toBe(COLOR_LIGHT.turquoise);
expect(
getActionIconColorOrThrow({
theme: customTheme,
actionType: 'CREATE_RECORD',
}),
).toBe(GRAY_SCALE_LIGHT.gray11);
});
it('should return undefined when red color is missing for SEND_EMAIL action', () => {
const themeWithoutBlue: ThemeType = {
color: {
orange: COLOR_LIGHT.orange,
pink: COLOR_LIGHT.pink,
},
font: {
color: {
tertiary: GRAY_SCALE_LIGHT.gray9,
},
},
} as ThemeType;
expect(
getActionIconColorOrThrow({
theme: themeWithoutBlue,
actionType: 'SEND_EMAIL',
}),
).toBeUndefined();
});
it('should handle null theme gracefully', () => {
expect(() => {
getActionIconColorOrThrow({
theme: null as unknown as ThemeType,
actionType: 'CODE',
});
}).toThrow();
});
it('should return the same color for the same action type', () => {
const result1 = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'CODE',
});
const result2 = getActionIconColorOrThrow({
theme: mockTheme,
actionType: 'CODE',
});
expect(result1).toBe(result2);
});
it('returns consistent values for repeated calls', () => {
expect(getActionIconColorOrThrow('CODE')).toBe(
getActionIconColorOrThrow('CODE'),
);
});
});
@@ -1,37 +1,33 @@
import { type WorkflowActionType } from '@/workflow/types/Workflow';
import { assertUnreachable } from 'twenty-shared/utils';
import { type ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const getActionIconColorOrThrow = ({
theme,
actionType,
}: {
theme: ThemeType;
actionType: WorkflowActionType;
}) => {
export const getActionIconColorOrThrow = (
actionType: WorkflowActionType,
): string => {
switch (actionType) {
case 'CODE':
case 'LOGIC_FUNCTION':
case 'HTTP_REQUEST':
case 'SEND_EMAIL':
case 'DRAFT_EMAIL':
return theme.color.red;
return themeCssVariables.color.red;
case 'CREATE_RECORD':
case 'UPDATE_RECORD':
case 'DELETE_RECORD':
case 'UPSERT_RECORD':
case 'FIND_RECORDS':
return theme.font.color.tertiary;
return themeCssVariables.font.color.tertiary;
case 'FORM':
return theme.color.orange;
return themeCssVariables.color.orange;
case 'ITERATOR':
case 'EMPTY':
case 'FILTER':
case 'IF_ELSE':
case 'DELAY':
return theme.color.green12;
return themeCssVariables.color.green12;
case 'AI_AGENT':
return theme.color.pink;
return themeCssVariables.color.pink;
default:
assertUnreachable(actionType, `Unsupported action type: ${actionType}`);
}
@@ -1,61 +1,22 @@
import { COLOR_LIGHT, type ThemeType } from 'twenty-ui/theme';
import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor';
import { themeCssVariables } from 'twenty-ui/theme-constants';
describe('getTriggerIconColor', () => {
const mockTheme: ThemeType = {
color: {
blue: COLOR_LIGHT.blue,
purple: COLOR_LIGHT.purple,
},
} as unknown as ThemeType;
it('returns the blue color for database event from theme', () => {
const result = getTriggerIconColor({
theme: mockTheme,
triggerType: 'DATABASE_EVENT',
});
expect(result).toBe(COLOR_LIGHT.blue);
it('returns the blue css variable for DATABASE_EVENT', () => {
expect(getTriggerIconColor('DATABASE_EVENT')).toBe(
themeCssVariables.color.blue,
);
});
it('returns the purple color for cron from theme', () => {
const result = getTriggerIconColor({
theme: mockTheme,
triggerType: 'CRON',
});
expect(result).toBe(COLOR_LIGHT.purple);
it('returns the purple css variable for CRON', () => {
expect(getTriggerIconColor('CRON')).toBe(themeCssVariables.color.purple);
});
it('works with different theme configurations', () => {
const differentTheme: ThemeType = {
color: {
blue: COLOR_LIGHT.blue,
purple: COLOR_LIGHT.purple,
},
} as unknown as ThemeType;
const result = getTriggerIconColor({
theme: differentTheme,
triggerType: 'DATABASE_EVENT',
});
expect(result).toBe(COLOR_LIGHT.blue);
it('returns the purple css variable for MANUAL', () => {
expect(getTriggerIconColor('MANUAL')).toBe(themeCssVariables.color.purple);
});
it('maintains reference to theme.color.blue', () => {
const customTheme: ThemeType = {
color: {
blue: COLOR_LIGHT.blue,
purple: COLOR_LIGHT.purple,
},
} as unknown as ThemeType;
const result = getTriggerIconColor({
theme: customTheme,
triggerType: 'DATABASE_EVENT',
});
expect(result).toBe(COLOR_LIGHT.blue);
it('returns the purple css variable for WEBHOOK', () => {
expect(getTriggerIconColor('WEBHOOK')).toBe(themeCssVariables.color.purple);
});
});
@@ -1,21 +1,17 @@
import { type WorkflowTriggerType } from '@/workflow/types/Workflow';
import { type ThemeType } from 'twenty-ui/theme';
import { themeCssVariables } from 'twenty-ui/theme-constants';
export const getTriggerIconColor = ({
theme,
triggerType,
}: {
theme: ThemeType;
triggerType: WorkflowTriggerType;
}) => {
export const getTriggerIconColor = (
triggerType: WorkflowTriggerType,
): string => {
switch (triggerType) {
case 'DATABASE_EVENT':
return theme.color.blue;
return themeCssVariables.color.blue;
case 'CRON':
case 'MANUAL':
case 'WEBHOOK':
return theme.color.purple;
return themeCssVariables.color.purple;
default:
return theme.color.purple;
return themeCssVariables.color.purple;
}
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -71,6 +71,8 @@
"require": "./dist/index.cjs"
},
"./style.css": "./dist/style.css",
"./theme-light.css": "./dist/theme-light.css",
"./theme-dark.css": "./dist/theme-dark.css",
"./accessibility": {
"types": "./dist/accessibility/index.d.ts",
"import": "./dist/accessibility.mjs",
@@ -215,6 +215,8 @@ const generateModulePackageExports = (moduleDirectories: string[]) => {
},
{
'./style.css': './dist/style.css',
'./theme-light.css': './dist/theme-light.css',
'./theme-dark.css': './dist/theme-dark.css',
},
);
};
@@ -1,135 +0,0 @@
// Generates static TypeScript files with pre-computed theme constants.
// These values are string literals with no runtime imports, so wyw-in-js
// can evaluate them in its restricted sandbox without triggering the
// twenty-ui dist bundle's dependency chain (safe-regex-test / get-intrinsic).
//
// Usage (from workspace root):
// npx tsx packages/twenty-ui/scripts/generateThemeConstants.ts
//
// Prerequisites: twenty-ui must be built first (npx nx build twenty-ui).
import { createRequire } from 'node:module';
import { writeFileSync, mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const scriptDir = dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const {
MOBILE_VIEWPORT,
THEME_LIGHT,
THEME_DARK,
ICON,
prepareThemeForRootCssVariableInjection,
} = require('../dist/theme.cjs');
const { themeCssVariables: existingThemeCssVariables } =
require('../dist/theme-constants.cjs');
const themeCssVariables =
existingThemeCssVariables ??
(() => {
const { buildThemeReferencingRootCssVariables } =
require('../dist/theme.cjs');
return buildThemeReferencingRootCssVariables({
themeNode: THEME_LIGHT,
prefix: 't',
});
})();
const HEADER = `\
// Auto-generated by scripts/generateThemeConstants.ts — do not edit manually.
// Regenerate: npx tsx packages/twenty-ui/scripts/generateThemeConstants.ts
`;
const serializeObject = (
obj: Record<string, unknown>,
indent = 2,
): string => {
const spaces = ' '.repeat(indent);
const entries = Object.entries(obj).map(([key, value]) => {
const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
? key
: JSON.stringify(key);
if (typeof value === 'object' && value !== null) {
return `${spaces}${safeKey}: ${serializeObject(value as Record<string, unknown>, indent + 2)},`;
}
return `${spaces}${safeKey}: ${JSON.stringify(value)},`;
});
return `{\n${entries.join('\n')}\n${' '.repeat(indent - 2)}}`;
};
const serializeTupleArray = (entries: [string, string][]): string => {
const lines = entries.map(
([name, value]) => ` [${JSON.stringify(name)}, ${JSON.stringify(value)}],`,
);
return `[\n${lines.join('\n')}\n]`;
};
const outputDir = resolve(scriptDir, '../src/theme-constants/generated');
mkdirSync(outputDir, { recursive: true });
// --- themeCssVariables.ts ---
writeFileSync(
resolve(outputDir, 'themeCssVariables.ts'),
`${HEADER}
import type { ThemeType } from '@ui/theme/types/ThemeType';
type DeepCSSVariableRefs<T> = {
[K in keyof T]: T[K] extends (...args: never[]) => unknown
? Record<string | number, string>
: T[K] extends Record<string, unknown>
? DeepCSSVariableRefs<T[K]>
: string;
};
export const themeCssVariables = ${serializeObject(themeCssVariables)} as DeepCSSVariableRefs<ThemeType>;
`,
'utf-8',
);
console.log('Generated themeCssVariables.ts');
// --- themeLightCssVariableEntries.ts ---
const lightEntries = prepareThemeForRootCssVariableInjection({
themeNode: THEME_LIGHT,
prefix: 't',
}) as [string, string][];
writeFileSync(
resolve(outputDir, 'themeLightCssVariableEntries.ts'),
`${HEADER}
// 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 = ${MOBILE_VIEWPORT};
// 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 = ${serializeObject(ICON.size)} as const;
export const ICON_STROKES = ${serializeObject(ICON.stroke)} as const;
export const THEME_LIGHT_CSS_VARIABLE_ENTRIES: [string, string][] = ${serializeTupleArray(lightEntries)};
`,
'utf-8',
);
console.log('Generated themeLightCssVariableEntries.ts');
// --- themeDarkCssVariableEntries.ts ---
const darkEntries = prepareThemeForRootCssVariableInjection({
themeNode: THEME_DARK,
prefix: 't',
}) as [string, string][];
writeFileSync(
resolve(outputDir, 'themeDarkCssVariableEntries.ts'),
`${HEADER}
export const THEME_DARK_CSS_VARIABLE_ENTRIES: [string, string][] = ${serializeTupleArray(darkEntries)};
`,
'utf-8',
);
console.log('Generated themeDarkCssVariableEntries.ts');
@@ -1,8 +1,7 @@
import { styled } from '@linaria/react';
import { type ColorScheme } from '@ui/input/types/ColorScheme';
import { MOBILE_VIEWPORT } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
import { MOBILE_VIEWPORT, themeCssVariables } from '@ui/theme-constants';
import { ColorSchemeCard } from './ColorSchemeCard';
const StyledContainer = styled.div`
@@ -0,0 +1,18 @@
// 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;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,11 +7,5 @@
* |___/
*/
export { themeCssVariables } from './generated/themeCssVariables';
export { THEME_DARK_CSS_VARIABLE_ENTRIES } from './generated/themeDarkCssVariableEntries';
export {
MOBILE_VIEWPORT,
ICON_SIZES,
ICON_STROKES,
THEME_LIGHT_CSS_VARIABLE_ENTRIES,
} from './generated/themeLightCssVariableEntries';
export { MOBILE_VIEWPORT, ICON_SIZES, ICON_STROKES } from './constants';
export { themeCssVariables } from './themeCssVariables';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,16 +1,5 @@
// Auto-generated by scripts/generateThemeConstants.ts — do not edit manually.
// Regenerate: npx tsx packages/twenty-ui/scripts/generateThemeConstants.ts
import type { ThemeType } from '@ui/theme/types/ThemeType';
type DeepCSSVariableRefs<T> = {
[K in keyof T]: T[K] extends (...args: never[]) => unknown
? Record<string | number, string>
: T[K] extends Record<string, unknown>
? DeepCSSVariableRefs<T[K]>
: string;
};
// This file is generated from packages/twenty-ui/src/theme/constants/.
// Do not edit manually — regenerate by running the generation script.
export const themeCssVariables = {
icon: {
size: {
@@ -1098,4 +1087,4 @@ export const themeCssVariables = {
amber12: 'var(--t-color-transparent-amber12)',
},
},
} as DeepCSSVariableRefs<ThemeType>;
};
@@ -6,7 +6,7 @@ import { GRAY_SCALE_DARK } from './GrayScaleDark';
import { TRANSPARENT_COLORS_DARK } from './TransparentColorsDark';
export const BACKGROUND_DARK = {
noisy: `url(${DarkNoise.toString()});`,
noisy: `url(${DarkNoise.toString()})`,
primary: GRAY_SCALE_DARK.gray1,
secondary: GRAY_SCALE_DARK.gray2,
tertiary: GRAY_SCALE_DARK.gray4,
@@ -6,7 +6,7 @@ import { GRAY_SCALE_LIGHT } from './GrayScaleLight';
import { TRANSPARENT_COLORS_LIGHT } from './TransparentColorsLight';
export const BACKGROUND_LIGHT = {
noisy: `url(${LightNoise.toString()});`,
noisy: `url(${LightNoise.toString()})`,
primary: GRAY_SCALE_LIGHT.gray1,
secondary: GRAY_SCALE_LIGHT.gray2,
tertiary: GRAY_SCALE_LIGHT.gray4,
@@ -1,8 +0,0 @@
import { type ThemeType } from '..';
export const HOVER_BACKGROUND = (props: { theme: ThemeType }) => `
transition: background 0.1s ease;
&:hover {
background: ${props.theme.background.transparent.light};
}
`;
@@ -1 +0,0 @@
export const MOBILE_VIEWPORT = 768;
@@ -1,19 +0,0 @@
import { themeCssVariables } from '../../theme-constants';
export const TEXT_INPUT_STYLE = `
background-color: transparent;
border: none;
color: ${themeCssVariables.font.color.primary};
font-family: ${themeCssVariables.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
&::placeholder,
&::-webkit-input-placeholder {
color: ${themeCssVariables.font.color.light};
font-family: ${themeCssVariables.font.family};
font-weight: ${themeCssVariables.font.weight.medium};
}
`;
@@ -1,11 +1,9 @@
import { BLUR_DARK } from '@ui/theme/constants/BlurDark';
import { ILLUSTRATION_ICON_DARK } from '@ui/theme/constants/IllustrationIconDark';
import {
COLOR_DARK,
GRAY_SCALE_DARK,
SNACK_BAR_DARK,
type ThemeType,
} from '..';
import { COLOR_DARK } from './ColorsDark';
import { GRAY_SCALE_DARK } from './GrayScaleDark';
import { SNACK_BAR_DARK } from './SnackBarDark';
import type { ThemeType } from '../types/ThemeType';
import { ACCENT_DARK } from './AccentDark';
import { BACKGROUND_DARK } from './BackgroundDark';
import { BORDER_DARK } from './BorderDark';
-5
View File
@@ -31,7 +31,6 @@ export { GRAY_SCALE_DARK } from './constants/GrayScaleDark';
export { GRAY_SCALE_DARK_ALPHA } from './constants/GrayScaleDarkAlpha';
export { GRAY_SCALE_LIGHT } from './constants/GrayScaleLight';
export { GRAY_SCALE_LIGHT_ALPHA } from './constants/GrayScaleLightAlpha';
export { HOVER_BACKGROUND } from './constants/HoverBackground';
export { ICON } from './constants/Icon';
export { ILLUSTRATION_ICON_DARK } from './constants/IllustrationIconDark';
export { ILLUSTRATION_ICON_LIGHT } from './constants/IllustrationIconLight';
@@ -39,7 +38,6 @@ export type { ThemeColor } from './constants/MainColorNames';
export { MAIN_COLOR_NAMES } from './constants/MainColorNames';
export { MAIN_COLORS_DARK } from './constants/MainColorsDark';
export { MAIN_COLORS_LIGHT } from './constants/MainColorsLight';
export { MOBILE_VIEWPORT } from './constants/MobileViewport';
export { MODAL } from './constants/Modal';
export { RGBA } from './constants/Rgba';
export { SECONDARY_COLORS_DARK } from './constants/SecondaryColorsDark';
@@ -49,7 +47,6 @@ export { SNACK_BAR_LIGHT } from './constants/SnackBarLight';
export { TAG_DARK } from './constants/TagDark';
export { TAG_LIGHT } from './constants/TagLight';
export { TEXT } from './constants/Text';
export { TEXT_INPUT_STYLE } from './constants/TextInputStyle';
export { THEME_COMMON } from './constants/ThemeCommon';
export { THEME_DARK } from './constants/ThemeDark';
export { THEME_LIGHT } from './constants/ThemeLight';
@@ -60,8 +57,6 @@ export {
ThemeContext,
ThemeContextProvider,
} from './provider/ThemeContextProvider';
export { ThemeCssVariableInjectorEffect } from './provider/ThemeCssVariableInjectorEffect';
export { ThemeProvider } from './provider/ThemeProvider';
export type { ThemeType } from './types/ThemeType';
export { getNextThemeColor } from './utils/getNextThemeColor';
export { SPACING_VALUES } from './utils/spacingValues';
@@ -1,7 +1,6 @@
import { createContext } from 'react';
import { createContext, useLayoutEffect } from 'react';
import { type ThemeType } from '@ui/theme/types/ThemeType';
import { ThemeCssVariableInjectorEffect } from '@ui/theme/provider/ThemeCssVariableInjectorEffect';
export type ThemeContextType = {
theme: ThemeType;
@@ -18,10 +17,14 @@ export const ThemeContextProvider = ({
children: React.ReactNode;
theme: ThemeType;
}) => {
useLayoutEffect(() => {
const root = document.documentElement;
const isDark = theme.name === 'dark';
root.classList.toggle('dark', isDark);
root.classList.toggle('light', !isDark);
}, [theme.name]);
return (
<ThemeContext.Provider value={{ theme }}>
<ThemeCssVariableInjectorEffect theme={theme} />
{children}
</ThemeContext.Provider>
<ThemeContext.Provider value={{ theme }}>{children}</ThemeContext.Provider>
);
};
@@ -1,31 +0,0 @@
import { useLayoutEffect } from 'react';
import {
THEME_DARK_CSS_VARIABLE_ENTRIES,
THEME_LIGHT_CSS_VARIABLE_ENTRIES,
} from '@ui/theme-constants';
export const ThemeCssVariableInjectorEffect = ({
theme,
}: {
theme: { name: string };
}) => {
const entries =
theme.name === 'dark'
? THEME_DARK_CSS_VARIABLE_ENTRIES
: THEME_LIGHT_CSS_VARIABLE_ENTRIES;
useLayoutEffect(() => {
const root = document.documentElement;
for (const [name, value] of entries) {
root.style.setProperty(name, value);
}
return () => {
for (const [name] of entries) {
root.style.removeProperty(name);
}
};
}, [entries]);
return null;
};
@@ -1,5 +0,0 @@
export const SPACING_VALUES = [
...Array.from({ length: 33 }, (_, i) => i),
0.5,
1.5,
];
@@ -1,4 +1,4 @@
import { MOBILE_VIEWPORT } from '@ui/theme';
import { MOBILE_VIEWPORT } from '@ui/theme-constants';
import { useMediaQuery } from 'react-responsive';
export const useIsMobile = () =>
+14 -1
View File
@@ -1,5 +1,6 @@
import react from '@vitejs/plugin-react-swc';
import wyw from '@wyw-in-js/vite';
import * as fs from 'fs';
import * as path from 'path';
import { createWywProfilingPlugin } from 'twenty-shared/vite';
import { defineConfig } from 'vite';
@@ -13,7 +14,7 @@ type Checkers = Parameters<typeof checker>[0];
import packageJson from './package.json';
const entries = Object.keys(packageJson.exports)
.filter((el) => el !== './style.css')
.filter((el) => !el.endsWith('.css'))
.map((module) => `src/${module}/index.ts`);
const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => {
@@ -90,6 +91,18 @@ export default defineConfig(({ command }) => {
},
}),
),
{
name: 'copy-theme-css',
closeBundle() {
const themeCssFiles = ['theme-light.css', 'theme-dark.css'];
for (const file of themeCssFiles) {
fs.copyFileSync(
path.resolve(__dirname, `src/theme-constants/${file}`),
path.resolve(__dirname, `dist/${file}`),
);
}
},
},
],
build: {
cssCodeSplit: false,