Make theme switch a native select/option menu

This commit is contained in:
Haishan 2022-03-06 16:46:05 +08:00
parent 06428daa53
commit d87cc00fc8
8 changed files with 2278 additions and 901 deletions

View file

@ -27,84 +27,85 @@
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "7.16.3", "@babel/runtime": "7.17.2",
"@fontsource/open-sans": "4.5.2", "@fontsource/open-sans": "4.5.5",
"@fontsource/roboto-mono": "4.5.0", "@fontsource/roboto-mono": "4.5.3",
"@hsjs/react-cache": "0.0.0-alpha.aa94237", "@hsjs/react-cache": "0.0.0-alpha.aa94237",
"@reach/tooltip": "0.16.2", "@reach/tooltip": "0.16.2",
"@reach/visually-hidden": "0.16.0",
"chart.js": "2.9.4", "chart.js": "2.9.4",
"clsx": "^1.1.0", "clsx": "^1.1.0",
"core-js": "3.19.1", "core-js": "3.21.1",
"date-fns": "2.25.0", "date-fns": "2.28.0",
"framer-motion": "5.3.0", "framer-motion": "6.2.8",
"history": "5.1.0", "history": "5.3.0",
"i18next": "21.4.2", "i18next": "21.6.13",
"i18next-browser-languagedetector": "6.1.2", "i18next-browser-languagedetector": "6.1.3",
"i18next-http-backend": "1.3.1", "i18next-http-backend": "1.3.2",
"immer": "9.0.6", "immer": "9.0.12",
"invariant": "^2.2.4", "invariant": "^2.2.4",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"memoize-one": "6.0.0", "memoize-one": "6.0.0",
"modern-normalize": "1.1.0", "modern-normalize": "1.1.0",
"prop-types": "^15.5.10", "prop-types": "15.8.1",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",
"react-i18next": "11.14.2", "react-i18next": "11.15.5",
"react-icons": "4.3.1", "react-icons": "4.3.1",
"react-modal": "3.14.4", "react-modal": "3.14.4",
"react-query": "3.32.1", "react-query": "3.34.16",
"react-router": "6.0.2", "react-router": "6.2.2",
"react-router-dom": "6.0.2", "react-router-dom": "6.2.2",
"react-switch": "^6.0.0", "react-switch": "^6.0.0",
"react-table": "7.7.0", "react-table": "7.7.0",
"react-tabs": "3.2.3", "react-tabs": "4.0.1",
"react-tiny-fab": "4.0.4", "react-tiny-fab": "4.0.4",
"react-window": "^1.8.5", "react-window": "^1.8.5",
"recoil": "0.5.2", "recoil": "0.6.1",
"regenerator-runtime": "0.13.9", "regenerator-runtime": "0.13.9",
"reselect": "4.1.2", "reselect": "4.1.5",
"tslib": "2.3.1", "tslib": "2.3.1",
"workbox-core": "6.3.0", "workbox-core": "6.5.1",
"workbox-expiration": "6.3.0", "workbox-expiration": "6.5.1",
"workbox-precaching": "6.3.0", "workbox-precaching": "6.5.1",
"workbox-routing": "6.3.0", "workbox-routing": "6.5.1",
"workbox-strategies": "6.3.0" "workbox-strategies": "6.5.1"
}, },
"devDependencies": { "devDependencies": {
"@types/invariant": "2.2.35", "@types/invariant": "2.2.35",
"@types/jest": "27.0.2", "@types/jest": "27.4.1",
"@types/lodash-es": "4.17.5", "@types/lodash-es": "4.17.6",
"@types/react": "17.0.34", "@types/react": "17.0.39",
"@types/react-dom": "17.0.11", "@types/react-dom": "17.0.13",
"@types/react-modal": "3.13.1", "@types/react-modal": "3.13.1",
"@types/react-tabs": "2.3.3", "@types/react-tabs": "2.3.4",
"@types/react-window": "1.8.5", "@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.3.1", "@typescript-eslint/eslint-plugin": "5.13.0",
"@typescript-eslint/parser": "5.3.1", "@typescript-eslint/parser": "5.13.0",
"@vitejs/plugin-react-refresh": "1.3.6", "@vitejs/plugin-react-refresh": "1.3.6",
"autoprefixer": "10.4.0", "autoprefixer": "10.4.2",
"cssnano": "5.0.10", "cssnano": "5.1.0",
"eslint": "8.2.0", "eslint": "8.10.0",
"eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "^6.0.0", "eslint-config-react-app": "7.0.0",
"eslint-plugin-flowtype": "8.0.3", "eslint-plugin-flowtype": "8.0.3",
"eslint-plugin-import": "2.25.3", "eslint-plugin-import": "2.25.4",
"eslint-plugin-jest": "25.2.4", "eslint-plugin-jest": "26.1.1",
"eslint-plugin-jsx-a11y": "6.5.1", "eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-react": "7.27.0", "eslint-plugin-react": "7.29.3",
"eslint-plugin-react-hooks": "4.3.0", "eslint-plugin-react-hooks": "4.3.0",
"eslint-plugin-simple-import-sort": "^7.0.0", "eslint-plugin-simple-import-sort": "^7.0.0",
"postcss": "8.3.11", "postcss": "8.4.7",
"postcss-custom-media": "^8.0.0", "postcss-custom-media": "^8.0.0",
"postcss-import": "14.0.2", "postcss-import": "14.0.2",
"postcss-simple-vars": "^6.0.3", "postcss-simple-vars": "^6.0.3",
"prettier": "2.4.1", "prettier": "2.5.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sass": "1.43.4", "sass": "1.49.9",
"typescript": "4.4.4", "typescript": "4.6.2",
"vite": "2.6.14", "vite": "2.8.6",
"vite-plugin-pwa": "0.11.3" "vite-plugin-pwa": "0.11.13"
} }
} }

