feat(FN-3028): render multi-sample theme swatches in ThemeSelector

ThemeSelector now renders multi-sample theme swatches with richer styling; tests were updated to match and the swatch contract is documented in the dashboard README.

Fusion-Task-Id: FN-3028
This commit is contained in:
Fusion
2026-04-30 18:22:15 -07:00
committed by gsxdsm
parent b1c0912320
commit 9c49b5d6f0
4 changed files with 43 additions and 13 deletions

View File

@@ -480,7 +480,10 @@ To add a new color theme:
1. Add the theme to `COLOR_THEMES` in `packages/core/src/types.ts`
2. Add CSS variables in `packages/dashboard/app/public/theme-data.css` under `[data-color-theme="your-theme"]` (dark variant) and `[data-color-theme="your-theme"][data-theme="light"]` (light variant)
3. Add the swatch class for the theme picker in the CSS
3. Add the swatch class for the theme picker in `packages/dashboard/app/components/CustomModelDropdown.css`
- Each `.theme-swatch-<name>` class should define `--bg` and `--surface` for dark mode.
- Add matching `[data-theme="light"] .theme-swatch-<name>` overrides for light mode.
- The theme picker now renders **4 explicit sample tiles** per option; samples 3 and 4 are generated from `--bg`/`--surface` via `color-mix`, so no extra preview metadata in `theme-data.css` is required.
4. Update `ThemeSelector.tsx` with the new theme option
**Note:** Theme variable blocks are stored in a separate `theme-data.css` file for optimized loading. This file is only loaded when a non-default color theme is active, reducing the initial payload for users with the default theme.

View File

@@ -650,23 +650,43 @@ html .column.drag-over * {
.theme-option.active {
border-color: var(--todo);
background: rgba(88, 166, 255, 0.1);
background: color-mix(in srgb, var(--todo) 14%, transparent);
}
.theme-option-swatch {
width: 48px;
height: 48px;
width: calc(var(--space-2xl) + var(--space-lg));
height: calc(var(--space-2xl) + var(--space-lg));
border-radius: var(--radius);
border: 2px solid var(--border);
position: relative;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(0, 1fr));
overflow: hidden;
--swatch-sample-1: var(--bg);
--swatch-sample-2: var(--surface);
--swatch-sample-3: color-mix(in srgb, var(--swatch-sample-1) 65%, var(--swatch-sample-2) 35%);
--swatch-sample-4: color-mix(in srgb, var(--swatch-sample-2) 65%, var(--swatch-sample-1) 35%);
}
.theme-option-swatch::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--bg) 50%, var(--surface) 50%);
.theme-option-swatch-sample {
display: block;
min-height: 0;
}
.theme-option-swatch-sample-1 {
background: var(--swatch-sample-1);
}
.theme-option-swatch-sample-2 {
background: var(--swatch-sample-2);
}
.theme-option-swatch-sample-3 {
background: var(--swatch-sample-3);
}
.theme-option-swatch-sample-4 {
background: var(--swatch-sample-4);
}
.theme-option-label {

View File

@@ -138,7 +138,12 @@ export function ThemeSelector({
aria-label={`${label} theme`}
title={label}
>
<div className={`theme-option-swatch ${className}`} />
<div className={`theme-option-swatch ${className}`} aria-hidden="true">
<span className="theme-option-swatch-sample theme-option-swatch-sample-1" />
<span className="theme-option-swatch-sample theme-option-swatch-sample-2" />
<span className="theme-option-swatch-sample theme-option-swatch-sample-3" />
<span className="theme-option-swatch-sample theme-option-swatch-sample-4" />
</div>
<span className="theme-option-label">{label}</span>
</button>
))}

View File

@@ -625,7 +625,7 @@ describe("ThemeSelector", () => {
expect(onColorThemeChange).toHaveBeenCalledWith("default");
});
it("each color theme has a swatch", () => {
it("each color theme has a swatch with four explicit sample colors", () => {
render(
<ThemeSelector
themeMode="dark"
@@ -635,7 +635,6 @@ describe("ThemeSelector", () => {
/>
);
// Query all buttons in theme-grid that have aria-pressed (these are the color theme buttons)
const themeOptions = screen.getAllByRole("button").filter(
(btn) => btn.className.includes("theme-option")
);
@@ -644,6 +643,9 @@ describe("ThemeSelector", () => {
themeOptions.forEach((btn) => {
const swatch = btn.querySelector(".theme-option-swatch");
expect(swatch).toBeDefined();
const samples = swatch?.querySelectorAll(".theme-option-swatch-sample");
expect(samples?.length).toBe(4);
});
});
});