From ab7d3bc9c28ea0ba8e81cba85763922655f04cd9 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Fri, 10 Apr 2026 14:44:08 -0300 Subject: [PATCH 01/10] feat: Add chart color tokens and getCSSVar utility Co-Authored-By: Claude Sonnet 4.6 --- frontend/common/theme/tokens.json | 12 +++ frontend/common/theme/tokens.ts | 14 +++ frontend/common/utils/getCSSVar.ts | 26 +++++ .../TokenReference.generated.stories.tsx | 102 ++++++++++++++++++ frontend/scripts/generate-tokens.mjs | 37 +++++++ frontend/web/styles/_tokens.scss | 22 ++++ 6 files changed, 213 insertions(+) create mode 100644 frontend/common/utils/getCSSVar.ts diff --git a/frontend/common/theme/tokens.json b/frontend/common/theme/tokens.json index 672535b68241..acd40cad8317 100644 --- a/frontend/common/theme/tokens.json +++ b/frontend/common/theme/tokens.json @@ -131,6 +131,18 @@ "info": { "cssVar": "--color-icon-info", "light": "#0aaddf", "dark": "#0aaddf" } } }, + "chart": { + "1": { "cssVar": "--color-chart-1", "light": "#0aaddf", "dark": "#45bce0", "description": "First series in charts. Blue." }, + "2": { "cssVar": "--color-chart-2", "light": "#ef4d56", "dark": "#f57c78", "description": "Second series. Red." }, + "3": { "cssVar": "--color-chart-3", "light": "#27ab95", "dark": "#56ccad", "description": "Third series. Green." }, + "4": { "cssVar": "--color-chart-4", "light": "#ff9f43", "dark": "#ffc08a", "description": "Fourth series. Orange." }, + "5": { "cssVar": "--color-chart-5", "light": "#7a4dfc", "dark": "#906af6", "description": "Fifth series. Purple." }, + "6": { "cssVar": "--color-chart-6", "light": "#0b8bb2", "dark": "#7ecde2", "description": "Sixth series. Blue dark." }, + "7": { "cssVar": "--color-chart-7", "light": "#e61b26", "dark": "#f5a5a2", "description": "Seventh series. Red dark." }, + "8": { "cssVar": "--color-chart-8", "light": "#13787b", "dark": "#87d4c4", "description": "Eighth series. Green dark." }, + "9": { "cssVar": "--color-chart-9", "light": "#fa810c", "dark": "#ffd7b5", "description": "Ninth series. Orange dark." }, + "10": { "cssVar": "--color-chart-10", "light": "#6837fc", "dark": "#b794ff", "description": "Tenth series. Purple dark." } + }, "radius": { "none": { "cssVar": "--radius-none", "value": "0px", "description": "Sharp corners. Tables, dividers." }, "xs": { "cssVar": "--radius-xs", "value": "2px", "description": "Barely rounded. Badges, tags." }, diff --git a/frontend/common/theme/tokens.ts b/frontend/common/theme/tokens.ts index eb361c183eaf..2d154a7e355e 100644 --- a/frontend/common/theme/tokens.ts +++ b/frontend/common/theme/tokens.ts @@ -157,6 +157,20 @@ export const easing: Record = { }, } +// Chart colours — use with getCSSVar() for runtime resolution +export const CHART_COLOURS = [ + '--color-chart-1', + '--color-chart-10', + '--color-chart-2', + '--color-chart-3', + '--color-chart-4', + '--color-chart-5', + '--color-chart-6', + '--color-chart-7', + '--color-chart-8', + '--color-chart-9', +] as const + export type TokenCategory = keyof typeof tokens export type TokenName = keyof (typeof tokens)[C] export type RadiusScale = keyof typeof radius diff --git a/frontend/common/utils/getCSSVar.ts b/frontend/common/utils/getCSSVar.ts new file mode 100644 index 000000000000..23daea6e3ab9 --- /dev/null +++ b/frontend/common/utils/getCSSVar.ts @@ -0,0 +1,26 @@ +/** + * Read the computed value of a CSS custom property from the document root. + * Use this when JS code needs actual colour strings (e.g. Recharts fills) + * rather than var() references. + * + * Respects dark mode — returns the currently resolved value. + * + * @example + * import { getCSSVar } from 'common/utils/getCSSVar' + * const fill = getCSSVar('--color-chart-1') // '#0aaddf' in light, '#45bce0' in dark + */ +export function getCSSVar(name: string): string { + if (typeof document === 'undefined') return '' + return getComputedStyle(document.documentElement) + .getPropertyValue(name) + .trim() +} + +/** + * Read multiple CSS custom properties at once. + */ +export function getCSSVars(names: readonly string[]): string[] { + if (typeof document === 'undefined') return names.map(() => '') + const style = getComputedStyle(document.documentElement) + return names.map((name) => style.getPropertyValue(name).trim()) +} diff --git a/frontend/documentation/TokenReference.generated.stories.tsx b/frontend/documentation/TokenReference.generated.stories.tsx index f85d80ec4671..6c5809bb7dc3 100644 --- a/frontend/documentation/TokenReference.generated.stories.tsx +++ b/frontend/documentation/TokenReference.generated.stories.tsx @@ -384,6 +384,108 @@ export const AllTokens: StoryObj = { +

