2022-06-03 09:32:44 +00:00
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
2022-02-20 11:51:55 +00:00
|
|
|
|
2022-05-21 07:35:16 +00:00
|
|
|
import { createGlobalHook } from './create-global-hook';
|
|
|
|
|
|
|
|
export enum ThemeEnum {
|
2022-03-12 03:27:56 +00:00
|
|
|
'dark' = 'dark',
|
|
|
|
'light' = 'light',
|
2022-06-03 09:32:44 +00:00
|
|
|
'system' = 'system',
|
2022-02-20 11:51:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-03 09:32:44 +00:00
|
|
|
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');
|
2022-03-23 10:08:07 +00:00
|
|
|
}
|
2022-06-03 09:32:44 +00:00
|
|
|
}
|
|
|
|
matchMode(mql);
|
|
|
|
}
|
2022-02-20 11:51:55 +00:00
|
|
|
|
2022-06-03 09:32:44 +00:00
|
|
|
const useThemeHook = () => {
|
|
|
|
const $remove = useRef<() => void>();
|
|
|
|
const [theme, setTheme] = useState(ThemeEnum.system);
|
|
|
|
const [userPrefer, setUserPrefer] = useState(ThemeEnum.system);
|
|
|
|
|
|
|
|
const followSystem = useCallback(() => {
|
2022-03-12 03:27:56 +00:00
|
|
|
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
2022-02-20 11:51:55 +00:00
|
|
|
|
|
|
|
function matchMode(e) {
|
|
|
|
if (e.matches) {
|
2022-05-21 07:35:16 +00:00
|
|
|
setTheme(ThemeEnum.dark);
|
2022-02-20 11:51:55 +00:00
|
|
|
} else {
|
2022-05-21 07:35:16 +00:00
|
|
|
setTheme(ThemeEnum.light);
|
2022-02-20 11:51:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
matchMode(mql);
|
2022-03-12 03:27:56 +00:00
|
|
|
mql.addEventListener('change', matchMode);
|
2022-05-21 07:35:16 +00:00
|
|
|
|
2022-06-03 09:32:44 +00:00
|
|
|
const remove = () => {
|
2022-05-21 07:35:16 +00:00
|
|
|
mql.removeEventListener('change', matchMode);
|
|
|
|
};
|
2022-06-03 09:32:44 +00:00
|
|
|
|
|
|
|
$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
|
2022-02-20 11:51:55 +00:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
return {
|
2022-06-03 09:32:44 +00:00
|
|
|
userPrefer,
|
2022-02-20 11:51:55 +00:00
|
|
|
theme,
|
|
|
|
toggle,
|
|
|
|
};
|
|
|
|
};
|
2022-05-21 07:35:16 +00:00
|
|
|
|
2022-06-03 09:32:44 +00:00
|
|
|
export const Theme =
|
|
|
|
createGlobalHook<{ userPrefer: ThemeEnum; theme: ThemeEnum; toggle: (nextTheme: ThemeEnum) => void }>(useThemeHook);
|