summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLASER-Yi <[email protected]>2021-12-11 18:45:19 +0800
committerHaishan <[email protected]>2022-03-06 16:47:40 +0800
commit06428daa53029dca82d63cbf3364b3f74fce67d5 (patch)
tree18f2a8b0ca0481f86d9c9689973d81c6f7b26e8d /src
parent3e87d8b52e62c6f8da48176c81267bacd36e3965 (diff)
Add support of automatic switch color scheme
Diffstat (limited to 'src')
-rw-r--r--src/components/Root.scss14
-rw-r--r--src/components/shared/ThemeSwitcher.tsx81
-rw-r--r--src/store/app.ts28
3 files changed, 95 insertions, 28 deletions
diff --git a/src/components/Root.scss b/src/components/Root.scss
index 0df5819..4ae7d5f 100644
--- a/src/components/Root.scss
+++ b/src/components/Root.scss
@@ -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;
diff --git a/src/components/shared/ThemeSwitcher.tsx b/src/components/shared/ThemeSwitcher.tsx
index fba5b0b..90990b6 100644
--- a/src/components/shared/ThemeSwitcher.tsx
+++ b/src/components/shared/ThemeSwitcher.tsx
@@ -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);
diff --git a/src/store/app.ts b/src/store/app.ts
index ea14e84..2ccd960 100644
--- a/src/store/app.ts
+++ b/src/store/app.ts
@@ -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);