FN-8146: restore persistent theme selection
Restore the full Settings theme picker while preserving Shadcn Mono selections across reloads. - Merge the Settings selector into the current-theme row with shared dropdown controls. - Restore Shadcn Mono across theme metadata, token styles, and dashboard/desktop bootstrap validation. - Scope Mono light swatches correctly and cover restored options and persistence with tests. Files changed: .changeset/FN-8146-theme-dropdown-current-row.md | 7 +++ docs/dashboard-guide.md | 5 ++- packages/core/src/types/execution-and-ui.ts | 1 + .../dashboard/app/components/ThemeDropdown.css | 52 ++++++++++++++++++++++ .../dashboard/app/components/ThemeDropdown.tsx | 42 ++++++++++++++--- .../dashboard/app/components/ThemeSelector.css | 42 +---------------- .../dashboard/app/components/ThemeSelector.tsx | 45 +++++-------------- .../components/__tests__/ThemeDropdown.test.tsx | 46 ++++++++++++++++--- .../components/__tests__/ThemeSelector.test.tsx | 29 ++++++++---- .../__tests__/CommandCenterControls.test.tsx | 15 ++++++- packages/dashboard/app/components/themeOptions.ts | 2 + .../dashboard/app/hooks/__tests__/useTheme.test.ts | 8 ++-- packages/dashboard/app/hooks/useTheme.ts | 8 ++-- packages/dashboard/app/index.html | 5 +-- packages/dashboard/app/public/theme-data.css | 16 ++++++- packages/desktop/src/renderer/index.html | 1 + 16 files changed, 213 insertions(+), 111 deletions(-) Fusion-Task-Id: FN-8146 Fusion-Task-Lineage: 32ec7f21-ee6b-4459-8e7b-f5a5435c4994 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/FN-8146-theme-dropdown-current-row.md
Normal file
7
.changeset/FN-8146-theme-dropdown-current-row.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Settings theme selector is merged into the current-theme row and lists every color theme.
|
||||
category: feature
|
||||
dev: ThemeDropdown gains a current-row trigger variant used by ThemeSelector; the static preview markup/CSS was removed. Tests assert the explicit historical theme set on Settings and Command Center, including restored Shadcn Mono.
|
||||
@@ -1852,10 +1852,11 @@ Non-Command-Center dashboard CSS uses `--text` as the canonical primary text tok
|
||||
<!-- FNXC:DashboardTheming 2026-07-01-00:00: Glass Silver is the silver/gray frosted sibling of Glass and is selectable anywhere color themes are listed, so keep the inventory count and theme description in this guide aligned with COLOR_THEMES. -->
|
||||
<!-- FNXC:DashboardTheming 2026-07-03-00:00: Fresh installs and reset-to-default use System mode so the dashboard follows the OS light/dark preference before and after hydration while keeping Shadcn Ember as the default color theme. -->
|
||||
<!-- FNXC:DashboardTheming 2026-07-16-00:00: FN-8151 adds Cobalt, Clay, and Moss; keep this inventory synchronized with the core COLOR_THEMES union and selector surfaces. -->
|
||||
<!-- FNXC:DashboardTheming 2026-07-16-14:30: FN-8146 restores historical Shadcn Mono as a persistent selector and makes the Settings current-theme row its single all-themes dropdown trigger while Command Center stays compact. -->
|
||||
|
||||
Dark/light modes via `data-theme`; fresh installs default to System mode so the resolved theme follows `prefers-color-scheme` until the user explicitly chooses Light, Dark, or System. 80 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including Cobalt (saturated blue), Clay (warm terracotta), Moss (muted forest green), the Shadcn zinc-neutral theme with an orange default highlight/accent, Shadcn Custom (the same base with sanitized per-token color-picker overrides), and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono Red/Blue/Green/Purple/Pink/Orange/Yellow (grayscale surfaces with color-specific accents; legacy `shadcn-mono` selections migrate to Shadcn Mono Red), Shadcn Black (pure black and white), Shadcn Gray (fully neutral zinc-gray accent), and Shadcn Gray Blue (blue-gray slate neutral surfaces with a muted slate-blue accent). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening. Glass Silver preserves the Glass theme's frosted translucent surfaces and transparent modal overlay behavior while using silver and graphite accents instead of purple/pink.
|
||||
Dark/light modes via `data-theme`; fresh installs default to System mode so the resolved theme follows `prefers-color-scheme` until the user explicitly chooses Light, Dark, or System. 81 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including Cobalt (saturated blue), Clay (warm terracotta), Moss (muted forest green), the Shadcn zinc-neutral theme with an orange default highlight/accent, Shadcn Custom (the same base with sanitized per-token color-picker overrides), and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono and Shadcn Mono Red/Blue/Green/Purple/Pink/Orange/Yellow (grayscale surfaces with color-specific accents; Shadcn Mono retains its selected id), Shadcn Black (pure black and white), Shadcn Gray (fully neutral zinc-gray accent), and Shadcn Gray Blue (blue-gray slate neutral surfaces with a muted slate-blue accent). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening. Glass Silver preserves the Glass theme's frosted translucent surfaces and transparent modal overlay behavior while using silver and graphite accents instead of purple/pink.
|
||||
|
||||
Choose Shadcn variants from **Settings → Appearance** or from the Command Center **Overview** theme card; both use the same compact `ThemeDropdown`, labels, and color-chip swatches. The left sidebar active-item highlight and resize accent use the active theme's `--accent`, so they follow the selected Shadcn accent instead of staying fixed blue.
|
||||
Choose color themes from **Settings → Appearance** or from the Command Center **Overview** theme card. Settings merges its selector into the current-theme row, which opens the full color-theme list; Command Center keeps the compact `ThemeDropdown` trigger. Both list every selectable color theme with the same labels and color-chip swatches. The left sidebar active-item highlight and resize accent use the active theme's `--accent`, so they follow the selected Shadcn accent instead of staying fixed blue.
|
||||
|
||||
- **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.
|
||||
|
||||
@@ -128,6 +128,7 @@ export const COLOR_THEMES = [
|
||||
"shadcn-pink",
|
||||
"shadcn-orange",
|
||||
"shadcn-yellow",
|
||||
"shadcn-mono",
|
||||
"shadcn-mono-red",
|
||||
"shadcn-mono-blue",
|
||||
"shadcn-mono-green",
|
||||
|
||||
@@ -37,6 +37,54 @@ Glass theme surfaces create stronger translucent stacking contexts, so the Comma
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-dropdown-trigger--current-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-color: var(--border);
|
||||
background: var(--card);
|
||||
color: var(--text);
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.theme-dropdown-current-row-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: calc(var(--space-2xl) + var(--space-md));
|
||||
block-size: calc(var(--space-2xl) + var(--space-md));
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--todo);
|
||||
}
|
||||
|
||||
.theme-dropdown-current-row-info {
|
||||
display: grid;
|
||||
gap: var(--space-xs);
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.theme-dropdown-current-row-label {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.theme-dropdown-current-row-value {
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-dropdown-trigger--current-row:hover,
|
||||
.theme-dropdown-trigger--current-row:focus-visible {
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--card) 90%, var(--accent));
|
||||
}
|
||||
|
||||
.theme-dropdown-modes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -109,4 +157,8 @@ Glass theme surfaces create stronger translucent stacking contexts, so the Comma
|
||||
.theme-dropdown-list {
|
||||
max-block-size: min(50vh, calc(var(--space-2xl) * 10));
|
||||
}
|
||||
|
||||
.theme-dropdown-trigger--current-row {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ interface ThemeDropdownProps {
|
||||
resolvedThemeMode?: "dark" | "light";
|
||||
onThemeModeChange?: (mode: ThemeMode) => void;
|
||||
onShadcnCustomColorsChange?: (colors: Record<string, string>) => void;
|
||||
triggerVariant?: "compact" | "current-row";
|
||||
}
|
||||
|
||||
export function resolveColorTheme(colorTheme: ColorTheme) {
|
||||
@@ -35,6 +36,9 @@ function ThemeSwatch({ className }: { className: string }) {
|
||||
/*
|
||||
FNXC:Theme 2026-06-19-12:10:
|
||||
FN-6727 requires Command Center operators to change the global app theme from a compact dropdown that previews each color theme with the same rich swatch chips used by Settings; this component accepts App-threaded setters instead of creating another theme owner.
|
||||
|
||||
FNXC:Theme 2026-07-16-14:30:
|
||||
FN-8146 merges Settings' current-theme summary into this dropdown trigger. The Settings variant carries mode and combined-value context while Command Center retains the compact swatch-and-label trigger.
|
||||
*/
|
||||
export function ThemeDropdown({
|
||||
colorTheme,
|
||||
@@ -44,6 +48,7 @@ export function ThemeDropdown({
|
||||
resolvedThemeMode = themeMode === "light" ? "light" : "dark",
|
||||
onThemeModeChange,
|
||||
onShadcnCustomColorsChange = () => {},
|
||||
triggerVariant = "compact",
|
||||
}: ThemeDropdownProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -52,6 +57,13 @@ export function ThemeDropdown({
|
||||
const optionRefs = useRef<Array<HTMLButtonElement | null>>([]);
|
||||
|
||||
const currentTheme = useMemo(() => resolveColorTheme(colorTheme), [colorTheme]);
|
||||
const currentMode = themeMode === "system" ? resolvedThemeMode : themeMode;
|
||||
const CurrentModeIcon = THEME_MODES.find((mode) => mode.value === currentMode)?.icon;
|
||||
const modeLabel = themeMode === "system"
|
||||
? t("theme.system", "System")
|
||||
: t(`theme.${themeMode}`, themeMode ? `${themeMode.charAt(0).toUpperCase()}${themeMode.slice(1)}` : "Dark");
|
||||
const currentThemeLabel = t(`theme.colorTheme.${currentTheme.value}`, currentTheme.label);
|
||||
const currentRowLabel = `${t("theme.currentTheme", "Current theme")} ${modeLabel} / ${currentThemeLabel}`;
|
||||
const listboxId = "theme-dropdown-listbox";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -118,20 +130,38 @@ export function ThemeDropdown({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`theme-dropdown${open ? " open" : ""}`} ref={rootRef}>
|
||||
<div className={`theme-dropdown${triggerVariant === "current-row" ? " theme-dropdown--current-row" : ""}${open ? " open" : ""}`} ref={rootRef}>
|
||||
<button
|
||||
type="button"
|
||||
className="theme-dropdown-trigger btn"
|
||||
className={`theme-dropdown-trigger btn${triggerVariant === "current-row" ? " theme-dropdown-trigger--current-row" : ""}`}
|
||||
aria-label={triggerVariant === "current-row" ? currentRowLabel : undefined}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
aria-controls={listboxId}
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
onKeyDown={handleTriggerKeyDown}
|
||||
>
|
||||
<ThemeSwatch className={currentTheme.className} />
|
||||
<span className="theme-dropdown-trigger-label">
|
||||
{t(`theme.colorTheme.${currentTheme.value}`, currentTheme.label)}
|
||||
</span>
|
||||
{triggerVariant === "current-row" ? (
|
||||
<>
|
||||
<span className="theme-dropdown-current-row-icon" aria-hidden="true">
|
||||
{CurrentModeIcon ? <CurrentModeIcon size={20} /> : null}
|
||||
</span>
|
||||
<span className="theme-dropdown-current-row-info">
|
||||
<span className="theme-dropdown-current-row-label">{t("theme.currentTheme", "Current theme")}</span>
|
||||
<span className="theme-dropdown-current-row-value">
|
||||
{modeLabel} / {currentThemeLabel}
|
||||
</span>
|
||||
</span>
|
||||
<ThemeSwatch className={currentTheme.className} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ThemeSwatch className={currentTheme.className} />
|
||||
<span className="theme-dropdown-trigger-label">
|
||||
{currentThemeLabel}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown size={16} aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
|
||||
@@ -425,6 +425,7 @@
|
||||
--swatch-sample-4: #27272a;
|
||||
}
|
||||
|
||||
.theme-swatch-shadcn-mono,
|
||||
.theme-swatch-shadcn-mono-red {
|
||||
--swatch-sample-1: #09090b;
|
||||
--swatch-sample-2: #18181b;
|
||||
@@ -786,6 +787,7 @@
|
||||
--swatch-sample-4: #e4e4e7;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-swatch-shadcn-mono,
|
||||
[data-theme="light"] .theme-swatch-shadcn-mono-red {
|
||||
--swatch-sample-1: #ffffff;
|
||||
--swatch-sample-2: #f4f4f5;
|
||||
@@ -1231,46 +1233,6 @@
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
}
|
||||
|
||||
/* Theme current preview in settings */
|
||||
.theme-current-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.theme-preview-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius);
|
||||
color: var(--todo);
|
||||
}
|
||||
|
||||
.theme-preview-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.theme-preview-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.theme-preview-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Model combobox dropdown: mobile-friendly sizing */
|
||||
.model-combobox-dropdown {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import "./ThemeSelector.css";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Sun, Moon, Monitor } from "lucide-react";
|
||||
import type { ThemeMode, ColorTheme } from "@fusion/core";
|
||||
import { THEME_MODES } from "./themeOptions";
|
||||
import { ThemeDropdown, resolveColorTheme } from "./ThemeDropdown";
|
||||
import { ThemeDropdown } from "./ThemeDropdown";
|
||||
|
||||
interface ThemeSelectorProps {
|
||||
themeMode: ThemeMode;
|
||||
@@ -40,7 +39,6 @@ export function ThemeSelector({
|
||||
onShadcnCustomColorsChange = () => {},
|
||||
}: ThemeSelectorProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const currentColorTheme = resolveColorTheme(colorTheme);
|
||||
const handleReset = useCallback(() => {
|
||||
onThemeModeChange("system");
|
||||
/*
|
||||
@@ -71,26 +69,16 @@ export function ThemeSelector({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Current Theme Preview */}
|
||||
<div className="theme-current-preview">
|
||||
<div className="theme-preview-icon">
|
||||
{themeMode === "light" ? (
|
||||
<Sun size={20} />
|
||||
) : themeMode === "dark" ? (
|
||||
<Moon size={20} />
|
||||
) : (
|
||||
<Monitor size={20} />
|
||||
)}
|
||||
</div>
|
||||
<div className="theme-preview-info">
|
||||
<div className="theme-preview-label">{t("theme.currentTheme", "Current theme")}</div>
|
||||
<div className="theme-preview-value">
|
||||
{themeMode === "system" ? "System" : `${themeMode.charAt(0).toUpperCase() + themeMode.slice(1)}`}
|
||||
{" / "}
|
||||
{currentColorTheme.label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* FNXC:Theme 2026-07-16-14:30: FN-8146 makes the current-theme row the sole Settings color-theme trigger, replacing the separate static preview and standalone dropdown without adding a second mode control. */}
|
||||
<ThemeDropdown
|
||||
triggerVariant="current-row"
|
||||
colorTheme={colorTheme}
|
||||
themeMode={themeMode}
|
||||
onColorThemeChange={onColorThemeChange}
|
||||
shadcnCustomColors={shadcnCustomColors}
|
||||
resolvedThemeMode={resolvedThemeMode}
|
||||
onShadcnCustomColorsChange={onShadcnCustomColorsChange}
|
||||
/>
|
||||
|
||||
<div className="theme-section-title">{t("theme.fontSize", "Font Size")}</div>
|
||||
<div className="theme-font-size-toggle" role="radiogroup" aria-label={t("theme.fontSizeLabel", "Dashboard font size")}>
|
||||
@@ -106,17 +94,6 @@ export function ThemeSelector({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/*
|
||||
FNXC:Theme 2026-07-16-12:00:
|
||||
Settings Appearance reuses ThemeDropdown so it has the same compact, keyboard-accessible color-theme affordance as the Command Center. ThemeSelector retains its mode, font-size, and reset controls, while ThemeDropdown remains the sole custom-color picker owner to avoid duplicate controls.
|
||||
*/}
|
||||
<ThemeDropdown
|
||||
colorTheme={colorTheme}
|
||||
onColorThemeChange={onColorThemeChange}
|
||||
shadcnCustomColors={shadcnCustomColors}
|
||||
resolvedThemeMode={resolvedThemeMode}
|
||||
onShadcnCustomColorsChange={onShadcnCustomColorsChange}
|
||||
/>
|
||||
|
||||
{/* Reset Button */}
|
||||
<button
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import { COLOR_THEMES } from "../themeOptions";
|
||||
import { ThemeDropdown } from "../ThemeDropdown";
|
||||
|
||||
// FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly.
|
||||
const EXPECTED_THEME_IDS = ['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', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const;
|
||||
|
||||
function renderedThemeIds(listbox: HTMLElement) {
|
||||
return within(listbox).getAllByRole("option").map((option) => {
|
||||
const swatch = option.querySelector<HTMLElement>(".theme-option-swatch");
|
||||
expect(swatch).toBeTruthy();
|
||||
return [...(swatch?.classList ?? [])].find((className) => className.startsWith("theme-swatch-"))?.replace("theme-swatch-", "");
|
||||
});
|
||||
}
|
||||
|
||||
describe("ThemeDropdown", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -21,12 +31,27 @@ describe("ThemeDropdown", () => {
|
||||
|
||||
const listbox = screen.getByRole("listbox", { name: /color theme/i });
|
||||
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
||||
for (const theme of COLOR_THEMES) {
|
||||
const option = within(listbox)
|
||||
.getAllByRole("option")
|
||||
.find((element) => element.textContent?.trim().startsWith(theme.label));
|
||||
expect(option?.querySelector(`.${theme.className}`)).toBeTruthy();
|
||||
}
|
||||
expect(renderedThemeIds(listbox)).toEqual(EXPECTED_THEME_IDS);
|
||||
});
|
||||
|
||||
it("renders a current-row trigger with mode context and all historical theme options", () => {
|
||||
render(
|
||||
<ThemeDropdown
|
||||
triggerVariant="current-row"
|
||||
colorTheme="forest"
|
||||
themeMode="system"
|
||||
resolvedThemeMode="light"
|
||||
onColorThemeChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const trigger = screen.getByRole("button", { name: /current theme system \/ forest/i });
|
||||
expect(trigger).toHaveAttribute("aria-haspopup", "listbox");
|
||||
expect(trigger.querySelector(".theme-dropdown-current-row-icon svg")).toBeTruthy();
|
||||
expect(trigger.querySelector(".theme-swatch-forest")).toBeTruthy();
|
||||
|
||||
fireEvent.click(trigger);
|
||||
expect(renderedThemeIds(screen.getByRole("listbox", { name: /color theme/i }))).toEqual(EXPECTED_THEME_IDS);
|
||||
});
|
||||
|
||||
it("labels only Shadcn Ember as the default option", () => {
|
||||
@@ -166,6 +191,13 @@ describe("ThemeDropdown", () => {
|
||||
expect(getComputedStyle(popover!).zIndex).toBe("10002");
|
||||
});
|
||||
|
||||
it("keeps Shadcn Mono and Mono Red swatches scoped to the active light mode", () => {
|
||||
const css = readFileSync("app/components/ThemeSelector.css", "utf8");
|
||||
|
||||
expect(css).toContain('[data-theme="light"] .theme-swatch-shadcn-mono,\n[data-theme="light"] .theme-swatch-shadcn-mono-red');
|
||||
expect(css).not.toContain('[data-theme="light"] .theme-swatch-shadcn-mono,\n.theme-swatch-shadcn-mono-red');
|
||||
});
|
||||
|
||||
it("preserves the mobile static in-flow popover branch without dropdown elevation", () => {
|
||||
const css = readFileSync("app/components/ThemeDropdown.css", "utf8");
|
||||
|
||||
|
||||
@@ -2,7 +2,17 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import type { ColorTheme } from "@fusion/core";
|
||||
import { ThemeSelector } from "../ThemeSelector";
|
||||
import { COLOR_THEMES } from "../themeOptions";
|
||||
|
||||
// FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly.
|
||||
const EXPECTED_THEME_IDS = ['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', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const;
|
||||
|
||||
function renderedThemeIds(listbox: HTMLElement) {
|
||||
return within(listbox).getAllByRole("option").map((option) => {
|
||||
const swatch = option.querySelector<HTMLElement>(".theme-option-swatch");
|
||||
expect(swatch).toBeTruthy();
|
||||
return [...(swatch?.classList ?? [])].find((className) => className.startsWith("theme-swatch-"))?.replace("theme-swatch-", "");
|
||||
});
|
||||
}
|
||||
|
||||
function renderSelector(colorTheme: ColorTheme | undefined) {
|
||||
const onThemeModeChange = vi.fn();
|
||||
@@ -29,7 +39,9 @@ describe("ThemeSelector", () => {
|
||||
it("uses the shared dropdown and preserves Settings mode, font-size, and reset controls", () => {
|
||||
const { onThemeModeChange, onColorThemeChange, onDashboardFontScaleChange, onShadcnCustomColorsChange } = renderSelector("ocean");
|
||||
|
||||
expect(screen.getByRole("button", { name: "Ocean" })).toHaveAttribute("aria-haspopup", "listbox");
|
||||
const currentThemeTrigger = screen.getByRole("button", { name: /current theme dark \/ ocean/i });
|
||||
expect(currentThemeTrigger).toHaveAttribute("aria-haspopup", "listbox");
|
||||
expect(currentThemeTrigger.querySelector(".theme-dropdown-current-row-icon svg")).toBeTruthy();
|
||||
expect(screen.getAllByRole("radiogroup")).toHaveLength(2);
|
||||
expect(screen.getByLabelText("Light mode")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Small" })).toBeInTheDocument();
|
||||
@@ -48,12 +60,12 @@ describe("ThemeSelector", () => {
|
||||
|
||||
it("opens shared swatched options and selects a color theme", () => {
|
||||
const { onColorThemeChange } = renderSelector("forest");
|
||||
const trigger = screen.getByRole("button", { name: "Forest" });
|
||||
const trigger = screen.getByRole("button", { name: /current theme dark \/ forest/i });
|
||||
|
||||
fireEvent.click(trigger);
|
||||
|
||||
const listbox = screen.getByRole("listbox", { name: "Color theme" });
|
||||
expect(within(listbox).getAllByRole("option")).toHaveLength(COLOR_THEMES.length);
|
||||
expect(renderedThemeIds(listbox)).toEqual(EXPECTED_THEME_IDS);
|
||||
const oceanOption = within(listbox).getByRole("option", { name: "Ocean" });
|
||||
expect(oceanOption.querySelector(".theme-swatch-ocean")).toBeTruthy();
|
||||
fireEvent.click(oceanOption);
|
||||
@@ -70,18 +82,19 @@ describe("ThemeSelector", () => {
|
||||
["forest" as ColorTheme, "Forest"],
|
||||
])("uses the dashboard fallback and label for %s", (colorTheme, expectedLabel) => {
|
||||
const { onColorThemeChange } = renderSelector(colorTheme);
|
||||
const trigger = screen.getByRole("button", { name: expectedLabel });
|
||||
const trigger = screen.getByRole("button", { name: new RegExp(`current theme dark / ${expectedLabel}`, "i") });
|
||||
|
||||
expect(trigger).toHaveTextContent(expectedLabel);
|
||||
expect(trigger).toHaveTextContent(`Dark / ${expectedLabel}`);
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.click(screen.getByRole("option", { name: "Ocean" }));
|
||||
expect(onColorThemeChange).toHaveBeenCalledWith("ocean");
|
||||
});
|
||||
|
||||
it("renders Shadcn Custom's picker exactly once through the shared dropdown", () => {
|
||||
it("uses one merged current-theme trigger and renders Shadcn Custom's picker exactly once", () => {
|
||||
renderSelector("shadcn-custom");
|
||||
|
||||
expect(document.querySelector(".theme-current-preview")).toBeNull();
|
||||
expect(screen.getAllByTestId("shadcn-color-picker")).toHaveLength(1);
|
||||
expect(screen.getByRole("button", { name: "Shadcn Custom" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /current theme dark \/ shadcn custom/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { CommandCenterControls } from "../CommandCenterControls";
|
||||
import { ConfirmDialogProvider } from "../../../hooks/useConfirm";
|
||||
|
||||
@@ -73,6 +73,17 @@ function expectCommandCenterUseOffset(testId: string, ratio: number) {
|
||||
);
|
||||
}
|
||||
|
||||
// FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly.
|
||||
const EXPECTED_THEME_IDS = ['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', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const;
|
||||
|
||||
function renderedThemeIds(listbox: HTMLElement) {
|
||||
return within(listbox).getAllByRole("option").map((option) => {
|
||||
const swatch = option.querySelector<HTMLElement>(".theme-option-swatch");
|
||||
expect(swatch).toBeTruthy();
|
||||
return [...(swatch?.classList ?? [])].find((className) => className.startsWith("theme-swatch-"))?.replace("theme-swatch-", "");
|
||||
});
|
||||
}
|
||||
|
||||
describe("CommandCenterControls concurrency markers", () => {
|
||||
beforeEach(() => {
|
||||
legacyMocks.fetchConfig.mockResolvedValue({ maxConcurrent: 12, rootDir: "/workspace/project" });
|
||||
@@ -94,6 +105,8 @@ describe("CommandCenterControls concurrency markers", () => {
|
||||
|
||||
expect(themeCard).toContainElement(trigger);
|
||||
fireEvent.click(trigger);
|
||||
const listbox = screen.getByRole("listbox", { name: "Color theme" });
|
||||
expect(renderedThemeIds(listbox)).toEqual(EXPECTED_THEME_IDS);
|
||||
fireEvent.click(screen.getByRole("option", { name: "Ocean" }));
|
||||
|
||||
expect(onColorThemeChange).toHaveBeenCalledWith("ocean");
|
||||
|
||||
@@ -85,6 +85,8 @@ export const COLOR_THEMES: { value: ColorTheme; label: string; className: string
|
||||
{ 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" },
|
||||
/* FNXC:DashboardTheming 2026-07-16-14:30: FN-8146 restores the historical Shadcn Mono selector after its FN-6813 migration to Mono Red, so operators can still choose the id shown by the old Settings grid. It intentionally shares Mono Red's tokens. */
|
||||
{ value: "shadcn-mono", label: "Shadcn Mono", className: "theme-swatch-shadcn-mono" },
|
||||
{ value: "shadcn-mono-red", label: "Shadcn Mono Red", className: "theme-swatch-shadcn-mono-red" },
|
||||
{ value: "shadcn-mono-blue", label: "Shadcn Mono Blue", className: "theme-swatch-shadcn-mono-blue" },
|
||||
{ value: "shadcn-mono-green", label: "Shadcn Mono Green", className: "theme-swatch-shadcn-mono-green" },
|
||||
|
||||
@@ -781,13 +781,13 @@ describe("useTheme", () => {
|
||||
expect(document.documentElement.getAttribute("data-theme")).toBe("light");
|
||||
});
|
||||
|
||||
it("remaps legacy shadcn mono color theme from localStorage", () => {
|
||||
it("preserves restored shadcn mono color theme from localStorage", () => {
|
||||
localStorageMock[COLOR_THEME_STORAGE_KEY] = "shadcn-mono";
|
||||
|
||||
const { result } = renderHook(() => useTheme());
|
||||
|
||||
expect(result.current.colorTheme).toBe("shadcn-mono-red");
|
||||
expect(document.documentElement.getAttribute("data-color-theme")).toBe("shadcn-mono-red");
|
||||
expect(result.current.colorTheme).toBe("shadcn-mono");
|
||||
expect(document.documentElement.getAttribute("data-color-theme")).toBe("shadcn-mono");
|
||||
});
|
||||
|
||||
it("preserves explicit Glass and Glass Silver color themes from localStorage", () => {
|
||||
@@ -946,7 +946,7 @@ describe("getThemeInitScript", () => {
|
||||
expect(script).toContain(theme);
|
||||
});
|
||||
expect(script).toContain("validThemes");
|
||||
expect(script).toContain("if (colorTheme === 'shadcn-mono') colorTheme = 'shadcn-mono-red';");
|
||||
expect(script).not.toContain("colorTheme === 'shadcn-mono'");
|
||||
expect(script).toContain("colorTheme = 'shadcn-ember'");
|
||||
});
|
||||
|
||||
|
||||
@@ -90,9 +90,8 @@ function readCachedThemeMode(): ThemeMode {
|
||||
function readCachedColorTheme(): ColorTheme {
|
||||
if (!isBrowser) return DEFAULT_COLOR_THEME;
|
||||
try {
|
||||
let colorTheme = localStorage.getItem(COLOR_THEME_STORAGE_KEY);
|
||||
// FNXC:DashboardTheming 2026-06-20-00:00: FN-6813 keeps existing shadcn-mono users on the renamed red mono variant before the validity guard would otherwise fall back to default.
|
||||
if (colorTheme === 'shadcn-mono') colorTheme = 'shadcn-mono-red';
|
||||
const colorTheme = localStorage.getItem(COLOR_THEME_STORAGE_KEY);
|
||||
// FNXC:DashboardTheming 2026-07-16-14:30: FN-8146 restores Shadcn Mono as a persisted theme, so cached selections must pass validation without remapping to Mono Red.
|
||||
if (colorTheme && VALID_COLOR_THEMES.includes(colorTheme as ColorTheme)) {
|
||||
return colorTheme as ColorTheme;
|
||||
}
|
||||
@@ -476,8 +475,7 @@ export function getThemeInitScript(): string {
|
||||
var colorTheme = localStorage.getItem('${COLOR_THEME_STORAGE_KEY}') || '${DEFAULT_COLOR_THEME}';
|
||||
var validThemes = ${JSON.stringify(VALID_COLOR_THEMES)};
|
||||
// FNXC:DashboardTheming 2026-06-30-00:00: Unset startup theme is Shadcn Ember; explicit stored legacy ids such as "default" and "ocean" remain valid and must not be migrated.
|
||||
// FNXC:DashboardTheming 2026-06-20-00:00: FN-6813 remaps the legacy mono id before bootstrap validation so persisted users keep the red mono accent.
|
||||
if (colorTheme === 'shadcn-mono') colorTheme = 'shadcn-mono-red';
|
||||
// FNXC:DashboardTheming 2026-07-16-14:30: FN-8146 keeps Shadcn Mono's stored id intact so pre-hydration and React hydration select the same restored theme.
|
||||
if (!validThemes.includes(colorTheme)) {
|
||||
colorTheme = '${DEFAULT_COLOR_THEME}';
|
||||
}
|
||||
|
||||
@@ -164,10 +164,9 @@
|
||||
mode = 'system';
|
||||
}
|
||||
var colorTheme = localStorage.getItem('kb-dashboard-color-theme') || 'shadcn-ember';
|
||||
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', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'];
|
||||
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', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'];
|
||||
// FNXC:DashboardTheming 2026-06-30-00:00: Unset startup theme is Shadcn Ember; explicit stored legacy ids such as "default" and "ocean" remain valid and must not be migrated.
|
||||
// FNXC:DashboardTheming 2026-06-20-00:00: FN-6813 remaps the legacy mono id before pre-hydration validation so persisted users keep the red mono accent.
|
||||
if (colorTheme === 'shadcn-mono') colorTheme = 'shadcn-mono-red';
|
||||
// FNXC:DashboardTheming 2026-07-16-14:30: FN-8146 restores Shadcn Mono as a distinct persisted choice, so bootstrap must retain its stored id instead of remapping it to Mono Red.
|
||||
if (!validThemes.includes(colorTheme)) {
|
||||
colorTheme = 'shadcn-ember';
|
||||
}
|
||||
|
||||
@@ -2575,8 +2575,12 @@ FN-6756 adds shadcn color-family themes as exact zinc-neutral shadcn variants wi
|
||||
|
||||
/*
|
||||
FNXC:DashboardTheming 2026-06-20-00:00:
|
||||
FN-6813 makes the shadcn mono theme a color family: shadcn-mono-red preserves the renamed legacy red accent while the other mono variants reuse the same zinc-neutral tokens and swap only accent/CTA values. Legacy stored shadcn-mono selections remap in bootstrap/theme hooks so users keep the red mono theme instead of falling back to default.
|
||||
FN-6813 makes the shadcn mono theme a color family: shadcn-mono-red preserves the renamed red accent while the other mono variants reuse the same zinc-neutral tokens and swap only accent/CTA values.
|
||||
|
||||
FNXC:DashboardTheming 2026-07-16-14:30:
|
||||
FN-8146 restores Shadcn Mono as a persistent option that shares Mono Red tokens. Keep both dark and light selectors fully scoped so neither theme leaks its light ramp into the other mode.
|
||||
*/
|
||||
[data-color-theme="shadcn-mono"],
|
||||
[data-color-theme="shadcn-mono-red"] {
|
||||
--bg: #09090b;
|
||||
--surface: #0c0c0e;
|
||||
@@ -2649,6 +2653,7 @@ FN-6813 makes the shadcn mono theme a color family: shadcn-mono-red preserves th
|
||||
}
|
||||
|
||||
|
||||
[data-color-theme="shadcn-mono"][data-theme="light"],
|
||||
[data-color-theme="shadcn-mono-red"][data-theme="light"] {
|
||||
--bg: #ffffff;
|
||||
--surface: #ffffff;
|
||||
@@ -3786,6 +3791,7 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-pink"] .card.agent-active,
|
||||
[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-mono-red"] .card.agent-active,
|
||||
[data-color-theme="shadcn-mono-blue"] .card.agent-active,
|
||||
[data-color-theme="shadcn-mono-green"] .card.agent-active,
|
||||
@@ -3808,6 +3814,7 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-pink"][data-theme="light"] .card.agent-active,
|
||||
[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-mono-red"][data-theme="light"] .card.agent-active,
|
||||
[data-color-theme="shadcn-mono-blue"][data-theme="light"] .card.agent-active,
|
||||
[data-color-theme="shadcn-mono-green"][data-theme="light"] .card.agent-active,
|
||||
@@ -3830,6 +3837,7 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-pink"] .btn,
|
||||
[data-color-theme="shadcn-orange"] .btn,
|
||||
[data-color-theme="shadcn-yellow"] .btn,
|
||||
[data-color-theme="shadcn-mono"] .btn,
|
||||
[data-color-theme="shadcn-mono-red"] .btn,
|
||||
[data-color-theme="shadcn-mono-blue"] .btn,
|
||||
[data-color-theme="shadcn-mono-green"] .btn,
|
||||
@@ -3859,6 +3867,8 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-orange"] .btn-task-create,
|
||||
[data-color-theme="shadcn-yellow"] .btn-primary,
|
||||
[data-color-theme="shadcn-yellow"] .btn-task-create,
|
||||
[data-color-theme="shadcn-mono"] .btn-primary,
|
||||
[data-color-theme="shadcn-mono"] .btn-task-create,
|
||||
[data-color-theme="shadcn-mono-red"] .btn-primary,
|
||||
[data-color-theme="shadcn-mono-red"] .btn-task-create,
|
||||
[data-color-theme="shadcn-mono-blue"] .btn-primary,
|
||||
@@ -3899,6 +3909,8 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-orange"] .btn-task-create:hover,
|
||||
[data-color-theme="shadcn-yellow"] .btn-primary:hover,
|
||||
[data-color-theme="shadcn-yellow"] .btn-task-create:hover,
|
||||
[data-color-theme="shadcn-mono"] .btn-primary:hover,
|
||||
[data-color-theme="shadcn-mono"] .btn-task-create:hover,
|
||||
[data-color-theme="shadcn-mono-red"] .btn-primary:hover,
|
||||
[data-color-theme="shadcn-mono-red"] .btn-task-create:hover,
|
||||
[data-color-theme="shadcn-mono-blue"] .btn-primary:hover,
|
||||
@@ -3939,6 +3951,8 @@ FN-6815 adds Shadcn Gray Blue as the slate-neutral shadcn variant: the whole sur
|
||||
[data-color-theme="shadcn-orange"] .column,
|
||||
[data-color-theme="shadcn-yellow"] .card,
|
||||
[data-color-theme="shadcn-yellow"] .column,
|
||||
[data-color-theme="shadcn-mono"] .card,
|
||||
[data-color-theme="shadcn-mono"] .column,
|
||||
[data-color-theme="shadcn-mono-red"] .card,
|
||||
[data-color-theme="shadcn-mono-red"] .column,
|
||||
[data-color-theme="shadcn-mono-blue"] .card,
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"shadcn-pink",
|
||||
"shadcn-orange",
|
||||
"shadcn-yellow",
|
||||
"shadcn-mono",
|
||||
"shadcn-mono-red",
|
||||
"shadcn-mono-blue",
|
||||
"shadcn-mono-green",
|
||||
|
||||
Reference in New Issue
Block a user