```typescript // src/theme/theme.types.ts export type Theme = 'light' | 'dark'; export type ThemeProviderProps = { children: React.ReactNode; defaultTheme?: Theme; themeCookieName?: string; };
// src/theme/theme.context.ts import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import type { Theme, ThemeProviderProps } from './theme.types'; import Cookies from 'js-cookie';
const ThemeContext = createContext<{ theme: Theme; setTheme: (newTheme: Theme) => void } | undefined>(undefined);
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, defaultTheme = 'light', themeCookieName = 'app-theme', }) => { const [theme, setThemeState] = useState<Theme>(() => { try { const cookieTheme = Cookies.get(themeCookieName); if (cookieTheme === 'light' || cookieTheme === 'dark') { return cookieTheme; } } catch (e) { console.error('Failed to read theme cookie:', e); }
if (typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return defaultTheme; });
const setTheme = useCallback((newTheme: Theme) => { setThemeState(newTheme); try { Cookies.set(themeCookieName, newTheme, { expires: 365, path: '/' }); } catch (e) { console.error('Failed to set theme cookie:', e); } }, [themeCookieName]);
// Hydration safety: Ensure initial render matches server-side useEffect(() => { const root = document.documentElement; root.style.setProperty('--initial-theme-color', theme === 'dark' ? '#1a202c' : '#ffffff'); // Example CSS variable root.setAttribute('data-theme', theme); }, [theme]);
return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); };
export const useTheme = () => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; };
// src/theme/index.ts export * from './theme.context'; export * from './theme.types';
// Example Usage (App.tsx) // import { ThemeProvider } from './theme'; // function App() { return (<ThemeProvider><YourAppContent /></ThemeProvider>); }
// Example Usage (Component.tsx) // import { useTheme } from './theme'; // function MyComponent() { const { theme, setTheme } = useTheme(); return (<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>); }
// Tests (theme.test.tsx) // import { render, screen, fireEvent } from '@testing-library/react'; // import { ThemeProvider } from './theme'; // ... tests for initial theme, cookie setting, hook usage ...
// Accessibility: Ensure sufficient contrast ratios for text and UI elements in both themes. // Performance: Minimize re-renders by memoizing theme-related calculations if necessary. ```