22
33import { faDesktop , faMoon , faSun } from "@fortawesome/free-solid-svg-icons"
44import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
5+ import { DARK_COLOR_MODE_CLASS , LIGHT_COLOR_MODE_CLASS } from "@vivshaw/basalt"
56import { useEffect , useState } from "react"
67
7- import { ColorMode , useColorMode } from "./ColorModeContext "
8+ import { COLOR_MODE_STORAGE_KEY } from "#data "
89import {
910 button ,
1011 buttonActive ,
@@ -15,6 +16,8 @@ import {
1516 indicatorSystem ,
1617} from "./ThemeSwitcher.css"
1718
19+ type ColorMode = "light" | "dark" | "system"
20+
1821const themes : { key : ColorMode ; icon : typeof faDesktop ; label : string } [ ] = [
1922 { key : "system" , icon : faDesktop , label : "System theme" } ,
2023 { key : "light" , icon : faSun , label : "Light theme" } ,
@@ -27,16 +30,50 @@ const indicatorPositions: Record<ColorMode, string> = {
2730 dark : indicatorDark ,
2831}
2932
30- export function ThemeSwitcher ( ) {
31- const { colorMode, setColorMode } = useColorMode ( )
32- const [ mounted , setMounted ] = useState ( false )
33+ /**
34+ * reads the user's color mode preference from localStorage.
35+ */
36+ function getStoredColorMode ( ) : ColorMode {
37+ try {
38+ const stored = localStorage . getItem ( COLOR_MODE_STORAGE_KEY )
39+ if ( stored === "light" || stored === "dark" || stored === "system" ) {
40+ return stored
41+ }
42+ } catch {
43+ // localStorage unavailable
44+ }
45+ return "system"
46+ }
47+
48+ /**
49+ * applies a color mode by updating DOM classes and persisting to localStorage.
50+ */
51+ function applyStoredColorMode ( mode : ColorMode ) {
52+ document . documentElement . classList . remove (
53+ DARK_COLOR_MODE_CLASS ,
54+ LIGHT_COLOR_MODE_CLASS ,
55+ )
3356
34- useEffect ( ( ) => {
35- setMounted ( true )
36- } , [ ] )
57+ if ( mode === "dark" ) {
58+ document . documentElement . classList . add ( DARK_COLOR_MODE_CLASS )
59+ } else if ( mode === "light" ) {
60+ document . documentElement . classList . add ( LIGHT_COLOR_MODE_CLASS )
61+ }
62+ // "system" means no explicit class - CSS handles via prefers-color-scheme
63+
64+ try {
65+ localStorage . setItem ( COLOR_MODE_STORAGE_KEY , mode )
66+ } catch {
67+ // localStorage unavailable
68+ }
69+ }
70+
71+ export function ThemeSwitcher ( ) {
72+ const [ colorMode , setColorMode ] = useState < ColorMode > ( getStoredColorMode ( ) )
3773
38- if ( ! mounted ) {
39- return null
74+ function handleSetColorMode ( mode : ColorMode ) {
75+ setColorMode ( mode )
76+ applyStoredColorMode ( mode )
4077 }
4178
4279 return (
@@ -49,7 +86,7 @@ export function ThemeSwitcher() {
4986 aria-label = { label }
5087 aria-pressed = { colorMode === key }
5188 className = { `${ button } ${ colorMode === key ? buttonActive : "" } ` }
52- onClick = { ( ) => setColorMode ( key ) }
89+ onClick = { ( ) => handleSetColorMode ( key ) }
5390 >
5491 < FontAwesomeIcon icon = { icon } size = "sm" />
5592 </ button >
0 commit comments