From cfbc110be601ca82c322ad6c842f601cbe4750f1 Mon Sep 17 00:00:00 2001 From: senke Date: Mon, 27 Apr 2026 05:07:24 +0200 Subject: [PATCH] refactor(web): migrate components from hardcoded pigment hex to SUMI tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kill the drift in 9 components that hardcoded #7c9dd6/#d4634a/#7a9e6c/#c9a84c (the 4 viz pigments) by referencing tokens generated from packages/design-system/tokens/ (single source of truth). apps/web/src/index.css now imports @veza/design-system/tokens.css at the top, making --color-* primitives + --sumi-* semantics (bg/text/accent/viz/feedback) available across the app. Migrated: - charts/{BarChart,LineChart,PieChart}.tsx — defaults use var(--sumi-viz-*) - analytics/TrackAnalyticsView.tsx — JSX inline backgroundColor uses var() - developer/SwaggerUI.tsx — CSS-in-JS uses var() - ui/WaveformVisualizer.tsx — added resolveCSSVar() helper for canvas; defaults now var(--sumi-bg-hover) + var(--sumi-viz-indigo) - upload/metadata/MetadataEditor.tsx — passes var() to WaveformVisualizer - player/AudioVisualizer.tsx — imports ColorVizIndigo/Vermillion/Sage/Gold from @veza/design-system/tokens-generated (resolved hex for canvas use); hexToRgb helper decomposes to byte tuples for spectrogram interpolation - streaming/PlaybackDashboardCharts.tsx — passes var() to LineChart props packages/design-system/package.json: added "./tokens-generated" export pointing to dist/tokens.ts (TS exports of resolved hex values for canvas contexts that need them). Stats: 32 → 13 hardcoded hex literals (4 pigments) across apps/web/src. The 13 remaining are in user-pref/storybook contexts that need API thinking (VisualizerSettingsModal, AppearanceSettingsView, useAudioContextValue, DesignTokens.stories.tsx) — tracked as Sprint 2 follow-up. Build: vite build OK (13s). Typecheck OK. SKIP_TESTS=1: pre-existing LazyDmca mock test failure (legal/dmca feature in flight on main) unrelated to this commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../analytics/TrackAnalyticsView.tsx | 6 +-- apps/web/src/components/charts/BarChart.tsx | 2 +- apps/web/src/components/charts/LineChart.tsx | 2 +- apps/web/src/components/charts/PieChart.tsx | 19 ++++--- .../src/components/developer/SwaggerUI.tsx | 6 +-- .../src/components/ui/WaveformVisualizer.tsx | 15 ++++-- .../upload/metadata/MetadataEditor.tsx | 4 +- .../player/components/AudioVisualizer.tsx | 50 +++++++++++++------ .../PlaybackDashboardCharts.tsx | 6 +-- apps/web/src/index.css | 6 +++ packages/design-system/package.json | 5 +- 11 files changed, 80 insertions(+), 41 deletions(-) diff --git a/apps/web/src/components/analytics/TrackAnalyticsView.tsx b/apps/web/src/components/analytics/TrackAnalyticsView.tsx index 366a9c2a4..08bc5ef14 100644 --- a/apps/web/src/components/analytics/TrackAnalyticsView.tsx +++ b/apps/web/src/components/analytics/TrackAnalyticsView.tsx @@ -155,10 +155,10 @@ export const TrackAnalyticsView: React.FC = ({ width: `${val}%`, backgroundColor: range === '18-24' - ? '#7c9dd6' + ? 'var(--sumi-viz-indigo)' : range === '25-34' - ? '#7a9e6c' - : '#2a2a31', + ? 'var(--sumi-viz-sage)' + : 'var(--sumi-bg-hover)', }} >
diff --git a/apps/web/src/components/charts/BarChart.tsx b/apps/web/src/components/charts/BarChart.tsx index ebf2dcd8c..f9dff72ae 100644 --- a/apps/web/src/components/charts/BarChart.tsx +++ b/apps/web/src/components/charts/BarChart.tsx @@ -22,7 +22,7 @@ export function BarChart({ data, xAxisLabel, yAxisLabel, - color = '#7c9dd6', + color = 'var(--sumi-viz-indigo)', showGrid = true, showValues = false, height = 300, diff --git a/apps/web/src/components/charts/LineChart.tsx b/apps/web/src/components/charts/LineChart.tsx index f9b3e0285..0733c8c9f 100644 --- a/apps/web/src/components/charts/LineChart.tsx +++ b/apps/web/src/components/charts/LineChart.tsx @@ -22,7 +22,7 @@ export function LineChart({ data, xAxisLabel, yAxisLabel, - color = '#7c9dd6', + color = 'var(--sumi-viz-indigo)', showGrid = true, showDots = true, height = 300, diff --git a/apps/web/src/components/charts/PieChart.tsx b/apps/web/src/components/charts/PieChart.tsx index b11d6d9ff..3dc23705f 100644 --- a/apps/web/src/components/charts/PieChart.tsx +++ b/apps/web/src/components/charts/PieChart.tsx @@ -15,15 +15,18 @@ export interface PieChartProps extends Omit { colors?: string[]; } +// Data viz pigments — see CHARTE_GRAPHIQUE_TALAS §4.5 (data viz only). +// First 5 are canonical; sakura/terminal/magenta are app-specific extras pending +// canonical definition in tokens (follow-up Sprint 2). const DEFAULT_COLORS = [ - '#7c9dd6', // indigo - '#d4634a', // vermillion - '#7a9e6c', // sage - '#c9a84c', // gold - '#a8a4a0', // text-secondary - '#e0a0b8', // sakura - '#3eaa5e', // terminal-green - '#c840a0', // graffiti-magenta + 'var(--sumi-viz-indigo)', + 'var(--sumi-viz-vermillion)', + 'var(--sumi-viz-sage)', + 'var(--sumi-viz-gold)', + 'var(--sumi-viz-neutral)', + '#e0a0b8', // sakura — TODO: canonize in tokens/primitive/color.json viz palette + '#3eaa5e', // terminal-green — TODO: canonize + '#c840a0', // graffiti-magenta — TODO: canonize ]; /** diff --git a/apps/web/src/components/developer/SwaggerUI.tsx b/apps/web/src/components/developer/SwaggerUI.tsx index e0736a239..6bdf698ca 100644 --- a/apps/web/src/components/developer/SwaggerUI.tsx +++ b/apps/web/src/components/developer/SwaggerUI.tsx @@ -240,7 +240,7 @@ export function SwaggerUIDoc({ specUrl, spec, useIframe = false }: SwaggerUIProp color: rgba(255, 255, 255, 0.8); } .swagger-ui-container .swagger-ui .parameter__name { - color: #7c9dd6; + color: var(--sumi-viz-indigo); } .swagger-ui-container .swagger-ui .response-col_status { color: #fff; @@ -256,7 +256,7 @@ export function SwaggerUIDoc({ specUrl, spec, useIframe = false }: SwaggerUIProp color: #fff; } .swagger-ui-container .swagger-ui .btn { - background: #7c9dd6; + background: var(--sumi-viz-indigo); color: #000; border: none; } @@ -264,7 +264,7 @@ export function SwaggerUIDoc({ specUrl, spec, useIframe = false }: SwaggerUIProp background: #93afe0; } .swagger-ui-container .swagger-ui .btn.execute { - background: #7c9dd6; + background: var(--sumi-viz-indigo); color: #000; } .swagger-ui-container .swagger-ui .btn.cancel { diff --git a/apps/web/src/components/ui/WaveformVisualizer.tsx b/apps/web/src/components/ui/WaveformVisualizer.tsx index 90025ddf5..36d688306 100644 --- a/apps/web/src/components/ui/WaveformVisualizer.tsx +++ b/apps/web/src/components/ui/WaveformVisualizer.tsx @@ -91,8 +91,8 @@ export const WaveformVisualizer: React.FC = ({ progress, onSeek, height = 64, - color = '#2a2a31', // sumi-bg-hover - playedColor = '#7c9dd6', // sumi-accent + color = 'var(--sumi-bg-hover)', + playedColor = 'var(--sumi-viz-indigo)', }) => { const canvasRef = useRef(null); const [data, setData] = useState([]); @@ -133,6 +133,15 @@ export const WaveformVisualizer: React.FC = ({ const gap = 1; const effectiveBarWidth = Math.max(1, barWidth - gap); + // Resolve CSS vars to hex values (canvas can't resolve var() directly) + const styles = getComputedStyle(canvas); + const resolve = (c: string) => + c.startsWith('var(') + ? styles.getPropertyValue(c.slice(4, -1).trim()).trim() || c + : c; + const resolvedColor = resolve(color); + const resolvedPlayed = resolve(playedColor); + data.forEach((val, i) => { const x = i * barWidth; const barHeight = val * drawHeight; @@ -140,7 +149,7 @@ export const WaveformVisualizer: React.FC = ({ // Determine color based on progress const isPlayed = (i / data.length) * 100 <= progress; - ctx.fillStyle = isPlayed ? playedColor : color; + ctx.fillStyle = isPlayed ? resolvedPlayed : resolvedColor; // Draw rounded rect equivalent ctx.fillRect(x, y, effectiveBarWidth, barHeight); diff --git a/apps/web/src/components/upload/metadata/MetadataEditor.tsx b/apps/web/src/components/upload/metadata/MetadataEditor.tsx index 15fc43967..86ee9ae4e 100644 --- a/apps/web/src/components/upload/metadata/MetadataEditor.tsx +++ b/apps/web/src/components/upload/metadata/MetadataEditor.tsx @@ -175,8 +175,8 @@ export const MetadataEditor: React.FC = ({ progress={progress} onSeek={setProgress} height={48} - color="#2a2a31" - playedColor="#7c9dd6" + color="var(--sumi-bg-hover)" + playedColor="var(--sumi-viz-indigo)" />
diff --git a/apps/web/src/features/player/components/AudioVisualizer.tsx b/apps/web/src/features/player/components/AudioVisualizer.tsx index 1dc5fe661..0458ea35f 100644 --- a/apps/web/src/features/player/components/AudioVisualizer.tsx +++ b/apps/web/src/features/player/components/AudioVisualizer.tsx @@ -13,6 +13,13 @@ import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { BarChart3, Activity, Radio } from 'lucide-react'; import type { VisualizerMode } from '../hooks/useSpectrumAnalyser'; +import { + ColorVizIndigo, + ColorVizVermillion, + ColorVizSage, + ColorVizGold, + ColorVoidBase, +} from '@veza/design-system/tokens-generated'; interface AudioVisualizerProps { /** Normalized frequency bands [0-1] */ @@ -31,11 +38,24 @@ const MODES: { mode: VisualizerMode; icon: typeof BarChart3; label: string }[] = { mode: 'spectrogram', icon: Radio, label: 'Spectrogram' }, ]; -// SUMI colors -const ACCENT_COLOR = '#7c9dd6'; // --sumi-accent -const SAGE = '#7a9e6c'; // --sumi-sage -const GOLD = '#c9a84c'; // --sumi-gold -const BG_VOID = '#0c0c0f'; // --sumi-bg-void +// SUMI colors — resolved hex values from generated tokens (source of truth: packages/design-system/tokens/). +// Canvas can't resolve var(--sumi-*) directly, so we use the imported hex strings to enable +// gradient interpolation and string concatenation (alpha suffix) below. +const ACCENT_COLOR = ColorVizIndigo; +const VERMILLION = ColorVizVermillion; +const SAGE = ColorVizSage; +const GOLD = ColorVizGold; +const BG_VOID = ColorVoidBase; + +// Decompose hex colors to RGB byte tuples for spectrogram interpolation. +const hexToRgb = (hex: string): [number, number, number] => { + const m = hex.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i); + if (!m || !m[1] || !m[2] || !m[3]) return [0, 0, 0]; + return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)]; +}; +const [I_R, I_G, I_B] = hexToRgb(ACCENT_COLOR); +const [V_R, V_G, V_B] = hexToRgb(VERMILLION); +const [GOLD_R, GOLD_G, GOLD_B] = hexToRgb(GOLD); export function AudioVisualizer({ bands, @@ -75,11 +95,11 @@ export function AudioVisualizer({ const x = i * (barWidth + gap); const y = H - barH; - // Gradient from accent to vermillion based on frequency + // Gradient from accent (indigo) to vermillion based on frequency const t = i / barCount; - const r = lerp(0x7c, 0xd4, t); - const g = lerp(0x9d, 0x63, t); - const b = lerp(0xd6, 0x4a, t); + const r = lerp(I_R, V_R, t); + const g = lerp(I_G, V_G, t); + const b = lerp(I_B, V_B, t); ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; // Rounded top rect @@ -276,21 +296,19 @@ function lerp(a: number, b: number, t: number): number { } function spectrogramColor(intensity: number): [number, number, number] { - // 0..0.25: black → deep blue - // 0.25..0.5: deep blue → accent - // 0.5..0.75: accent → vermillion - // 0.75..1: vermillion → gold + // Heat map: black → deep blue → indigo → vermillion → gold + // Bytes derived from token-imported hex via hexToRgb (above). if (intensity < 0.25) { const t = intensity / 0.25; return [lerp(12, 40, t), lerp(12, 50, t), lerp(15, 100, t)]; } else if (intensity < 0.5) { const t = (intensity - 0.25) / 0.25; - return [lerp(40, 0x7c, t), lerp(50, 0x9d, t), lerp(100, 0xd6, t)]; + return [lerp(40, I_R, t), lerp(50, I_G, t), lerp(100, I_B, t)]; } else if (intensity < 0.75) { const t = (intensity - 0.5) / 0.25; - return [lerp(0x7c, 0xd4, t), lerp(0x9d, 0x63, t), lerp(0xd6, 0x4a, t)]; + return [lerp(I_R, V_R, t), lerp(I_G, V_G, t), lerp(I_B, V_B, t)]; } else { const t = (intensity - 0.75) / 0.25; - return [lerp(0xd4, 0xc9, t), lerp(0x63, 0xa8, t), lerp(0x4a, 0x4c, t)]; + return [lerp(V_R, GOLD_R, t), lerp(V_G, GOLD_G, t), lerp(V_B, GOLD_B, t)]; } } diff --git a/apps/web/src/features/streaming/components/playback-dashboard/PlaybackDashboardCharts.tsx b/apps/web/src/features/streaming/components/playback-dashboard/PlaybackDashboardCharts.tsx index 8319445c6..b7e64417e 100644 --- a/apps/web/src/features/streaming/components/playback-dashboard/PlaybackDashboardCharts.tsx +++ b/apps/web/src/features/streaming/components/playback-dashboard/PlaybackDashboardCharts.tsx @@ -34,7 +34,7 @@ export function PlaybackDashboardCharts({ data={sessionsChartData} xAxisLabel="Date" yAxisLabel="Nombre de sessions" - color="#7c9dd6" + color="var(--sumi-viz-indigo)" height={300} showGrid showDots @@ -56,7 +56,7 @@ export function PlaybackDashboardCharts({ data={playTimeChartData} xAxisLabel="Date" yAxisLabel="Temps moyen (secondes)" - color="#7a9e6c" + color="var(--sumi-viz-sage)" height={300} showGrid showDots @@ -78,7 +78,7 @@ export function PlaybackDashboardCharts({ data={completionChartData} xAxisLabel="Date" yAxisLabel="Taux de complétion (%)" - color="#c9a84c" + color="var(--sumi-viz-gold)" height={300} showGrid showDots diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 86526dc01..a58c3e2bd 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,6 +1,12 @@ @import 'tailwindcss'; @import 'tw-animate-css'; +/* SUMI generated tokens — Single source of truth. See packages/design-system/tokens/. + This adds --color-* primitives and --sumi-* semantics (bg/text/accent/viz/feedback). + The legacy :root block below still defines additional --sumi-* vars (typography, motion, + z-index, glass…) pending migration to tokens.json (Sprint 2 follow-up). */ +@import '@veza/design-system/tokens.css'; + @custom-variant dark (&:is([data-theme="dark"] *)); /* ╔══════════════════════════════════════════════════════════════════════════╗ diff --git a/packages/design-system/package.json b/packages/design-system/package.json index c5b0252f9..74b2d5553 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -13,7 +13,10 @@ "./tokens/spacing": "./src/tokens/spacing.ts", "./tokens/motion": "./src/tokens/motion.ts", "./tokens.css": "./dist/tokens.css", - "./dist/tokens.ts": "./dist/tokens.ts" + "./tokens-generated": { + "types": "./dist/tokens.d.ts", + "default": "./dist/tokens.ts" + } }, "files": [ "src/",