Add support of automatic switch color scheme

This commit is contained in:
LASER-Yi 2021-12-11 18:45:19 +08:00 committed by Haishan
parent 3e87d8b52e
commit 06428daa53
3 changed files with 95 additions and 28 deletions

View file

@ -124,10 +124,16 @@ body {
--select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20);
}
// we don't have a "system" or "auto" mode now
// it's just not make sense to have these yet
// @media (prefers-color-scheme: dark) {}
// @media (prefers-color-scheme: light) {}
:root[data-theme='auto'] {
@media (prefers-color-scheme: dark) {
@include dark;
color-scheme: dark;
}
@media (prefers-color-scheme: light) {
@include light;
color-scheme: light;
}
}
:root[data-theme='dark'] {
@include dark;

View file

@ -16,18 +16,38 @@ export function ThemeSwitcherImpl({ theme, dispatch }) {
dispatch(switchTheme());
}, [dispatch]);
const nextThemeName = React.useMemo(() => {
switch (theme) {
case 'light':
return 'dark';
case 'dark':
return 'auto';
case 'auto':
return 'light';
default:
console.assert(false, 'Unknown theme');
return 'unknown';
}
}, [theme]);
const themeIcon = React.useMemo(() => {
switch (theme) {
case 'light':
return <MoonA />;
case 'dark':
return <Auto />;
case 'auto':
return <Sun />;
default:
console.assert(false, 'Unknown theme');
return <MoonA />;
}
}, [theme]);
return (
<Tooltip
label={t('theme')}
aria-label={
'switch to ' + (theme === 'light' ? 'dark' : 'light') + ' theme'
}
>
<button
className={cx(s.iconWrapper, s.themeSwitchContainer)}
onClick={switchThemeHooked}
>
{theme === 'light' ? <MoonA /> : <Sun />}
<Tooltip label={t('theme')} aria-label={'switch to ' + nextThemeName + ' theme'}>
<button className={cx(s.iconWrapper, s.themeSwitchContainer)} onClick={switchThemeHooked}>
{themeIcon}
</button>
</Tooltip>
);
@ -75,11 +95,7 @@ function Sun() {
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="5"></circle>
<motion.g
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ duration: 0.7 }}
>
<motion.g initial={{ scale: 0.8 }} animate={{ scale: 1 }} transition={{ duration: 0.7 }}>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
@ -93,5 +109,38 @@ function Sun() {
);
}
function Auto() {
const module = framerMotionResouce.read();
const motion = module.motion;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="11" />
<clipPath id="cut-off-bottom">
<motion.rect
x="12"
y="0"
width="12"
height="24"
initial={{ rotate: -30 }}
animate={{ rotate: 0 }}
transition={{ duration: 0.7 }}
/>
</clipPath>
<circle cx="12" cy="12" r="6" clip-path="url(#cut-off-bottom)" fill="currentColor" />
</svg>
);
}
const mapState = (s: State) => ({ theme: getTheme(s) });
export const ThemeSwitcher = connect(mapState)(ThemeSwitcherImpl);

View file

@ -94,25 +94,37 @@ export function updateClashAPIConfig({ baseURL, secret }) {
}
const rootEl = document.querySelector('html');
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
function setTheme(theme = 'dark') {
if (theme === 'dark') {
type ThemeType = 'dark' | 'light' | 'auto';
function setTheme(theme: ThemeType = 'dark') {
if (theme === 'auto') {
rootEl.setAttribute('data-theme', 'auto');
} else if (theme === 'dark') {
rootEl.setAttribute('data-theme', 'dark');
themeColorMeta.setAttribute('content', '#202020');
} else {
rootEl.setAttribute('data-theme', 'light');
themeColorMeta.setAttribute('content', '#f7f7f7');
}
}
export function switchTheme() {
return (dispatch: DispatchFn, getState: GetStateFn) => {
const currentTheme = getTheme(getState());
const theme = currentTheme === 'light' ? 'dark' : 'light';
let nextTheme: ThemeType = 'auto';
switch (currentTheme) {
case 'light':
nextTheme = 'dark';
break;
case 'dark':
nextTheme = 'auto';
break;
case 'auto':
nextTheme = 'light';
break;
}
// side effect
setTheme(theme);
setTheme(nextTheme);
dispatch('storeSwitchTheme', (s) => {
s.app.theme = theme;
s.app.theme = nextTheme;
});
// side effect
saveState(getState().app);