-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathuseTheme.mjs
More file actions
112 lines (96 loc) · 3.31 KB
/
useTheme.mjs
File metadata and controls
112 lines (96 loc) · 3.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { useState, useEffect, useCallback } from 'react';
const THEME_STORAGE_KEY = 'theme';
const THEME_PREFERENCES = new Set(['system', 'light', 'dark']);
/**
* Sets up theme toggle button and system preference listener
*/
const getSystemTheme = () =>
matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
/**
* Retrieves the stored theme preference from local storage.
*
* @returns {'system'|'light'|'dark'|null} The stored theme preference or null if not found.
*/
const getStoredThemePreference = () => {
try {
const storedTheme = localStorage.getItem(THEME_STORAGE_KEY);
return THEME_PREFERENCES.has(storedTheme) ? storedTheme : null;
} catch {
return null;
}
};
/**
* Stores the theme preference in local storage.
* If storage is unavailable, it fails silently, allowing the application to continue functioning with an in-memory preference.
*
* @param {'system'|'light'|'dark'} themePreference - The theme preference to store.
*/
const setStoredThemePreference = themePreference => {
try {
localStorage.setItem(THEME_STORAGE_KEY, themePreference);
} catch {
// Ignore storage failures and keep non-persistent in-memory preference.
}
};
/**
* Applies a theme preference to the document.
*
* The persisted preference can be 'system', but the applied document theme is
* always resolved to either 'light' or 'dark'.
*
* @param {'system'|'light'|'dark'} themePreference - Theme preference.
*/
const applyThemePreference = themePreference => {
const resolvedTheme =
themePreference === 'system' ? getSystemTheme() : themePreference;
document.documentElement.setAttribute('data-theme', resolvedTheme);
document.documentElement.style.colorScheme = resolvedTheme;
};
/**
* A React hook for managing the application's theme preference.
*/
export const useTheme = () => {
const [themePreference, setThemePreferenceState] = useState('system');
useEffect(() => {
// Use persisted preference if available, otherwise default to system.
const initialPreference = getStoredThemePreference() || 'system';
applyThemePreference(initialPreference);
setThemePreferenceState(initialPreference);
}, []);
/**
* Keep the resolved document theme in sync with system changes
* whenever the preference is set to 'system'.
*/
useEffect(() => {
if (themePreference !== 'system') {
return;
}
const mediaQueryList = matchMedia('(prefers-color-scheme: dark)');
/**
*
*/
const handleSystemThemeChange = () => applyThemePreference('system');
if ('addEventListener' in mediaQueryList) {
mediaQueryList.addEventListener('change', handleSystemThemeChange);
return () => {
mediaQueryList.removeEventListener('change', handleSystemThemeChange);
};
}
mediaQueryList.addListener(handleSystemThemeChange);
return () => {
mediaQueryList.removeListener(handleSystemThemeChange);
};
}, [themePreference]);
/**
* Updates the theme preference and applies it immediately.
*/
const setThemePreference = useCallback(nextPreference => {
if (!THEME_PREFERENCES.has(nextPreference)) {
return;
}
setThemePreferenceState(nextPreference);
setStoredThemePreference(nextPreference);
applyThemePreference(nextPreference);
}, []);
return [themePreference, setThemePreference];
};