SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
1.8 KB · 52 lines tsx
Raw Blame History
1'use client';23import { useSyncExternalStore } from 'react';4import { Moon, Sun } from 'lucide-react';56type Theme = 'light' | 'dark';78const listeners = new Set<() => void>();9function subscribe(cb: () => void) {10  listeners.add(cb);11  const mq = window.matchMedia('(prefers-color-scheme: dark)');12  mq.addEventListener('change', cb);13  return () => {14    listeners.delete(cb);15    mq.removeEventListener('change', cb);16  };17}18function getSnapshot(): Theme {19  const attr = document.documentElement.getAttribute('data-theme');20  if (attr === 'dark' || attr === 'light') return attr;21  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';22}23function getServerSnapshot(): Theme {24  return 'light';25}26function applyTheme(t: Theme) {27  document.documentElement.setAttribute('data-theme', t);28  try {29    localStorage.setItem('ri-theme', t);30  } catch {31    /* ignore */32  }33  for (const l of listeners) l();34}3536export function ThemeToggle({ className }: { className?: string }) {37  const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);38  return (39    <button40      type="button"41      aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}42      className={className ?? 'inline-flex h-8 w-8 items-center justify-center rounded-md text-muted hover:bg-inset hover:text-fg'}43      onClick={() => applyTheme(theme === 'dark' ? 'light' : 'dark')}44    >45      {theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}46    </button>47  );48}4950/** Inline script: apply the stored theme before paint to avoid flashes. */51export const THEME_SCRIPT = `(function(){try{var t=localStorage.getItem('ri-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();`;52