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
50 lines
2.8 KiB
TypeScript
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 “needs your input” banner that appears when AI sessions are awaiting input or have failed. ")}</small>
|
|
</div>
|
|
</>);
|
|
}
|
|
export default AppearanceSection;
|