-
Notifications
You must be signed in to change notification settings - Fork 167
/
_dark_mode_js.tmpl.html
118 lines (99 loc) · 4.48 KB
/
_dark_mode_js.tmpl.html
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
113
114
115
116
117
118
<script>
/**
* Implements a tri-state theme switch (i.e. light/dark mode) allowing an
* explicit setting of one or the other, or inheriting the operating system's
* default and styling accordingly.
*
* The reason this is in a template file as opposed to a separate JavaScript is
* so that it's parsed inline with the HTML body and given a chance to run
* before the page is rendered, thereby avoiding the common pitfall of briefly
* "flashing" the wrong theme when the page initially loads. Under this
* framework, there's one initial call to `setThemeFromLocalStorage()` inline,
* so the theme always appears correct on first render.
*/
const LOCAL_STORAGE_KEY_THEME = 'theme';
const THEME_VALUE_AUTO = 'auto';
const THEME_VALUE_DARK = 'dark';
const THEME_VALUE_LIGHT = 'light';
const THEME_CLASS_DARK = 'dark'; // activates dark mode styling
const THEME_CLASS_DARK_OVERRIDE = 'dark_override'; // slides toggle to dark mode
const THEME_CLASS_LIGHT = 'light'; // here for consistency, but has no real effect
const THEME_CLASS_LIGHT_OVERRIDE = 'light_override'; // slides toggle to light mode
const THEME_CLASS_ALL = Object.freeze([
THEME_CLASS_DARK,
THEME_CLASS_DARK_OVERRIDE,
THEME_CLASS_LIGHT,
THEME_CLASS_LIGHT_OVERRIDE,
])
function setDocumentClasses(...classes) {
THEME_CLASS_ALL.forEach((themeClass) => {
if (classes.includes(themeClass)) {
document.documentElement.classList.add(themeClass)
} else {
document.documentElement.classList.remove(themeClass)
}
})
}
// Sets light or dark mode based on a preference from local storage, or if none
// is set there, sets based on preference from `prefers-color-scheme` CSS.
function setThemeFromLocalStorageOrMediaPreference() {
const theme = localStorage.getItem(LOCAL_STORAGE_KEY_THEME) || THEME_VALUE_AUTO
switch (theme) {
case THEME_VALUE_AUTO:
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
setDocumentClasses(THEME_CLASS_DARK)
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
setDocumentClasses(THEME_CLASS_LIGHT)
}
break
case THEME_VALUE_DARK:
setDocumentClasses(THEME_CLASS_DARK, THEME_CLASS_DARK_OVERRIDE)
break
case THEME_VALUE_LIGHT:
setDocumentClasses(THEME_CLASS_LIGHT, THEME_CLASS_LIGHT_OVERRIDE)
break
}
console.log(`theme is: ${theme}`)
document.querySelectorAll(`.theme_toggle input[value='${theme}']`).forEach((toggle) => {
console.log(`set toggle`)
toggle.checked = true;
})
}
// See comment above, but this call must be made inline to avoid briefly
// flashing the wrong theme on initial page load.
setThemeFromLocalStorageOrMediaPreference()
// Runs on initial page load. Add change listeners to light/dark toggles that
// set a local storage key and trigger a theme change.
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.theme_toggle input').forEach((toggle) => {
toggle.addEventListener('change', (e) => {
if (e.target.checked) {
if (e.target.value == THEME_VALUE_AUTO) {
localStorage.removeItem(LOCAL_STORAGE_KEY_THEME)
} else {
localStorage.setItem(LOCAL_STORAGE_KEY_THEME, e.target.value)
}
}
setThemeFromLocalStorageOrMediaPreference()
}, false)
})
// We already our theme "early" to avoid a flash that would occur if we only
// set it here on content loaded, but set it one more time so that we set
// the theme toggle to the appropriate value. It's not loaded yet when we
// set the theme the first time.
setThemeFromLocalStorageOrMediaPreference()
})
// Listen for local storage changes on our theme key. This allows one tab to be
// notified if the theme is changed in another and update itself accordingly.
window.addEventListener("storage", (e) => {
if (e.key == LOCAL_STORAGE_KEY_THEME) {
setThemeFromLocalStorageOrMediaPreference()
}
})
// Watch for OS-level changes in preference for light/dark mode. This will
// trigger for example if a user explicitly changes their OS-level light/dark
// configuration, or on sunrise/sunset if they have it set to automatic.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
setThemeFromLocalStorageOrMediaPreference()
})
</script>