client: improve theme

support set dark, light and follow system
This commit is contained in:
fantasticit 2022-06-03 17:32:44 +08:00
parent 4891c278db
commit ef8d3924b9
2 changed files with 104 additions and 33 deletions

View File

@ -1,17 +1,47 @@
import { IconMoon, IconSun } from '@douyinfe/semi-icons'; import { IconDesktop, IconMoon, IconSun } from '@douyinfe/semi-icons';
import { Button } from '@douyinfe/semi-ui'; import { Button, Dropdown } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip'; import { Theme as ThemeState, ThemeEnum } from 'hooks/use-theme';
import { Theme as ThemeState } from 'hooks/use-theme'; import React, { useCallback } from 'react';
import React from 'react';
export const Theme = () => { export const Theme = () => {
const { theme, toggle } = ThemeState.useHook(); const { userPrefer, toggle } = ThemeState.useHook();
const Icon = theme === 'dark' ? IconSun : IconMoon; const Icon = userPrefer === 'dark' ? IconSun : IconMoon;
const text = theme === 'dark' ? '切换到亮色模式' : '切换到深色模式';
const setLight = useCallback(() => {
toggle(ThemeEnum.light);
}, [toggle]);
const setDark = useCallback(() => {
toggle(ThemeEnum.dark);
}, [toggle]);
const setSystem = useCallback(() => {
toggle(ThemeEnum.system);
}, [toggle]);
return ( return (
<Tooltip content={text} position="bottom"> <Dropdown
<Button onClick={toggle} icon={<Icon style={{ fontSize: 20 }} />} theme="borderless"></Button> position="bottomRight"
</Tooltip> trigger="click"
showTick
render={
<Dropdown.Menu>
<Dropdown.Item active={userPrefer === ThemeEnum.light} onClick={setLight}>
<IconSun />
</Dropdown.Item>
<Dropdown.Item active={userPrefer === ThemeEnum.dark} onClick={setDark}>
<IconMoon />
</Dropdown.Item>
<Dropdown.Item active={userPrefer === ThemeEnum.system} onClick={setSystem}>
<IconDesktop />
</Dropdown.Item>
</Dropdown.Menu>
}
>
<Button icon={<Icon style={{ fontSize: 20 }} />} theme="borderless"></Button>
</Dropdown>
); );
}; };

View File

@ -1,33 +1,31 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { createGlobalHook } from './create-global-hook'; import { createGlobalHook } from './create-global-hook';
export enum ThemeEnum { export enum ThemeEnum {
'dark' = 'dark', 'dark' = 'dark',
'light' = 'light', '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 useThemeHook = () => {
const [theme, setTheme] = useState(ThemeEnum.light); const $remove = useRef<() => void>();
const [theme, setTheme] = useState(ThemeEnum.system);
const [userPrefer, setUserPrefer] = useState(ThemeEnum.system);
const toggle = useCallback(() => { const followSystem = useCallback(() => {
const nextTheme = theme === 'dark' ? ThemeEnum.light : ThemeEnum.dark;
setTheme(nextTheme);
}, [theme]);
useEffect(() => {
const body = document.body;
if (theme === 'dark') {
body.setAttribute('theme-mode', 'dark');
return;
}
if (theme === 'light') {
body.setAttribute('theme-mode', 'light');
return;
}
}, [theme]);
useEffect(() => {
const mql = window.matchMedia('(prefers-color-scheme: dark)'); const mql = window.matchMedia('(prefers-color-scheme: dark)');
function matchMode(e) { function matchMode(e) {
@ -41,15 +39,58 @@ const useThemeHook = () => {
matchMode(mql); matchMode(mql);
mql.addEventListener('change', matchMode); mql.addEventListener('change', matchMode);
return () => { const remove = () => {
mql.removeEventListener('change', matchMode); 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 { return {
userPrefer,
theme, theme,
toggle, toggle,
}; };
}; };
export const Theme = createGlobalHook<{ theme: ThemeEnum; toggle: () => void }>(useThemeHook); export const Theme =
createGlobalHook<{ userPrefer: ThemeEnum; theme: ThemeEnum; toggle: (nextTheme: ThemeEnum) => void }>(useThemeHook);