spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1'use client';2import { Monitor, Moon, Sun } from 'lucide-react';3import { useEffect, useState } from 'react';4import { cn } from '@/lib/cn';5import { THEME_KEY } from '@/lib/prepaint';6import { THEME_DARK, THEME_LIGHT } from '@/lib/site';78export type ThemePref = 'light' | 'dark' | 'system';910function apply(pref: ThemePref) {11 const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;12 const t = pref === 'system' ? (dark ? 'dark' : 'light') : pref;13 document.documentElement.setAttribute('data-theme', t);14 document.documentElement.style.colorScheme = t;15 const meta = document.querySelector('meta[name="theme-color"]');16 if (meta) meta.setAttribute('content', t === 'dark' ? THEME_DARK : THEME_LIGHT);17}1819export function useTheme(): [ThemePref, (p: ThemePref) => void] {20 const [pref, setPref] = useState<ThemePref>('system');21 useEffect(() => {22 const p = localStorage.getItem(THEME_KEY);23 if (p === 'light' || p === 'dark') setPref(p);24 // Error/not-found shells are client-rendered from scratch and can lose the prepaint attribute: re-apply.25 if (!document.documentElement.getAttribute('data-theme')) apply(p === 'light' || p === 'dark' ? p : 'system');26 const m = window.matchMedia('(prefers-color-scheme: dark)');27 const onChange = () => {28 const cur = localStorage.getItem(THEME_KEY);29 if (cur !== 'light' && cur !== 'dark') apply('system');30 };31 m.addEventListener('change', onChange);32 return () => m.removeEventListener('change', onChange);33 }, []);34 const set = (p: ThemePref) => {35 setPref(p);36 if (p === 'system') localStorage.removeItem(THEME_KEY);37 else localStorage.setItem(THEME_KEY, p);38 apply(p);39 };40 return [pref, set];41}4243/** Three-state toggle (system → light → dark). Touch target 44 px. */44export function ThemeToggle({ className }: { className?: string }) {45 const [pref, set] = useTheme();46 const next: ThemePref = pref === 'system' ? 'light' : pref === 'light' ? 'dark' : 'system';47 const Icon = pref === 'system' ? Monitor : pref === 'light' ? Sun : Moon;48 const label = pref === 'system' ? 'Theme: system' : pref === 'light' ? 'Theme: light' : 'Theme: dark';49 return (50 <button type="button" onClick={() => set(next)} className={cn('flex size-11 items-center justify-center rounded-sm text-ink-2 hover:bg-surface-2 hover:text-ink md:size-10', className)} aria-label={`${label} — switch`} title={label}>51 <Icon className="size-[18px]" aria-hidden />52 </button>53 );54}55