think/packages/client/src/hooks/use-theme.tsx

97 lines
2.1 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { createGlobalHook } from './create-global-hook';
export enum ThemeEnum {
'dark' = 'dark',
'light' = 'light',
'system' = 'system',
}
function syncSystemTheme() {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
function matchMode(e) {
if (e.matches) {
document.body.setAttribute('theme-mode', 'dark');
} else {
document.body.setAttribute('theme-mode', 'light');
}
}
matchMode(mql);
}
const useThemeHook = () => {
const $remove = useRef<() => void>();
const [theme, setTheme] = useState(ThemeEnum.system);
const [userPrefer, setUserPrefer] = useState(ThemeEnum.system);
const followSystem = useCallback(() => {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
function matchMode(e) {
if (e.matches) {
setTheme(ThemeEnum.dark);
} else {
setTheme(ThemeEnum.light);
}
}
matchMode(mql);
mql.addEventListener('change', matchMode);
const remove = () => {
mql.removeEventListener('change', matchMode);
};
$remove.current = remove;
}, []);
const toggle = useCallback(
(nextTheme: ThemeEnum) => {
setUserPrefer(nextTheme);
setTheme(nextTheme);
if (nextTheme !== ThemeEnum.system) {
$remove.current && $remove.current();
} else {
followSystem();
}
},
[followSystem]
);
useEffect(() => {
const body = document.body;
switch (theme) {
case ThemeEnum.light:
body.setAttribute('theme-mode', 'light');
return;
case ThemeEnum.dark:
body.setAttribute('theme-mode', 'dark');
return;
case ThemeEnum.system:
default:
syncSystemTheme();
return;
}
}, [theme]);
useEffect(() => {
if (theme === ThemeEnum.system) {
followSystem();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return {
userPrefer,
theme,
toggle,
};
};
export const Theme =
createGlobalHook<{ userPrefer: ThemeEnum; theme: ThemeEnum; toggle: (nextTheme: ThemeEnum) => void }>(useThemeHook);