Add support of automatic switch color scheme
This commit is contained in:
parent
3e87d8b52e
commit
06428daa53
3 changed files with 95 additions and 28 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue