at main 2.0 kB view raw
1import { useEffect, useState } from 'react'; 2import * as styles from './ThemeToggle.styles'; 3import { 4 ARIA_LABEL, 5 ICON_VIEWBOX, 6 MEDIA_QUERY_DARK, 7 MOON_ICON_PATH, 8 STROKE_WIDTH, 9 SUN_ICON_PATH, 10 THEME_DARK, 11 THEME_LIGHT, 12 THEME_STORAGE_KEY, 13} from './ThemeToggle.constants'; 14 15export default function ThemeToggle() { 16 const [isDark, setIsDark] = useState(false); 17 18 // Initialize theme from localStorage or system preference 19 useEffect(() => { 20 const savedTheme = localStorage.getItem(THEME_STORAGE_KEY); 21 const prefersDark = window.matchMedia(MEDIA_QUERY_DARK).matches; 22 23 if (savedTheme === THEME_DARK || (!savedTheme && prefersDark)) { 24 setIsDark(true); 25 document.documentElement.classList.add(THEME_DARK); 26 } else { 27 setIsDark(false); 28 document.documentElement.classList.remove(THEME_DARK); 29 } 30 }, []); 31 32 const toggleTheme = () => { 33 const newIsDark = !isDark; 34 setIsDark(newIsDark); 35 36 if (newIsDark) { 37 document.documentElement.classList.add(THEME_DARK); 38 localStorage.setItem(THEME_STORAGE_KEY, THEME_DARK); 39 } else { 40 document.documentElement.classList.remove(THEME_DARK); 41 localStorage.setItem(THEME_STORAGE_KEY, THEME_LIGHT); 42 } 43 }; 44 45 return ( 46 <button onClick={toggleTheme} className={styles.button} aria-label={ARIA_LABEL}> 47 {isDark ? ( 48 <svg 49 className={styles.icon} 50 fill="none" 51 stroke="currentColor" 52 viewBox={ICON_VIEWBOX} 53 xmlns="http://www.w3.org/2000/svg" 54 > 55 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={STROKE_WIDTH} d={SUN_ICON_PATH} /> 56 </svg> 57 ) : ( 58 <svg 59 className={styles.icon} 60 fill="none" 61 stroke="currentColor" 62 viewBox={ICON_VIEWBOX} 63 xmlns="http://www.w3.org/2000/svg" 64 > 65 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={STROKE_WIDTH} d={MOON_ICON_PATH} /> 66 </svg> 67 )} 68 </button> 69 ); 70}