'use client'; import { useSyncExternalStore } from 'react'; import { Moon, Sun } from 'lucide-react'; import { applyTheme, DEFAULT_THEME, getStoredTheme, isTheme, setStoredTheme, THEME_STORAGE_KEY, type Theme, } from '@/lib/theme'; function subscribe(onChange: () => void): () => void { function handler(event: StorageEvent) { if (event.key === THEME_STORAGE_KEY) onChange(); } window.addEventListener('storage', handler); return () => window.removeEventListener('storage', handler); } function getSnapshot(): Theme { const attr = document.documentElement.dataset.theme; if (isTheme(attr)) return attr; return getStoredTheme(); } function getServerSnapshot(): Theme { return DEFAULT_THEME; } type Variant = 'inline' | 'row'; interface ThemeToggleProps { /** * `inline` is a small icon-only button for use inside the regular nav row. * `row` is a labeled segmented control sized for the compact-mode nav * expansion drawer. */ variant?: Variant; } export default function ThemeToggle({ variant = 'inline' }: ThemeToggleProps) { // Read the live `data-theme` attribute (set by THEME_INIT_SCRIPT before // first paint) so the button reflects the saved theme without a hydration // mismatch — useSyncExternalStore returns the server value during SSR // and swaps to the client value after mount in one pass. const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); function pick(next: Theme) { setStoredTheme(next); applyTheme(next); // Storage events don't fire in the same tab, so nudge subscribers // manually by dispatching a synthetic one. window.dispatchEvent( new StorageEvent('storage', { key: THEME_STORAGE_KEY, newValue: next }) ); } if (variant === 'row') { // Match the other compact-menu rows exactly: an icon + label button using // the shared .compact-nav-link styling. Clicking toggles dark/light; the // moon/sun icon reflects the current theme. const rowNext: Theme = theme === 'dark' ? 'light' : 'dark'; return ( ); } const next: Theme = theme === 'dark' ? 'light' : 'dark'; return ( ); }