- TanStack Router with type-safe file-based routes - Sidebar: collapsible, workspace switcher, 12 nav links, user menu - Topbar: search, quick-add, notifications, user avatar - cmdk command palette: nav + create + search + @mentions - Keyboard shortcuts: Cmd+K palette, g+t/h/p/n/c/g/s navigation, ? help - Theme: dark default, accent picker (slate/blue/green/purple/orange), persisted to localStorage - Auth: login -> /, logout -> /login, 401 redirect - Placeholder pages for all 13 nav routes (real in T6/T7) - Typed API client with useApiQuery/useApiMutation hooks
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useThemeStore, ACCENT_PALETTE, type AccentColor } from "@/lib/stores/use-theme-store";
|
|
|
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
const { mode, accent } = useThemeStore();
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
|
|
// Apply dark/light mode
|
|
if (mode === "system") {
|
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
const handler = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
root.classList.toggle("dark", e.matches);
|
|
};
|
|
handler(mediaQuery);
|
|
mediaQuery.addEventListener("change", handler as (e: MediaQueryListEvent) => void);
|
|
return () =>
|
|
mediaQuery.removeEventListener("change", handler as (e: MediaQueryListEvent) => void);
|
|
} else {
|
|
root.classList.toggle("dark", mode === "dark");
|
|
}
|
|
}, [mode]);
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
const palette = ACCENT_PALETTE[accent as AccentColor] || ACCENT_PALETTE.blue;
|
|
root.style.setProperty("--accent-hsl", palette.hsl);
|
|
}, [accent]);
|
|
|
|
return <>{children}</>;
|
|
}
|