Phase 1 & 2: full-stack family dashboard scaffold
- pnpm monorepo (apps/client + apps/server) - Server: Express + node:sqlite with numbered migration runner, REST API for all 9 features (members, events, chores, shopping, meals, messages, countdowns, photos, settings) - Client: React 18 + Vite + TypeScript + Tailwind + Framer Motion + Zustand - Theme system: dark/light + 5 accent colors, CSS custom properties, anti-FOUC script, ThemeToggle on every surface - AppShell: collapsible sidebar, animated route transitions, mobile drawer - Phase 2 features: Calendar (custom month grid, event chips, add/edit modal), Chores (card grid, complete/reset, member filter, streaks), Shopping (multi-list tabs, animated check-off, quick-add bar, member assign) - Family member CRUD with avatar, color picker - Settings page: theme/accent, photo folder, slideshow, weather, date/time - Docker: multi-stage Dockerfile, docker-compose.yml, entrypoint with PUID/PGID - Unraid: CA XML template, CLI install script, UNRAID.md guide - .gitignore covering node_modules, dist, db files, secrets, build artifacts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type ThemeMode = 'light' | 'dark';
|
||||
export type AccentColor = 'indigo' | 'teal' | 'rose' | 'amber' | 'slate';
|
||||
|
||||
interface ThemeState {
|
||||
mode: ThemeMode;
|
||||
accent: AccentColor;
|
||||
setMode: (mode: ThemeMode) => void;
|
||||
setAccent: (accent: AccentColor) => void;
|
||||
toggleMode: () => void;
|
||||
}
|
||||
|
||||
export const ACCENT_TOKENS: Record<AccentColor, { base: string; light: string; label: string }> = {
|
||||
indigo: { base: '#6366f1', light: '#e0e7ff', label: 'Indigo' },
|
||||
teal: { base: '#14b8a6', light: '#ccfbf1', label: 'Teal' },
|
||||
rose: { base: '#f43f5e', light: '#ffe4e6', label: 'Rose' },
|
||||
amber: { base: '#f59e0b', light: '#fef3c7', label: 'Amber' },
|
||||
slate: { base: '#64748b', light: '#f1f5f9', label: 'Slate' },
|
||||
};
|
||||
|
||||
function applyTheme(mode: ThemeMode, accent: AccentColor) {
|
||||
const root = document.documentElement;
|
||||
const { base, light } = ACCENT_TOKENS[accent];
|
||||
|
||||
// Toggle dark class on <html>
|
||||
root.classList.toggle('dark', mode === 'dark');
|
||||
|
||||
// Accent tokens (same in both modes)
|
||||
root.style.setProperty('--color-accent', base);
|
||||
root.style.setProperty('--color-accent-light', light);
|
||||
|
||||
// Surface tokens
|
||||
if (mode === 'dark') {
|
||||
root.style.setProperty('--color-bg', '#0f172a');
|
||||
root.style.setProperty('--color-surface', '#1e293b');
|
||||
root.style.setProperty('--color-surface-raised', '#263548');
|
||||
root.style.setProperty('--color-border', '#334155');
|
||||
root.style.setProperty('--color-text-primary', '#f1f5f9');
|
||||
root.style.setProperty('--color-text-secondary', '#94a3b8');
|
||||
root.style.setProperty('--color-text-muted', '#64748b');
|
||||
} else {
|
||||
root.style.setProperty('--color-bg', '#f8fafc');
|
||||
root.style.setProperty('--color-surface', '#ffffff');
|
||||
root.style.setProperty('--color-surface-raised', '#f1f5f9');
|
||||
root.style.setProperty('--color-border', '#e2e8f0');
|
||||
root.style.setProperty('--color-text-primary', '#0f172a');
|
||||
root.style.setProperty('--color-text-secondary', '#475569');
|
||||
root.style.setProperty('--color-text-muted', '#94a3b8');
|
||||
}
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
mode: 'light',
|
||||
accent: 'indigo',
|
||||
setMode: (mode) => {
|
||||
set({ mode });
|
||||
applyTheme(mode, get().accent);
|
||||
},
|
||||
setAccent: (accent) => {
|
||||
set({ accent });
|
||||
applyTheme(get().mode, accent);
|
||||
},
|
||||
toggleMode: () => {
|
||||
const next = get().mode === 'light' ? 'dark' : 'light';
|
||||
set({ mode: next });
|
||||
applyTheme(next, get().accent);
|
||||
},
|
||||
}),
|
||||
{ name: 'fp-theme' }
|
||||
)
|
||||
);
|
||||
|
||||
/** Call once at app boot to hydrate CSS tokens from persisted state */
|
||||
export function initTheme() {
|
||||
const { mode, accent } = useThemeStore.getState();
|
||||
applyTheme(mode, accent);
|
||||
}
|
||||
Reference in New Issue
Block a user