/ mission-control / src / components / ThemeToggle.tsx
ThemeToggle.tsx
 1  import { useEffect, useState } from 'react'
 2  
 3  type ThemeMode = 'light' | 'dark' | 'auto'
 4  
 5  function getInitialMode(): ThemeMode {
 6    if (typeof window === 'undefined') {
 7      return 'auto'
 8    }
 9  
10    const stored = window.localStorage.getItem('theme')
11    if (stored === 'light' || stored === 'dark' || stored === 'auto') {
12      return stored
13    }
14  
15    return 'auto'
16  }
17  
18  function applyThemeMode(mode: ThemeMode) {
19    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
20    const resolved = mode === 'auto' ? (prefersDark ? 'dark' : 'light') : mode
21  
22    document.documentElement.classList.remove('light', 'dark')
23    document.documentElement.classList.add(resolved)
24  
25    if (mode === 'auto') {
26      document.documentElement.removeAttribute('data-theme')
27    } else {
28      document.documentElement.setAttribute('data-theme', mode)
29    }
30  
31    document.documentElement.style.colorScheme = resolved
32  }
33  
34  export default function ThemeToggle() {
35    const [mode, setMode] = useState<ThemeMode>('auto')
36  
37    useEffect(() => {
38      const initialMode = getInitialMode()
39      setMode(initialMode)
40      applyThemeMode(initialMode)
41    }, [])
42  
43    useEffect(() => {
44      if (mode !== 'auto') {
45        return
46      }
47  
48      const media = window.matchMedia('(prefers-color-scheme: dark)')
49      const onChange = () => applyThemeMode('auto')
50  
51      media.addEventListener('change', onChange)
52      return () => {
53        media.removeEventListener('change', onChange)
54      }
55    }, [mode])
56  
57    function toggleMode() {
58      const nextMode: ThemeMode =
59        mode === 'light' ? 'dark' : mode === 'dark' ? 'auto' : 'light'
60      setMode(nextMode)
61      applyThemeMode(nextMode)
62      window.localStorage.setItem('theme', nextMode)
63    }
64  
65    const label =
66      mode === 'auto'
67        ? 'Theme mode: auto (system). Click to switch to light mode.'
68        : `Theme mode: ${mode}. Click to switch mode.`
69  
70    return (
71      <button
72        type="button"
73        onClick={toggleMode}
74        aria-label={label}
75        title={label}
76        className="rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm font-semibold text-[var(--sea-ink)] shadow-[0_8px_22px_rgba(30,90,72,0.08)] transition hover:-translate-y-0.5"
77      >
78        {mode === 'auto' ? 'Auto' : mode === 'dark' ? 'Dark' : 'Light'}
79      </button>
80    )
81  }