View file

@ -1,28 +0,0 @@
.iconWrapper {
--sz: 40px;
width: var(--sz);
height: var(--sz);
display: flex;
justify-content: center;
align-items: center;
outline: none;
padding: 5px;
color: var(--color-text);
border-radius: 100%;
border: 1px solid transparent;
}
.iconWrapper:hover {
opacity: 0.6;
}
.iconWrapper:focus {
border-color: var(--color-focus-blue);
}
.themeSwitchContainer {
appearance: none;
user-select: none;
background: none;
cursor: pointer;
}

View file

@ -0,0 +1,58 @@
.iconWrapper {
--sz: 40px;
width: var(--sz);
height: var(--sz);
display: flex;
justify-content: center;
align-items: center;
outline: none;
padding: 5px;
color: var(--color-text);
border-radius: 100%;
border: 1px solid transparent;
}
.iconWrapper:hover {
opacity: 0.6;
}
.iconWrapper:focus {
border-color: var(--color-focus-blue);
}
.themeSwitchContainer {
--sz: 40px;
position: relative;
display: flex;
align-items: center;
height: var(--sz);
select {
cursor: pointer;
padding-left: var(--sz);
width: var(--sz);
height: var(--sz);
appearance: none;
outline: none;
border-radius: 100%;
border: 1px solid transparent;
background: var(--color-bg-sidebar);
&:focus {
border-color: var(--color-focus-blue);
}
option {
// this has effect in Firefox
// Chrome and Safari use the native menu
background: var(--color-bg-sidebar);
}
}
.iconWrapper {
pointer-events: none;
display: inline-flex;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
top: 0;
}
}

View file

