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 6c59560a0a
commit 6bfdb6bcde
16 changed files with 1602 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from "react";
import type { TaskDetail, TaskCreateInput, Task } from "@kb/core";
import type { TaskDetail, TaskCreateInput, Task, ThemeMode } from "@kb/core";
import { fetchConfig, fetchSettings, fetchAuthStatus, updateSettings } from "./api";
import { Header } from "./components/Header";
import { Board } from "./components/Board";
@@ -13,6 +13,7 @@ import { GitHubImportModal } from "./components/GitHubImportModal";
import { GitManagerModal } from "./components/GitManagerModal";
import { useTasks } from "./hooks/useTasks";
import { ToastProvider, useToast } from "./hooks/useToast";
import { useTheme } from "./hooks/useTheme";
function AppInner() {
const [isCreating, setIsCreating] = useState(false);
@@ -38,6 +39,17 @@ function AppInner() {
const [githubTokenConfigured, setGithubTokenConfigured] = useState(false);
const { tasks, createTask, moveTask, deleteTask, mergeTask, retryTask, updateTask, duplicateTask, archiveTask, unarchiveTask } = useTasks();
// Theme management
const { themeMode, colorTheme, setThemeMode, setColorTheme } = useTheme();
// Theme toggle handler: cycles Dark → Light → System → Dark
const handleToggleTheme = useCallback(() => {
const cycle: ThemeMode[] = ["dark", "light", "system"];
const currentIndex = cycle.indexOf(themeMode);
const nextMode = cycle[(currentIndex + 1) % cycle.length];
setThemeMode(nextMode);
}, [themeMode, setThemeMode]);
useEffect(() => {
fetchConfig()
.then((cfg) => setMaxConcurrent(cfg.maxConcurrent))
@@ -145,6 +157,8 @@ function AppInner() {
onToggleEnginePause={handleToggleEnginePause}
view={view}
onChangeView={handleChangeView}
themeMode={themeMode}
onToggleTheme={handleToggleTheme}
/>
{view === "board" ? (
<Board
@@ -200,6 +214,10 @@ function AppInner() {
}}
addToast={addToast}
initialSection={settingsInitialSection}
themeMode={themeMode}
colorTheme={colorTheme}
onThemeModeChange={setThemeMode}
onColorThemeChange={setColorTheme}
/>
)}
<GitHubImportModal