810485ecc8
---ci--- phase: 7 milestone: v0.1 status: complete requirements: covered: [REQ-001, REQ-002, REQ-003, REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011, REQ-012, REQ-013, REQ-014, REQ-015, REQ-016, REQ-017, REQ-018, REQ-019, REQ-020, REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026, REQ-027, REQ-028] partial: [] ---/ci--- Milestone v0.1 (nextcraft-ui-prototype) complete. Summary: - 7 phases (P0 pre-execution + P1-P6 execution + P7 final review) - 28 requirements covered (all complete) - 4 surfaces: Learner (7 pages), Marketplace (5), Employer Dashboard (4), Admin (4) - 19 routes, 104 TypeScript files - Shared component library (5 primitives + design tokens) - Mock data: 5 competency stacks (70 competencies), 20 jobs, 15 candidates, 10 employers - Tech: Next.js 15, Tailwind CSS v4, lucide-react, recharts, @xyflow/react, Inter font - Storybook with 6 stories - Dark mode, breadcrumbs, role switcher, responsive design - Build passes, typecheck passes, Storybook build passes Phases: P0 pre-execution → v0.0.1 P1 project-scaffolding → v0.0.2 P2 learner-surface → v0.0.3 P3 marketplace-surface → v0.0.4 P4 employer-dashboard → v0.0.5 P5 admin-surface → v0.0.6 P6 polish-integration → v0.0.7 P7 final-review-ship → v0.1.0 (milestone release)
43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
import type { ThemeMode } from '@nextcraft/types';
|
|
|
|
interface ThemeContextValue {
|
|
mode: ThemeMode;
|
|
toggle: () => void;
|
|
setMode: (mode: ThemeMode) => void;
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [mode, setModeState] = useState<ThemeMode>('light');
|
|
|
|
useEffect(() => {
|
|
const stored = typeof window !== 'undefined' ? window.localStorage.getItem('nextcraft-theme') : null;
|
|
const prefersDark =
|
|
typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
const initial: ThemeMode = stored === 'dark' || stored === 'light' ? stored : prefersDark ? 'dark' : 'light';
|
|
setModeState(initial);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
if (mode === 'dark') root.classList.add('dark');
|
|
else root.classList.remove('dark');
|
|
window.localStorage.setItem('nextcraft-theme', mode);
|
|
}, [mode]);
|
|
|
|
const setMode = useCallback((m: ThemeMode) => setModeState(m), []);
|
|
const toggle = useCallback(() => setModeState((m) => (m === 'dark' ? 'light' : 'dark')), []);
|
|
|
|
const value = useMemo(() => ({ mode, toggle, setMode }), [mode, toggle, setMode]);
|
|
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
}
|
|
|
|
export function useTheme(): ThemeContextValue {
|
|
const ctx = useContext(ThemeContext);
|
|
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
|
return ctx;
|
|
} |