My personal website
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}