mkstack/src/components/AppProvider.tsx

82 lines
2.1 KiB
TypeScript
Raw Normal View History

2025-06-04 10:04:39 -05:00
import { ReactNode, useEffect } from 'react';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { AppContext, type AppConfig, type AppContextType, type Theme } from '@/contexts/AppContext';
interface AppProviderProps {
children: ReactNode;
/** Application storage key */
storageKey: string;
/** Default app configuration */
defaultConfig: AppConfig;
}
export function AppProvider(props: AppProviderProps) {
const {
children,
storageKey,
defaultConfig
} = props;
// App configuration state with localStorage persistence
const [config, setConfig] = useLocalStorage<AppConfig>(storageKey, defaultConfig);
// Generic config updater with callback pattern
const updateConfig = (updater: (currentConfig: AppConfig) => AppConfig) => {
setConfig(updater);
};
const appContextValue: AppContextType = {
config,
updateConfig,
};
// Apply theme effects to document
useApplyTheme(config.theme);
return (
<AppContext.Provider value={appContextValue}>
{children}
</AppContext.Provider>
);
}
/**
* Hook to apply theme changes to the document root
*/
function useApplyTheme(theme: Theme) {
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
.matches
? 'dark'
: 'light';
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
// Handle system theme changes when theme is set to "system"
useEffect(() => {
if (theme !== 'system') return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
const root = window.document.documentElement;
root.classList.remove('light', 'dark');
const systemTheme = mediaQuery.matches ? 'dark' : 'light';
root.classList.add(systemTheme);
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [theme]);
}