Feat(webui): 深色模式添加自动跟随系统,添加格式化配置文件。 (#1109)

* Style(webui): Add format config.

Add prettier config to confirm all developer have same code format style.

* Feat(WebUI): 深色模式行为优化

新增:
1、添加了「自动跟随系统主题」模式,并设置为默认行为,该模式将实时监控系统/浏览器主题,并同步变化;
2、添加了自动切换模式按钮。
修改:
1、修改按钮深色模式、浅色模式显示为对应当前模式;
2、修改默认主题为自动跟随系统。

* Update prettier config.

限定解析器的部分格式,避免影响其他语言的格式化功能。

* Disassemble some components.

1.move theme hooks to /lib folder;
2.disassemble ThemeButton component;
3.refactor ThemeButton related code.
This commit is contained in:
Monine Melolaris authored and GitHub committed 2024-12-09 02:34:38 +00:00
1 parent 5db2691155
commit 4273edc6c7
9 files changed
+229 -54

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "prettier"]
}
+38
View File
@@ -0,0 +1,38 @@
//.prettierignore
README.md
.DS_Store
node_modules
/dist
/tests/e2e/videos/
/tests/e2e/screenshots/
.github
.next
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
.vscode
# Lock File
package-lock.json
yarn.lock
# autoGen
/autoGen
/public/cdn
/public/cdnimages
+49
View File
@@ -0,0 +1,49 @@
# .prettierrc.toml
# 箭头函数只有一个参数的时候可以忽略括号
arrowParens = "avoid"
# 括号内部不要出现空格
bracketSpacing = true
# 行结束符使用 Unix 格式
endOfLine = "lf"
# true: Put > on the last line instead of at a new line
jsxBracketSameLine = false
# 行宽
printWidth = 100
# 换行方式
proseWrap = "preserve"
# 分号
semi = false
# 使用单引号
singleQuote = true
# 缩进
tabWidth = 2
# 使用 tab 缩进
useTabs = false
# 后置逗号,多行对象、数组在最后一行增加逗号
trailingComma = "es5"
# 解析器,仅指定部分文件格式,避免影响其他语言功能
[[overrides]]
files = "*.ts"
[overrides.options]
parser = "typescript"
[[overrides]]
files = "*.tsx"
[overrides.options]
parser = "typescript"
[[overrides]]
files = "*.css"
[overrides.options]
parser = "css"
+6
View File
@@ -0,0 +1,6 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
+11 -51
View File
@@ -1,9 +1,9 @@
"use client";
import "./globals.css";
import styles from "./page.module.css";
import { SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
import { Button, Nav } from "@douyinfe/semi-ui";
import { OnSelectedData } from "@douyinfe/semi-ui/lib/es/navigation";
import { Layout as SeLayout } from "@douyinfe/semi-ui/lib/es/layout";
@@ -12,14 +12,13 @@ import {
IconCustomerSupport,
IconDoubleChevronLeft,
IconDoubleChevronRight,
IconMoon,
IconSemiLogo,
IconStar,
IconSun,
IconVideoListStroked,
IconHome, IconSetting,
} from "@douyinfe/semi-icons";
import Image from "next/image";
import ThemeButton from "./ui/ThemeButton";
import { useSystemTheme, useTheme } from "./lib/utils";
export default function RootLayout({
children,
@@ -36,13 +35,9 @@ export default function RootLayout({
const [openKeys, setOpenKeys] = useState(initOpenKeys);
const [selectedKeys, setSelectedKeys] = useState<any>([pathname.slice(1)]);
const [isCollapsed, setIsCollapsed] = useState(false);
const [mode, setMode] = useState(typeof window !== 'undefined' && localStorage.getItem("mode") || "light");
useEffect(() => {
localStorage.setItem("mode", mode);
if (mode === "dark") {
document.body.setAttribute("theme-mode", "dark");
}
}, [mode]);
const [mode, setMode] = useState((typeof window !== 'undefined' && localStorage.getItem('mode')) || 'auto')
const systemTheme = useSystemTheme()
useTheme(mode,systemTheme)
let navStyle = isCollapsed
? { height: "100%", overflow: "visible" }
: { height: "100%" };
@@ -290,7 +285,9 @@ export default function RootLayout({
/>
</div>
</Nav.Header>
{footer(mode, setMode)}
<Nav.Footer collapseButton={false}>
<ThemeButton mode={mode} setMode={setMode} systemTheme={systemTheme} />
</Nav.Footer>
</Nav>
</Sider>
<SeLayout style={{ height: "100vh" }}>{children}</SeLayout>
@@ -300,44 +297,7 @@ export default function RootLayout({
);
}
function footer(
mode: string,
setMode: {
(value: SetStateAction<string>): void;
(arg0: string): void;
}
) {
const switchMode = () => {
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.removeAttribute("theme-mode");
setMode("light");
} else {
body.setAttribute("theme-mode", "dark");
setMode("dark");
}
};
return (
<Nav.Footer collapseButton={true}>
<Button
onClick={switchMode}
theme="borderless"
icon={
mode === "light" ? (
<IconMoon size="large" />
) : (
<IconSun size="large" />
)
}
style={{
color: "var(--semi-color-text-2)",
// marginRight: '12px',
// zIndex: 100,
}}
/>
</Nav.Footer>
);
}
function isSub(key1: string, key2: string | number) {
const routerMap: any = {
+34 -1
View File
@@ -1,3 +1,5 @@
import { useState, useEffect } from "react";
export const responsiveMap = {
xs: '(max-width: 575px)',
sm: '(min-width: 576px)',
@@ -49,4 +51,35 @@ export const humDate = (time: number): string => new Date(time * 1000).toLocaleS
minute: '2-digit',
second: '2-digit',
hour12: false
}).replaceAll('/', '-')
}).replaceAll('/', '-')
export const useSystemTheme = () => {
const [theme, setTheme] = useState<string>('light')
useEffect(() => {
const getSystemTheme = () =>
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
setTheme(getSystemTheme)
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = () => setTheme(getSystemTheme)
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])
return theme
}
export const useTheme = (mode: string, systemTheme: string) => {
useEffect(() => {
localStorage.setItem('mode', mode)
switch (mode) {
case 'light':
document.body.setAttribute('theme-mode', 'light')
break
case 'dark':
document.body.setAttribute('theme-mode', 'dark')
break
default:
document.body.setAttribute('theme-mode', systemTheme)
break
}
}, [mode, systemTheme])
}
+74
View File
@@ -0,0 +1,74 @@
'use client'
import { SetStateAction, useEffect, useState } from 'react'
import { Button } from '@douyinfe/semi-ui'
import { IconMoon, IconSun, IconContrast } from '@douyinfe/semi-icons'
interface ThemeButtonProps {
mode: string
setMode: {
(value: SetStateAction<string>): void
(arg0: string): void
}
systemTheme: string
}
const ThemeButton: React.FC<ThemeButtonProps> = props => {
const [switchTrigger, setSwitchTrigger] = useState(false)
const [icon, setIcon] = useState(<IconContrast size="large" />)
useEffect(() => {
{
// 按下按钮切换主题
if (typeof window !== 'undefined' && switchTrigger === true) {
const body = document.body
const currentMode = props.mode
let nextMode = currentMode
switch (currentMode) {
case 'auto':
nextMode = 'light'
break
case 'light':
nextMode = 'dark'
break
default:
nextMode = 'auto'
break
}
body.removeAttribute('theme-mode')
nextMode === 'auto'
? body.setAttribute('theme-mode', props.systemTheme)
: body.setAttribute('theme-mode', nextMode)
props.setMode(nextMode)
setSwitchTrigger(false)
}
// 更新图标
switch (props.mode) {
case 'light':
setIcon(<IconSun size="large" />)
break
case 'dark':
setIcon(<IconMoon size="large" />)
break
default:
setIcon(<IconContrast size="large" />)
break
}
}
}, [props, switchTrigger])
const switchMode = () => {
setSwitchTrigger(true)
}
return (
<Button
onClick={switchMode}
theme="borderless"
icon={icon}
style={{
color: 'var(--semi-color-text-2)',
}}
/>
)
}
export default ThemeButton
+14
View File
@@ -27,6 +27,7 @@
"@types/react-dom": "^18.3.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.1.0",
"typescript": "^5.4.5"
}
},
@@ -1795,6 +1796,19 @@
}
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+2 -1
View File
@@ -28,6 +28,7 @@
"@types/react-dom": "^18.3.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.3",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"eslint-config-prettier": "^9.1.0"
}
}