diff --git a/.changeset/shadcn-gray-variant.md b/.changeset/shadcn-gray-variant.md new file mode 100644 index 0000000000..5e75544ecf --- /dev/null +++ b/.changeset/shadcn-gray-variant.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Add a Shadcn Gray dashboard color theme with a fully neutral zinc-gray accent. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 67275e26d6..a4d79a0ebe 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1285,7 +1285,7 @@ Non-Command-Center dashboard CSS uses `--text` as the canonical primary text tok ### Theme system -Dark/light modes via `data-theme`; 66 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including the Shadcn zinc-neutral theme with an orange default highlight/accent and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono (grayscale with red accent), and Shadcn Black (pure black and white). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening. +Dark/light modes via `data-theme`; 67 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including the Shadcn zinc-neutral theme with an orange default highlight/accent and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono (grayscale with red accent), Shadcn Black (pure black and white), and Shadcn Gray (fully neutral zinc-gray accent). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening. - **Base tokens** (`--bg`, `--surface`, etc.) — redefine in `:root`, `[data-theme="light"]`, and every theme block. - **Semantic tokens** (`--autopilot-pulse`, `--event-error-text`, `--badge-mission-*`, `--fab-*`) — `:root` + `[data-theme="light"]` only; no per-color-theme overrides. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a064210265..e8b7720fe0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -330,7 +330,7 @@ export const COLOR_THEMES = [ "neon-bloom", "sepia", "shadcn", - // FNXC:DashboardTheming 2026-06-19-16:07: FN-6756 extends the published color-theme union with shadcn-family accent variants; keep dashboard theme options, bootstrap validation, swatches, and theme-data token blocks in lockstep with this ordered list. + // FNXC:DashboardTheming 2026-06-19-16:07: FN-6756 extends the published color-theme union with shadcn-family accent variants; keep dashboard theme options, bootstrap validation, swatches, and theme-data token blocks in lockstep with this ordered list. FNXC:DashboardTheming 2026-06-20-00:00: FN-6814 adds Shadcn Gray as the fully-neutral zinc accent variant; keep it distinct from Shadcn Mono's red accent and Shadcn Black's black/white CTA while preserving the shadcn-family lockstep ordering. "shadcn-blue", "shadcn-green", "shadcn-red", @@ -339,7 +339,7 @@ export const COLOR_THEMES = [ "shadcn-orange", "shadcn-yellow", "shadcn-mono", - "shadcn-black", + "shadcn-black", "shadcn-gray", ] as const; export type ColorTheme = (typeof COLOR_THEMES)[number]; diff --git a/packages/dashboard/app/__tests__/shadcn-gray-theme.test.ts b/packages/dashboard/app/__tests__/shadcn-gray-theme.test.ts new file mode 100644 index 0000000000..cfee31e737 --- /dev/null +++ b/packages/dashboard/app/__tests__/shadcn-gray-theme.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { COLOR_THEMES as CORE_COLOR_THEMES } from "@fusion/core"; +import { COLOR_THEMES as DASHBOARD_COLOR_THEMES } from "../components/themeOptions"; +import fs from "fs"; +import path from "path"; + +const themeDataPath = path.resolve(__dirname, "../public/theme-data.css"); +const dashboardIndexPath = path.resolve(__dirname, "../index.html"); + +/* +FNXC:DashboardTheming 2026-06-20-00:00: +Shadcn Gray is a zinc-only accent variant. This test locks the dashboard wiring and neutral token intent without asserting the desktop bootstrap validator, which intentionally excludes all shadcn variants. +*/ +describe("Shadcn Gray color theme", () => { + const themeData = fs.readFileSync(themeDataPath, "utf-8"); + + it("defines dark and light neutral-gray shadcn token blocks", () => { + const darkBlock = extractSelectorBlock(themeData, '[data-color-theme="shadcn-gray"]'); + const lightBlock = extractSelectorBlock(themeData, '[data-color-theme="shadcn-gray"][data-theme="light"]'); + + expect(darkBlock).toContain("--surface-hover:"); + expect(lightBlock).toContain("--surface-hover:"); + expect(darkBlock).toContain("--btn-border-width: 1px;"); + expect(lightBlock).toContain("--cta-glow: none;"); + expect(darkBlock).toContain("--accent: #a1a1aa;"); + expect(lightBlock).toContain("--accent: #52525b;"); + expect(darkBlock).toContain("--color-info: #a1a1aa;"); + expect(lightBlock).toContain("--color-info: #52525b;"); + expect(`${darkBlock}\n${lightBlock}`).not.toContain("#ef4444"); + expect(`${darkBlock}\n${lightBlock}`).not.toContain("#3b82f6"); + expect(`${darkBlock}\n${lightBlock}`).not.toContain("#60a5fa"); + }); + + it("registers Shadcn Gray in core, dashboard options, and the dashboard bootstrap validator", () => { + expect(CORE_COLOR_THEMES).toContain("shadcn-gray"); + expect(DASHBOARD_COLOR_THEMES).toContainEqual({ + value: "shadcn-gray", + label: "Shadcn Gray", + className: "theme-swatch-shadcn-gray", + }); + + expect(fs.readFileSync(dashboardIndexPath, "utf-8")).toContain("'shadcn-gray'"); + }); +}); + +function extractSelectorBlock(css: string, selector: string): string { + const startIdx = css.indexOf(`${selector} {`); + if (startIdx === -1) { + throw new Error(`Could not find selector block: ${selector}`); + } + + const openBraceIdx = css.indexOf("{", startIdx); + let depth = 1; + let end = openBraceIdx; + for (let i = openBraceIdx + 1; i < css.length; i++) { + if (css[i] === "{") depth++; + if (css[i] === "}") depth--; + if (depth === 0) { + end = i; + break; + } + } + + return css.slice(startIdx, end + 1); +} diff --git a/packages/dashboard/app/components/ThemeSelector.css b/packages/dashboard/app/components/ThemeSelector.css index 7d809e483d..b2f0beff66 100644 --- a/packages/dashboard/app/components/ThemeSelector.css +++ b/packages/dashboard/app/components/ThemeSelector.css @@ -451,6 +451,14 @@ --swatch-sample-4: #27272a; } +/* FNXC:DashboardTheming 2026-06-20-00:00: The Shadcn Gray swatch previews the zinc-only accent choice so users can distinguish it from red-accent Mono and black/white CTA Black before selecting it. */ +.theme-swatch-shadcn-gray { + --swatch-sample-1: #09090b; + --swatch-sample-2: #18181b; + --swatch-sample-3: #a1a1aa; + --swatch-sample-4: #27272a; +} + .theme-swatch-ayu { --swatch-sample-1: #0f1419; --swatch-sample-2: #131d27; @@ -732,6 +740,13 @@ --swatch-sample-4: #e4e4e7; } +[data-theme="light"] .theme-swatch-shadcn-gray { + --swatch-sample-1: #ffffff; + --swatch-sample-2: #f4f4f5; + --swatch-sample-3: #52525b; + --swatch-sample-4: #e4e4e7; +} + [data-theme="light"] .theme-swatch-ayu { --swatch-sample-1: #fafafa; --swatch-sample-2: #f3f3f3; diff --git a/packages/dashboard/app/components/themeOptions.ts b/packages/dashboard/app/components/themeOptions.ts index fd6d1894ac..5e0594e415 100644 --- a/packages/dashboard/app/components/themeOptions.ts +++ b/packages/dashboard/app/components/themeOptions.ts @@ -78,4 +78,6 @@ export const COLOR_THEMES: { value: ColorTheme; label: string; className: string { value: "shadcn-yellow", label: "Shadcn Yellow", className: "theme-swatch-shadcn-yellow" }, { value: "shadcn-mono", label: "Shadcn Mono", className: "theme-swatch-shadcn-mono" }, { value: "shadcn-black", label: "Shadcn Black", className: "theme-swatch-shadcn-black" }, + /* FNXC:DashboardTheming 2026-06-20-00:00: Shadcn Gray is the fully-neutral zinc accent option; keep it adjacent to Shadcn Black so selectors mirror the core COLOR_THEMES order. */ + { value: "shadcn-gray", label: "Shadcn Gray", className: "theme-swatch-shadcn-gray" }, ]; diff --git a/packages/dashboard/app/index.html b/packages/dashboard/app/index.html index 7d49303281..bd8e94e77d 100644 --- a/packages/dashboard/app/index.html +++ b/packages/dashboard/app/index.html @@ -160,7 +160,7 @@ try { var mode = localStorage.getItem('kb-dashboard-theme-mode') || 'dark'; var colorTheme = localStorage.getItem('kb-dashboard-color-theme') || 'default'; - var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'shadcn', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-black']; + var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'shadcn', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-black', 'shadcn-gray']; if (!validThemes.includes(colorTheme)) { colorTheme = 'default'; } diff --git a/packages/dashboard/app/public/theme-data.css b/packages/dashboard/app/public/theme-data.css index c0c71832df..1d9a69714f 100644 --- a/packages/dashboard/app/public/theme-data.css +++ b/packages/dashboard/app/public/theme-data.css @@ -2463,6 +2463,128 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi --accent-text: #fafafa; } +/* +FNXC:DashboardTheming 2026-06-20-00:00: +Shadcn Gray is the fully-neutral zinc variant: it keeps the shadcn surfaces and button treatment while using only zinc/gray accent, CTA, and info tokens instead of red, blue, or other chroma. +*/ +[data-color-theme="shadcn-gray"] { + --bg: #09090b; + --surface: #0c0c0e; + --card: #18181b; + --card-hover: #1f1f23; + --surface-hover: color-mix(in srgb, var(--surface) 90%, var(--text) 10%); + --border: #27272a; + + --text: #fafafa; + --text-muted: #a1a1aa; + --text-dim: #52525b; + + --todo: #a1a1aa; + --in-progress: #71717a; + --in-progress-rgb: 113, 113, 122; + --in-review: #71717a; + --triage: #a1a1aa; + --done: #52525b; + + --color-success: #71717a; + --color-warning: #a1a1aa; + --color-error: #d4d4d8; + --color-muted: #71717a; + + --font-primary: "Geist", "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace; + + --space-xs: 4px; + --space-sm: 6px; + --space-md: 10px; + --space-lg: 16px; + --space-xl: 20px; + --space-2xl: 28px; + + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius: var(--radius-md); + + --btn-padding: 6px 12px; + --btn-border-width: 1px; + --card-padding: 10px 12px; + --modal-padding: var(--space-md) var(--space-lg); + --header-padding: var(--space-sm) var(--space-lg); + --column-gap: var(--space-md); + --board-padding: var(--space-md) var(--space-lg); + + --shadow-sm: 0 1px 2px color-mix(in srgb, #000000 18%, transparent); + --shadow-md: 0 1px 3px color-mix(in srgb, #000000 22%, transparent); + --shadow-lg: 0 4px 12px color-mix(in srgb, #000000 24%, transparent); + --shadow-glow: none; + --glow-success: none; + --glow-warning: none; + --glow-danger: none; + --focus-ring: 0 0 0 calc(var(--btn-border-width) * 3) color-mix(in srgb, var(--accent) 35%, transparent); + --focus-ring-strong: 0 0 0 calc(var(--btn-border-width) * 3) color-mix(in srgb, var(--accent) 45%, transparent); + --shadow: var(--shadow-lg); + + --cta-bg: #71717a; + --cta-border: #71717a; + --cta-text: #ffffff; + --cta-bg-hover: #52525b; + --cta-border-hover: #52525b; + --cta-glow: none; + --logo-accent: var(--accent); + --color-info: #a1a1aa; + --accent: #a1a1aa; + --accent-text: #18181b; +} + +[data-color-theme="shadcn-gray"][data-theme="light"] { + --bg: #ffffff; + --surface: #ffffff; + --card: #ffffff; + --card-hover: #f4f4f5; + --surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%); + --border: #e4e4e7; + + --text: #09090b; + --text-muted: #71717a; + --text-dim: #a1a1aa; + + --todo: #71717a; + --in-progress: #52525b; + --in-progress-rgb: 82, 82, 91; + --in-review: #52525b; + --triage: #71717a; + --done: #a1a1aa; + + --color-success: #52525b; + --color-warning: #71717a; + --color-error: #3f3f46; + --color-muted: #71717a; + + --shadow-sm: 0 1px 2px color-mix(in srgb, var(--text) 8%, transparent); + --shadow-md: 0 1px 3px color-mix(in srgb, var(--text) 10%, transparent); + --shadow-lg: 0 4px 12px color-mix(in srgb, var(--text) 12%, transparent); + --shadow-glow: none; + --glow-success: none; + --glow-warning: none; + --glow-danger: none; + --focus-ring: 0 0 0 calc(var(--btn-border-width) * 3) color-mix(in srgb, var(--accent) 18%, transparent); + --focus-ring-strong: 0 0 0 calc(var(--btn-border-width) * 3) color-mix(in srgb, var(--accent) 28%, transparent); + --shadow: var(--shadow-lg); + + --cta-bg: #52525b; + --cta-border: #52525b; + --cta-text: #ffffff; + --cta-bg-hover: #3f3f46; + --cta-border-hover: #3f3f46; + --cta-glow: none; + --logo-accent: var(--accent); + --color-info: #52525b; + --accent: #52525b; + --accent-text: #ffffff; +} + [data-color-theme="shadcn-blue"] .card.agent-active, [data-color-theme="shadcn-green"] .card.agent-active, [data-color-theme="shadcn-red"] .card.agent-active, @@ -2471,7 +2593,8 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-orange"] .card.agent-active, [data-color-theme="shadcn-yellow"] .card.agent-active, [data-color-theme="shadcn-mono"] .card.agent-active, -[data-color-theme="shadcn-black"] .card.agent-active { +[data-color-theme="shadcn-black"] .card.agent-active, +[data-color-theme="shadcn-gray"] .card.agent-active { border-color: var(--accent); box-shadow: none; animation: none; @@ -2485,7 +2608,8 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-orange"][data-theme="light"] .card.agent-active, [data-color-theme="shadcn-yellow"][data-theme="light"] .card.agent-active, [data-color-theme="shadcn-mono"][data-theme="light"] .card.agent-active, -[data-color-theme="shadcn-black"][data-theme="light"] .card.agent-active { +[data-color-theme="shadcn-black"][data-theme="light"] .card.agent-active, +[data-color-theme="shadcn-gray"][data-theme="light"] .card.agent-active { border-color: var(--accent); box-shadow: none; animation: none; @@ -2499,7 +2623,8 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-orange"] .btn, [data-color-theme="shadcn-yellow"] .btn, [data-color-theme="shadcn-mono"] .btn, -[data-color-theme="shadcn-black"] .btn { +[data-color-theme="shadcn-black"] .btn, +[data-color-theme="shadcn-gray"] .btn { text-transform: none; letter-spacing: normal; font-weight: 500; @@ -2522,7 +2647,9 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-mono"] .btn-primary, [data-color-theme="shadcn-mono"] .btn-task-create, [data-color-theme="shadcn-black"] .btn-primary, -[data-color-theme="shadcn-black"] .btn-task-create { +[data-color-theme="shadcn-black"] .btn-task-create, +[data-color-theme="shadcn-gray"] .btn-primary, +[data-color-theme="shadcn-gray"] .btn-task-create { background: var(--cta-bg); border-color: var(--cta-border); color: var(--cta-text); @@ -2546,7 +2673,9 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-mono"] .btn-primary:hover, [data-color-theme="shadcn-mono"] .btn-task-create:hover, [data-color-theme="shadcn-black"] .btn-primary:hover, -[data-color-theme="shadcn-black"] .btn-task-create:hover { +[data-color-theme="shadcn-black"] .btn-task-create:hover, +[data-color-theme="shadcn-gray"] .btn-primary:hover, +[data-color-theme="shadcn-gray"] .btn-task-create:hover { background: var(--cta-bg-hover); border-color: var(--cta-border-hover); color: var(--cta-text); @@ -2570,7 +2699,9 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi [data-color-theme="shadcn-mono"] .card, [data-color-theme="shadcn-mono"] .column, [data-color-theme="shadcn-black"] .card, -[data-color-theme="shadcn-black"] .column { +[data-color-theme="shadcn-black"] .column, +[data-color-theme="shadcn-gray"] .card, +[data-color-theme="shadcn-gray"] .column { border-width: var(--btn-border-width); }