Files
fusion/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx
gsxdsm 58f758842b FN-6816: add shadcn custom color picker
Add a Shadcn Custom theme flow for editing and applying sanitized dashboard color tokens.

- Add a lazy-loaded Shadcn color picker modal with token defaults, reset controls, and validation tests.
- Persist sanitized custom color overrides through settings, pre-hydration theme bootstrapping, and Command Center/theme dropdown surfaces.
- Register the Shadcn Custom theme option in core types, schema, docs, theme CSS, and release notes.

Files changed:
 .changeset/fn-6816-shadcn-custom-colors.md         |   5 +
 docs/dashboard-guide.md                            |   2 +-
 docs/settings-reference.md                         |   3 +-
 packages/core/src/settings-schema.ts               |   1 +
 packages/core/src/types.ts                         |   4 +
 packages/dashboard/app/App.tsx                     |   7 +-
 packages/dashboard/app/components/AppModals.tsx    |   6 +
 .../dashboard/app/components/SettingsModal.tsx     |  12 ++
 .../dashboard/app/components/ShadcnColorPicker.css | 101 ++++++++++++++
 .../dashboard/app/components/ShadcnColorPicker.tsx |  95 +++++++++++++
 .../dashboard/app/components/ThemeDropdown.tsx     |  23 +++-
 .../dashboard/app/components/ThemeSelector.css     |  14 ++
 .../dashboard/app/components/ThemeSelector.tsx     |  19 ++-
 .../__tests__/ShadcnColorPicker.test.tsx           |  50 +++++++
 .../components/__tests__/ThemeDropdown.test.tsx    |  19 +++
 .../components/__tests__/ThemeSelector.test.tsx    |  64 +++++++++
 .../__tests__/shadcnCustomColors.test.ts           |  52 ++++++++
 .../components/command-center/CommandCenter.tsx    |  15 +++
 .../command-center/CommandCenterControls.tsx       |   8 +-
 .../settings/sections/AppearanceSection.tsx        |   8 +-
 .../dashboard/app/components/shadcnCustomColors.ts |  80 +++++++++++
 packages/dashboard/app/components/themeOptions.ts  |   1 +
 .../dashboard/app/hooks/__tests__/useTheme.test.ts | 108 +++++++++++++++
 packages/dashboard/app/hooks/useTheme.ts           |  91 ++++++++++++-
 packages/dashboard/app/index.html                  |  19 ++-
 packages/dashboard/app/public/theme-data.css       | 148 ++++++++++++++++++++-
 26 files changed, 938 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-6816

Fusion-Task-Lineage: f16baaa9-16f5-4526-8159-9a1ceff4f807
2026-06-21 16:13:54 -07:00

50 lines
2.8 KiB
TypeScript

import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import type { ThemeMode, ColorTheme } from "@fusion/core";
import { ThemeSelector } from "../../ThemeSelector";
import { LanguageSelector } from "../../LanguageSelector";
import type { SectionBaseProps } from "./context";
export interface AppearanceSectionProps extends SectionBaseProps {
scopeBanner: ReactNode;
themeMode: ThemeMode;
colorTheme: ColorTheme;
dashboardFontScalePct: number;
shadcnCustomColors?: Record<string, string>;
resolvedThemeMode?: "dark" | "light";
onThemeModeChange?: (mode: ThemeMode) => void;
onColorThemeChange?: (theme: ColorTheme) => void;
onDashboardFontScaleChange?: (scalePct: number) => void;
onShadcnCustomColorsChange?: (colors: Record<string, string>) => void;
sessionBannersHidden: boolean;
setSessionBannersHidden: (hidden: boolean) => void;
}
export function AppearanceSection({ scopeBanner, setForm, themeMode, colorTheme, dashboardFontScalePct, shadcnCustomColors = {}, resolvedThemeMode, onThemeModeChange, onColorThemeChange, onDashboardFontScaleChange, onShadcnCustomColorsChange, sessionBannersHidden, setSessionBannersHidden, }: AppearanceSectionProps) {
const { t } = useTranslation("app");
return (<>
{scopeBanner}
<h4 className="settings-section-heading">{t("settings.appearance.title", "Appearance")}</h4>
<ThemeSelector themeMode={themeMode} colorTheme={colorTheme} dashboardFontScalePct={dashboardFontScalePct} onThemeModeChange={(mode) => {
setForm((f) => ({ ...f, themeMode: mode }));
onThemeModeChange?.(mode);
}} onColorThemeChange={(theme) => {
setForm((f) => ({ ...f, colorTheme: theme }));
onColorThemeChange?.(theme);
}} onDashboardFontScaleChange={(scalePct) => {
setForm((f) => ({ ...f, dashboardFontScalePct: scalePct }));
onDashboardFontScaleChange?.(scalePct);
}} shadcnCustomColors={shadcnCustomColors} resolvedThemeMode={resolvedThemeMode} onShadcnCustomColorsChange={(colors) => {
setForm((f) => ({ ...f, shadcnCustomColors: colors }));
onShadcnCustomColorsChange?.(colors);
}}/>
<LanguageSelector />
<div className="form-group">
<label className="checkbox-label">
<input type="checkbox" checked={sessionBannersHidden} onChange={(e) => setSessionBannersHidden(e.target.checked)}/>
<span>{t("settings.appearance.hideAISessionNotificationBanners", "Hide AI session notification banners")}</span>
</label>
<small className="form-text text-muted">{t("settings.appearance.suppressTheLdquoNeedsYourInputRdquoBanner", " Suppress the &ldquo;needs your input&rdquo; banner that appears when AI sessions are awaiting input or have failed. ")}</small>
</div>
</>);
}
export default AppearanceSection;