SPB Git

Repository Public

theme.ts 2.2 KB 61 lines typescript

1import { create } from 'zustand';23export type ThemePref = 'light' | 'dark' | 'system';4export type Theme = 'light' | 'dark';5export const THEME_KEY = 'cr.theme';6export const THEME_COLOR: Record<Theme, string> = { light: '#F7F6F3', dark: '#080B10' };78const systemDark = () => typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches;9export const resolveTheme = (pref: ThemePref): Theme => (pref === 'dark' || (pref === 'system' && systemDark()) ? 'dark' : 'light');10function readPref(): ThemePref {11  try {12    const v = typeof window === 'undefined' ? null : localStorage.getItem(THEME_KEY);13    return v === 'dark' || v === 'system' ? v : 'light';14  } catch {15    return 'light';16  }17}1819interface ThemeState {20  pref: ThemePref;21  theme: Theme;22  /** Apply a preference: <html data-theme>, theme-color meta, localStorage; cross-fades unless told not to. */23  setPref: (pref: ThemePref, opts?: { animate?: boolean }) => void;24}25function applyDom(pref: ThemePref, theme: Theme, animate: boolean): void {26  const root = document.documentElement;27  if (animate && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {28    root.classList.add('theme-fade');29    window.setTimeout(() => root.classList.remove('theme-fade'), 320);30  }31  root.setAttribute('data-theme', theme);32  root.setAttribute('data-theme-pref', pref);33  document.querySelector('meta[name="theme-color"]')?.setAttribute('content', THEME_COLOR[theme]);34}3536export const useTheme = create<ThemeState>((set, get) => {37  const pref = readPref();38  if (typeof window !== 'undefined') {39    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {40      if (get().pref !== 'system') return;41      const theme = resolveTheme('system');42      applyDom('system', theme, true);43      set({ theme });44    });45  }46  return {47    pref,48    theme: typeof window === 'undefined' ? 'light' : resolveTheme(pref),49    setPref: (next, opts) => {50      const theme = resolveTheme(next);51      try {52        localStorage.setItem(THEME_KEY, next);53      } catch {54        /* storage unavailable */55      }56      applyDom(next, theme, opts?.animate ?? true);57      set({ pref: next, theme });58    },59  };60});61
Files main
Menu

Theme