Skip to content

Commit 1135f6f

Browse files
committed
Better theme management
1 parent 63d2af2 commit 1135f6f

1 file changed

Lines changed: 29 additions & 19 deletions

File tree

website/src/hooks/useTheme.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
1-
import { useState, useEffect } from "react";
1+
import { useEffect, useState } from "react";
22

3-
type Theme = "light" | "dark" | "system";
4-
type ResolvedTheme = "light" | "dark";
3+
type StoredTheme = "light" | "dark";
4+
type ResolvedTheme = StoredTheme;
55

66
const getSystemTheme = () =>
77
matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
88

9-
export function useTheme() {
10-
const [theme, setTheme] = useState<Theme>(
11-
() => (localStorage.getItem("theme") as Theme) ?? "system"
12-
);
13-
const [systemTheme, setSystemTheme] = useState<ResolvedTheme>(() => getSystemTheme());
9+
const readStoredTheme = (): StoredTheme | null => {
10+
const theme = localStorage.getItem("theme");
11+
return theme === "light" || theme === "dark" ? theme : null;
12+
};
1413

15-
const resolved = theme === "system" ? systemTheme : theme;
14+
export function useTheme() {
15+
const initialSystemTheme = getSystemTheme();
16+
const [systemTheme, setSystemTheme] = useState<ResolvedTheme>(initialSystemTheme);
17+
const [override, setOverride] = useState<StoredTheme | null>(() => {
18+
const stored = readStoredTheme();
19+
return stored === initialSystemTheme ? null : stored;
20+
});
21+
const resolved = override ?? systemTheme;
1622

1723
useEffect(() => {
1824
const mq = matchMedia("(prefers-color-scheme: dark)");
19-
const onChange = (e: MediaQueryListEvent) => setSystemTheme(e.matches ? "dark" : "light");
20-
setSystemTheme(mq.matches ? "dark" : "light");
25+
const onChange = (e: MediaQueryListEvent) => {
26+
const nextSystemTheme = e.matches ? "dark" : "light";
27+
setSystemTheme(nextSystemTheme);
28+
setOverride(current => current === nextSystemTheme ? null : current);
29+
};
2130
mq.addEventListener("change", onChange);
2231
return () => mq.removeEventListener("change", onChange);
2332
}, []);
2433

2534
useEffect(() => {
26-
const root = document.documentElement;
27-
root.classList.toggle("dark", resolved === "dark");
28-
29-
if (theme === "system") localStorage.removeItem("theme");
30-
else localStorage.setItem("theme", theme);
31-
}, [theme, resolved]);
32-
33-
const toggle = () => setTheme(resolved === "dark" ? "light" : "dark");
35+
document.documentElement.classList.toggle("dark", resolved === "dark");
36+
if (override) localStorage.setItem("theme", override);
37+
else localStorage.removeItem("theme");
38+
}, [override, resolved]);
39+
40+
const toggle = () => {
41+
const next = resolved === "dark" ? "light" : "dark";
42+
setOverride(next === systemTheme ? null : next);
43+
};
3444

3545
return { resolved, toggle } as const;
3646
}

0 commit comments

Comments
 (0)