forked from jkulton/gistdoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paththeme-toggle.tsx
90 lines (83 loc) · 3.04 KB
/
theme-toggle.tsx
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
import { useCallback, useEffect, useRef, useState } from "react";
import Moon from "./moon";
import Sun from "./sun";
const resolveColorScheme = (isDark) => (isDark ? "dark" : "light");
export default function ThemeToggle() {
// This is required to prevent the animation from playing on
// initial mount as well as update the aria-label.
const changedRef = useRef(false);
const [colorScheme, setColorScheme] = useState<"dark" | "light" | null>(null);
const handleChange = useCallback(
(ev) => {
const colorScheme = resolveColorScheme(ev.target.checked);
if (!changedRef.current) {
changedRef.current = true;
}
setColorScheme(colorScheme);
// Notify the document so it will update the theme.
// Refer to _document.js for implementation.
window.dispatchEvent(
new CustomEvent("colorSchemeChange", {
detail: { colorScheme, persist: true },
})
);
},
[setColorScheme]
);
useEffect(() => {
const query = window.matchMedia("(prefers-color-scheme: dark)");
const listener = ({ matches }) => {
setColorScheme(resolveColorScheme(matches));
};
query.addEventListener("change", listener);
// Sets the initial color scheme based on user's preference.
// Doing this here so that it's ran on client side only.
setColorScheme(
resolveColorScheme(document.documentElement.classList.contains("dark"))
);
return () => {
query.removeEventListener("change", listener);
};
}, [setColorScheme]);
const hasThemeChanged = changedRef.current;
return (
<div>
<input
type="checkbox"
title="Toggle between light & dark modes"
// Screen readers should announce changes to aria-label
// It's set to auto for the first time since the user didn't
// actually change it and we set the theme automatically based
// on preference.
aria-label={(hasThemeChanged && colorScheme) || "auto"}
aria-live="polite"
id="theme-toggle"
className={`sr-only peer`}
onChange={handleChange}
data-changed={hasThemeChanged}
checked={colorScheme === "dark"}
/>
<div
className={
"overflow-hidden rounded-full w-8 h-8 border-2 isolate " +
"dark:border-gray-200 border-slate-300 bg-gray-50/10 " +
"peer-focus-visible:outline-2 peer-focus-visible:outline-white " +
"peer-focus-visible:outline peer-focus-visible:border-sky-600"
}
>
<label
htmlFor="theme-toggle"
className={
'[[aria-label="light"]~*_&]:motion-safe:animate-rotate-0-180 ' +
'[[aria-label="dark"]~*_&]:motion-safe:animate-rotate-180-360 ' +
"block h-full w-[200%] whitespace-nowrap rotate-180 origin-center " +
"select-none dark:rotate-0"
}
>
<Moon className="h-full p-1.5 inline-block" aria-hidden="true" />
<Sun className="h-full p-1.5 inline-block" aria-hidden="true" />
</label>
</div>
</div>
);
}