useAppearance.tsx 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { useCallback, useEffect, useState } from 'react';
  2. export type Appearance = 'light' | 'dark' | 'system';
  3. const prefersDark = () => {
  4. if (typeof window === 'undefined') {
  5. return false;
  6. }
  7. return window.matchMedia('(prefers-color-scheme: dark)').matches;
  8. };
  9. const setCookie = (name: string, value: string, days = 365) => {
  10. if (typeof document === 'undefined') {
  11. return;
  12. }
  13. const maxAge = days * 24 * 60 * 60;
  14. document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
  15. };
  16. const applyTheme = (appearance: Appearance) => {
  17. const isDark = appearance === 'dark' || (appearance === 'system' && prefersDark());
  18. document.documentElement.classList.toggle('dark', isDark);
  19. document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
  20. };
  21. const mediaQuery = () => {
  22. if (typeof window === 'undefined') {
  23. return null;
  24. }
  25. return window.matchMedia('(prefers-color-scheme: dark)');
  26. };
  27. const handleSystemThemeChange = () => {
  28. const currentAppearance = localStorage.getItem('appearance') as Appearance;
  29. applyTheme(currentAppearance || 'system');
  30. };
  31. export function initializeTheme() {
  32. const savedAppearance = (localStorage.getItem('appearance') as Appearance) || 'system';
  33. applyTheme(savedAppearance);
  34. // Add the event listener for system theme changes...
  35. mediaQuery()?.addEventListener('change', handleSystemThemeChange);
  36. }
  37. export function useAppearance() {
  38. const [appearance, setAppearance] = useState<Appearance>('system');
  39. const updateAppearance = useCallback((mode: Appearance) => {
  40. setAppearance(mode);
  41. // Store in localStorage for client-side persistence...
  42. localStorage.setItem('appearance', mode);
  43. // Store in cookie for SSR...
  44. setCookie('appearance', mode);
  45. applyTheme(mode);
  46. }, []);
  47. useEffect(() => {
  48. const savedAppearance = localStorage.getItem('appearance') as Appearance | null;
  49. updateAppearance(savedAppearance || 'system');
  50. return () => mediaQuery()?.removeEventListener('change', handleSystemThemeChange);
  51. }, [updateAppearance]);
  52. return { appearance, updateAppearance } as const;
  53. }