feat(KB-024): add comprehensive theme system to dashboard

- Add ThemePreference type and useTheme hook for theme state management
- Create CSS theme architecture with 8 built-in color themes
- Build ThemeSelector component with live preview and keyboard navigation
- Add theme toggle to Header with quick-switch capability
- Integrate Appearance section into SettingsModal
- Wire theme system into App component with system preference detection
- Add comprehensive tests for useTheme, ThemeSelector, and Header
- Include theming documentation in dashboard README
- Add changeset for patch release
This commit is contained in:
gsxdsm
2026-03-29 20:22:00 -07:00
parent b935f310ce
commit d288e158d5
16 changed files with 1602 additions and 10 deletions

View File

@@ -187,5 +187,70 @@ describe("Header", () => {
expect(boardBtn.className).not.toContain("active");
expect(boardBtn.getAttribute("aria-pressed")).toBe("false");
});
// ── Theme Toggle ─────────────────────────────────────────────────
it("renders theme toggle button when onToggleTheme is provided", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="dark" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTestId("theme-toggle-btn");
expect(btn).toBeDefined();
});
it("does not render theme toggle when onToggleTheme is not provided", () => {
render(<Header />);
const btn = screen.queryByTestId("theme-toggle-btn");
expect(btn).toBeNull();
});
it("calls onToggleTheme when theme toggle button is clicked", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="dark" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTestId("theme-toggle-btn");
fireEvent.click(btn);
expect(onToggleTheme).toHaveBeenCalledOnce();
});
it("shows Moon icon for dark mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="dark" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTestId("theme-toggle-btn");
expect(btn.querySelector("svg")).toBeDefined();
});
it("shows Sun icon for light mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="light" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTestId("theme-toggle-btn");
expect(btn.querySelector("svg")).toBeDefined();
});
it("shows Monitor icon for system mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="system" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTestId("theme-toggle-btn");
expect(btn.querySelector("svg")).toBeDefined();
});
it("shows correct title for dark mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="dark" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTitle("Toggle theme (Dark)");
expect(btn).toBeDefined();
});
it("shows correct title for light mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="light" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTitle("Toggle theme (Light)");
expect(btn).toBeDefined();
});
it("shows correct title for system mode", () => {
const onToggleTheme = vi.fn();
render(<Header themeMode="system" onToggleTheme={onToggleTheme} />);
const btn = screen.getByTitle("Toggle theme (System)");
expect(btn).toBeDefined();
});
});