@ -1,5 +1,4 @@
import Tooltip from '@reach/tooltip'; import Tooltip from '@reach/tooltip';
import cx from 'clsx';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { connect } from 'src/components/StateProvider'; import { connect } from 'src/components/StateProvider';
@ -7,36 +6,18 @@ import { framerMotionResouce } from 'src/misc/motion';
import { getTheme, switchTheme } from 'src/store/app'; import { getTheme, switchTheme } from 'src/store/app';
import { State } from 'src/store/types'; import { State } from 'src/store/types';
import s from './ThemeSwitcher.module.css'; import s from './ThemeSwitcher.module.scss';
export function ThemeSwitcherImpl({ theme, dispatch }) { export function ThemeSwitcherImpl({ theme, dispatch }) {
const { t } = useTranslation(); const { t } = useTranslation();
const switchThemeHooked = React.useCallback(() => {
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(() => { const themeIcon = React.useMemo(() => {
switch (theme) { switch (theme) {
case 'light':
return <MoonA />;
case 'dark': case 'dark':
return <Auto />; return <MoonA />;
case 'auto': case 'auto':
return <Auto />;
case 'light':
return <Sun />; return <Sun />;
default: default:
console.assert(false, 'Unknown theme'); console.assert(false, 'Unknown theme');
@ -44,11 +25,21 @@ export function ThemeSwitcherImpl({ theme, dispatch }) {
} }
}, [theme]); }, [theme]);
const onChange = React.useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => dispatch(switchTheme(e.target.value)),
[dispatch]
);
return ( return (
<Tooltip label={t('theme')} aria-label={'switch to ' + nextThemeName + ' theme'}> <Tooltip label={t('switch_theme')} aria-label={'switch theme'}>
<button className={cx(s.iconWrapper, s.themeSwitchContainer)} onClick={switchThemeHooked}> <div className={s.themeSwitchContainer}>
{themeIcon} <span className={s.iconWrapper}>{themeIcon}</span>
</button> <select onChange={onChange}>
<option value="auto">Auto</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
</Tooltip> </Tooltip>
); );
} }
@ -95,7 +86,7 @@ function Sun() {
strokeLinejoin="round" strokeLinejoin="round"
> >
<circle cx="12" cy="12" r="5"></circle> <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.7 }} animate={{ scale: 1 }} transition={{ duration: 0.5 }}>
<line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></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> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
@ -137,7 +128,7 @@ function Auto() {
transition={{ duration: 0.7 }} transition={{ duration: 0.7 }}
/> />
</clipPath> </clipPath>
<circle cx="12" cy="12" r="6" clip-path="url(#cut-off-bottom)" fill="currentColor" /> <circle cx="12" cy="12" r="6" clipPath="url(#cut-off-bottom)" fill="currentColor" />
</svg> </svg>
); );
} }

View file

@ -27,6 +27,7 @@ export const data = {
Connections: 'Connections', Connections: 'Connections',
Active: 'Active', Active: 'Active',
Closed: 'Closed', Closed: 'Closed',
switch_theme: 'Switch theme',
theme: 'theme', theme: 'theme',
about: 'about', about: 'about',
no_logs: 'No logs yet, hang tight...', no_logs: 'No logs yet, hang tight...',

View file

@ -27,6 +27,7 @@ export const data = {
Connections: '连接', Connections: '连接',
Active: '活动', Active: '活动',
Closed: '已断开', Closed: '已断开',
switch_theme: '切换主题',
theme: '主题', theme: '主题',
about: '关于', about: '关于',
no_logs: '暂无日志...', no_logs: '暂无日志...',

View file

@ -105,24 +105,12 @@ function setTheme(theme: ThemeType = 'dark') {
} }
} }
export function switchTheme() { export function switchTheme(nextTheme = 'auto') {
return (dispatch: DispatchFn, getState: GetStateFn) => { return (dispatch: DispatchFn, getState: GetStateFn) => {
const currentTheme = getTheme(getState()); const currentTheme = getTheme(getState());
let nextTheme: ThemeType = 'auto'; if (currentTheme === nextTheme) return;
switch (currentTheme) {
case 'light':
nextTheme = 'dark';
break;
case 'dark':
nextTheme = 'auto';
break;
case 'auto':
nextTheme = 'light';
break;
}
// side effect // side effect
setTheme(nextTheme); setTheme(nextTheme as ThemeType);
dispatch('storeSwitchTheme', (s) => { dispatch('storeSwitchTheme', (s) => {
s.app.theme = nextTheme; s.app.theme = nextTheme;
}); });

2933
yarn.lock

File diff suppressed because it is too large Load diff