Chart colours

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TokenValueUsage
+ --color-chart-1 + + #0aaddf + First series in charts. Blue.
+ --color-chart-2 + + #ef4d56 + Second series. Red.
+ --color-chart-3 + + #27ab95 + Third series. Green.
+ --color-chart-4 + + #ff9f43 + Fourth series. Orange.
+ --color-chart-5 + + #7a4dfc + Fifth series. Purple.
+ --color-chart-6 + + #0b8bb2 + Sixth series. Blue dark.
+ --color-chart-7 + + #e61b26 + Seventh series. Red dark.
+ --color-chart-8 + + #13787b + Eighth series. Green dark.
+ --color-chart-9 + + #fa810c + Ninth series. Orange dark.
+ --color-chart-10 + + #6837fc + Tenth series. Purple dark.

Radius

diff --git a/frontend/scripts/generate-tokens.mjs b/frontend/scripts/generate-tokens.mjs index b45be36ad549..f845dfcfce4a 100644 --- a/frontend/scripts/generate-tokens.mjs +++ b/frontend/scripts/generate-tokens.mjs @@ -39,6 +39,8 @@ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1) const NON_COLOUR = ['radius', 'shadow', 'duration', 'easing'] const DESCRIBED = ['radius', 'shadow', 'duration', 'easing'] +// Chart colours are like colour tokens (light/dark) but not under "color" +const CHART_CATEGORY = 'chart' // Build reverse lookups for primitives const hexToPrimitive = new Map() @@ -133,6 +135,18 @@ function buildScssLines() { rootLines.push('') } + // Chart colour tokens + if (json[CHART_CATEGORY]) { + rootLines.push(' // Chart') + for (const [, e] of sorted(json[CHART_CATEGORY])) { + rootLines.push(` ${e.cssVar}: ${toPrimitiveRef(e.light)};`) + if (e.dark && e.dark !== e.light) { + darkLines.push(` ${e.cssVar}: ${toPrimitiveRef(e.dark)};`) + } + } + rootLines.push('') + } + // Non-colour tokens for (const cat of NON_COLOUR) { if (!json[cat]) continue @@ -239,6 +253,18 @@ function generateTs() { const colourLines = buildTsColourLines() const describedBlocks = buildTsDescribedLines() + // Chart tokens as a flat array for JS chart libraries + const chartLines = [] + if (json[CHART_CATEGORY]) { + chartLines.push('// Chart colours — use with getCSSVar() for runtime resolution') + chartLines.push(`export const CHART_COLOURS = [`) + for (const [, e] of sorted(json[CHART_CATEGORY])) { + chartLines.push(` '${e.cssVar}',`) + } + chartLines.push('] as const') + chartLines.push('') + } + const output = [ '// =============================================================================', '// Design Tokens — AUTO-GENERATED from common/theme/tokens.json', @@ -256,6 +282,7 @@ function generateTs() { '', ...describedBlocks, '', + ...chartLines, 'export type TokenCategory = keyof typeof tokens', 'export type TokenName = keyof (typeof tokens)[C]', 'export type RadiusScale = keyof typeof radius', @@ -278,6 +305,16 @@ function generateMcpStory() { tables.push(...buildTableRows(`Colour: ${cat}`, data)) } + // Chart colours + if (json[CHART_CATEGORY]) { + const chartData = Object.values(json[CHART_CATEGORY]).map((e) => ({ + cssVar: e.cssVar, + value: e.light, + description: e.description || '', + })) + tables.push(...buildTableRows('Chart colours', chartData, { showDescription: true })) + } + for (const cat of DESCRIBED) { if (!json[cat]) continue const data = Object.values(json[cat]).map((e) => ({ diff --git a/frontend/web/styles/_tokens.scss b/frontend/web/styles/_tokens.scss index 68db7cadb0f4..554c043af100 100644 --- a/frontend/web/styles/_tokens.scss +++ b/frontend/web/styles/_tokens.scss @@ -134,6 +134,18 @@ --color-icon-success: var(--green-500); --color-icon-warning: var(--orange-500); + // Chart + --color-chart-1: var(--blue-500); + --color-chart-10: var(--purple-600); + --color-chart-2: var(--red-500); + --color-chart-3: var(--green-500); + --color-chart-4: var(--orange-500); + --color-chart-5: var(--purple-500); + --color-chart-6: var(--blue-600); + --color-chart-7: var(--red-600); + --color-chart-8: var(--green-600); + --color-chart-9: var(--orange-600); + // Radius --radius-2xl: 18px; --radius-full: 9999px; @@ -191,6 +203,16 @@ --color-icon-default: var(--slate-0); --color-icon-disabled: oklch(from var(--slate-0) l c h / 0.32); --color-icon-secondary: var(--slate-300); + --color-chart-1: var(--blue-400); + --color-chart-10: var(--purple-300); + --color-chart-2: var(--red-400); + --color-chart-3: var(--green-400); + --color-chart-4: var(--orange-300); + --color-chart-5: var(--purple-400); + --color-chart-6: var(--blue-300); + --color-chart-7: var(--red-300); + --color-chart-8: var(--green-300); + --color-chart-9: var(--orange-200); --shadow-lg: 0px 8px 16px oklch(from var(--slate-1000) l c h / 0.35); --shadow-md: 0px 4px 8px oklch(from var(--slate-1000) l c h / 0.30); --shadow-sm: 0px 1px 2px oklch(from var(--slate-1000) l c h / 0.20); From 42b6e481f1f2083f04da2d73fdf0db54f33fca70 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Fri, 10 Apr 2026 14:44:12 -0300 Subject: [PATCH 02/10] feat: Add ColorSwatch component Co-Authored-By: Claude Sonnet 4.6 --- frontend/web/components/ColorSwatch.tsx | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 frontend/web/components/ColorSwatch.tsx diff --git a/frontend/web/components/ColorSwatch.tsx b/frontend/web/components/ColorSwatch.tsx new file mode 100644 index 000000000000..9e4ece554ce0 --- /dev/null +++ b/frontend/web/components/ColorSwatch.tsx @@ -0,0 +1,35 @@ +import React, { FC } from 'react' +import classNames from 'classnames' + +type ColorSwatchSize = 'sm' | 'md' | 'lg' + +type ColorSwatchProps = { + color: string + size?: ColorSwatchSize + className?: string +} + +const SIZE_MAP: Record = { + lg: 16, + md: 12, + sm: 8, +} + +const ColorSwatch: FC = ({ + className, + color, + size = 'md', +}) => ( +