FN-6756: add Shadcn color theme variants
Add the Shadcn color family to dashboard theme selection and persisted theme validation. - Register blue, green, red, purple, pink, orange, yellow, mono, and black Shadcn theme IDs across core and dashboard selectors. - Add swatches and CSS token blocks for light and dark variants, including bootstrap loading coverage. - Document the expanded theme count and include a minor changeset for the published CLI package. Files changed: .changeset/shadcn-color-variants.md | 5 + docs/dashboard-guide.md | 2 +- packages/core/src/types.ts | 10 + .../dashboard/app/components/ThemeSelector.css | 126 +++ packages/dashboard/app/components/themeOptions.ts | 9 + .../dashboard/app/hooks/__tests__/useTheme.test.ts | 41 + packages/dashboard/app/index.html | 2 +- packages/dashboard/app/public/theme-data.css | 1177 ++++++++++++++++++++ 8 files changed, 1370 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-6756 Fusion-Task-Lineage: 143e717a-9718-419f-a6c5-34305e53c65d
This commit is contained in:
5
.changeset/shadcn-color-variants.md
Normal file
5
.changeset/shadcn-color-variants.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
Add Shadcn color-variant dashboard themes for blue, green, red, purple, pink, orange, yellow, mono, and black variants.
|
||||
@@ -1280,7 +1280,7 @@ Non-Command-Center dashboard CSS uses `--text` as the canonical primary text tok
|
||||
|
||||
### Theme system
|
||||
|
||||
Dark/light modes via `data-theme`; 57 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. Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening.
|
||||
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.
|
||||
|
||||
- **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.
|
||||
|
||||
@@ -330,6 +330,16 @@ 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.
|
||||
"shadcn-blue",
|
||||
"shadcn-green",
|
||||
"shadcn-red",
|
||||
"shadcn-purple",
|
||||
"shadcn-pink",
|
||||
"shadcn-orange",
|
||||
"shadcn-yellow",
|
||||
"shadcn-mono",
|
||||
"shadcn-black",
|
||||
] as const;
|
||||
export type ColorTheme = (typeof COLOR_THEMES)[number];
|
||||
|
||||
|
||||
@@ -388,6 +388,69 @@
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-blue {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #3b82f6;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-green {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #22c55e;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-red {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #ef4444;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-purple {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #8b5cf6;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-pink {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #ec4899;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-orange {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #f97316;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-yellow {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #eab308;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-mono {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #ef4444;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-black {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
--swatch-sample-3: #fafafa;
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-ayu {
|
||||
--swatch-sample-1: #0f1419;
|
||||
--swatch-sample-2: #131d27;
|
||||
@@ -606,6 +669,69 @@
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-blue {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #2563eb;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-green {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #16a34a;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-red {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #dc2626;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-purple {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #7c3aed;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-pink {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #db2777;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-orange {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #ea580c;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-yellow {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #ca8a04;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-mono {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #dc2626;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-black {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
--swatch-sample-3: #09090b;
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-ayu {
|
||||
--swatch-sample-1: #fafafa;
|
||||
--swatch-sample-2: #f3f3f3;
|
||||
|
||||
@@ -69,4 +69,13 @@ export const COLOR_THEMES: { value: ColorTheme; label: string; className: string
|
||||
{ value: "neon-bloom", label: "Neon Bloom", className: "theme-swatch-neon-bloom" },
|
||||
{ value: "sepia", label: "Sepia", className: "theme-swatch-sepia" },
|
||||
{ value: "shadcn", label: "Shadcn", className: "theme-swatch-shadcn" },
|
||||
{ value: "shadcn-blue", label: "Shadcn Blue", className: "theme-swatch-shadcn-blue" },
|
||||
{ value: "shadcn-green", label: "Shadcn Green", className: "theme-swatch-shadcn-green" },
|
||||
{ value: "shadcn-red", label: "Shadcn Red", className: "theme-swatch-shadcn-red" },
|
||||
{ value: "shadcn-purple", label: "Shadcn Purple", className: "theme-swatch-shadcn-purple" },
|
||||
{ value: "shadcn-pink", label: "Shadcn Pink", className: "theme-swatch-shadcn-pink" },
|
||||
{ value: "shadcn-orange", label: "Shadcn Orange", className: "theme-swatch-shadcn-orange" },
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
@@ -551,6 +551,47 @@ describe("useTheme", () => {
|
||||
document.head.removeChild(style);
|
||||
});
|
||||
|
||||
it("applies representative shadcn color-family design tokens with neutralized glow effects", () => {
|
||||
const style = document.createElement("style");
|
||||
const baseCss = readFileSync(resolve(PACKAGE_ROOT, "app/styles.css"), "utf8");
|
||||
const themeDataCss = readFileSync(resolve(PACKAGE_ROOT, "app/public/theme-data.css"), "utf8");
|
||||
const shadcnVariants = [
|
||||
{ id: "shadcn-blue", accent: "#3b82f6" },
|
||||
{ id: "shadcn-mono", accent: "#ef4444" },
|
||||
{ id: "shadcn-black", accent: "#fafafa" },
|
||||
] as const;
|
||||
style.textContent = `${baseCss}\n${themeDataCss}`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
for (const variant of shadcnVariants) {
|
||||
const block = themeDataCss.match(
|
||||
new RegExp(`\\[data-color-theme="${variant.id}"\\] \\{(?<body>[\\s\\S]*?)\\n\\}`),
|
||||
)?.groups?.body;
|
||||
expect(block).toBeDefined();
|
||||
|
||||
localStorageMock[COLOR_THEME_STORAGE_KEY] = variant.id;
|
||||
renderHook(() => useTheme());
|
||||
|
||||
expect(document.documentElement.getAttribute("data-color-theme")).toBe(variant.id);
|
||||
expect(block).toContain("--btn-border-width: 1px;");
|
||||
expect(block).toContain(`--accent: ${variant.accent};`);
|
||||
expect(block).toContain("--shadow-glow: none;");
|
||||
expect(block).toContain("--cta-glow: none;");
|
||||
expect(block).not.toMatch(/--(?:shadow-glow|glow-success|glow-warning|glow-danger|cta-glow):\s*0 0/);
|
||||
}
|
||||
|
||||
const blueBlock = themeDataCss.match(/\[data-color-theme="shadcn-blue"\] \{(?<body>[\s\S]*?)\n\}/)?.groups
|
||||
?.body;
|
||||
const blackBlock = themeDataCss.match(/\[data-color-theme="shadcn-black"\] \{(?<body>[\s\S]*?)\n\}/)
|
||||
?.groups?.body;
|
||||
expect(blueBlock).toContain("--todo: #60a5fa;");
|
||||
expect(blackBlock).toContain("--todo: #d4d4d8;");
|
||||
expect(blackBlock).toContain("--in-progress: #a1a1aa;");
|
||||
expect(blackBlock).not.toContain("--todo: #60a5fa;");
|
||||
|
||||
document.head.removeChild(style);
|
||||
});
|
||||
|
||||
it("supports all valid theme modes", () => {
|
||||
const { result } = renderHook(() => useTheme());
|
||||
|
||||
|
||||
@@ -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'];
|
||||
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'];
|
||||
if (!validThemes.includes(colorTheme)) {
|
||||
colorTheme = 'default';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user