diff options
| author | shu <nvcsadjkojf@outlook.com> | 2026-09-06 18:59:36 +0800 |
|---|---|---|
| committer | shu <nvcsadjkojf@outlook.com> | 2026-09-06 18:59:52 +0800 |
| commit | f62ab62e72698c57b4775792a0986f12de950750 (patch) | |
| tree | cf41eb5d701b112f5d1ba0aa1a7ac5b12a9e2b1f /src | |
| parent | a0df82a3e4b42334793a6c3d7139c9f24ee22e0d (diff) | |
新增全局设置抽屉与亮暗主题切换
- 在基础布局顶栏右侧添加网站设置按钮,桌面使用右侧抽屉,移动端使用全宽抽屉
- 提供亮色、暗色、跟随系统三种外观模式,默认跟随系统并通过 Mantine 自动保存选择
- 设置即时生效,切换系统主题时同步更新页面外观
- 使用查询参数管理抽屉状态,支持浏览器返回关闭和直接访问设置链接
- 使用 CSS Modules 配置触控间距及移动端安全区域
验证:构建、lint 和空白检查通过。
浏览器验证通过:主题切换、本地保存、刷新恢复、跟随系统、返回关闭、移动端宽度、Escape 关闭、焦点恢复与直接设置链接关闭。
Diffstat (limited to 'src')
| -rw-r--r-- | src/layouts/BasicLayout.tsx | 2 | ||||
| -rw-r--r-- | src/layouts/SiteSettings.module.css | 16 | ||||
| -rw-r--r-- | src/layouts/SiteSettings.tsx | 73 | ||||
| -rw-r--r-- | src/main.tsx | 2 |
4 files changed, 92 insertions, 1 deletions
diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index 6af8564..9851fd4 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -19,6 +19,7 @@ import { NavLink as RouterNavLink, Outlet, useLocation, useMatches } from 'react import { pageRoutes } from '../routes.ts' import type { PageRoute } from '../routes.ts' import classes from './BasicLayout.module.css' +import { SiteSettings } from './SiteSettings.tsx' const collapsedNavbarWidth = rem(64) @@ -194,6 +195,7 @@ export function BasicLayout() { aria-controls="mobile-navigation" /> <Text fw={600}>{title}</Text> + <SiteSettings /> </Group> </AppShell.Header> diff --git a/src/layouts/SiteSettings.module.css b/src/layouts/SiteSettings.module.css new file mode 100644 index 0000000..4718208 --- /dev/null +++ b/src/layouts/SiteSettings.module.css @@ -0,0 +1,16 @@ +.trigger { + margin-inline-start: auto; + flex-shrink: 0; +} + +.header { + padding-top: max(var(--mantine-spacing-md), env(safe-area-inset-top)); + padding-left: max(var(--mantine-spacing-md), env(safe-area-inset-left)); + padding-right: max(var(--mantine-spacing-md), env(safe-area-inset-right)); +} + +.body { + padding-bottom: max(var(--mantine-spacing-md), env(safe-area-inset-bottom)); + padding-left: max(var(--mantine-spacing-md), env(safe-area-inset-left)); + padding-right: max(var(--mantine-spacing-md), env(safe-area-inset-right)); +} diff --git a/src/layouts/SiteSettings.tsx b/src/layouts/SiteSettings.tsx new file mode 100644 index 0000000..5c0eef8 --- /dev/null +++ b/src/layouts/SiteSettings.tsx @@ -0,0 +1,73 @@ +import { useRef } from 'react' +import { + ActionIcon, Drawer, SegmentedControl, Stack, Text, Tooltip, + useMantineColorScheme, useMatches, +} from '@mantine/core' +import { GearIcon } from '@phosphor-icons/react' +import { useNavigate, useSearchParams } from 'react-router' +import classes from './SiteSettings.module.css' + +export function SiteSettings() { + const { colorScheme, setColorScheme } = useMantineColorScheme() + const size = useMatches({ base: '100%', sm: 'sm' }) + const [searchParams, setSearchParams] = useSearchParams() + const navigate = useNavigate() + const openedHere = useRef(false) + const opened = searchParams.get('settings') === 'appearance' + + function open() { + if (opened) return + openedHere.current = true + setSearchParams((previous) => { + const next = new URLSearchParams(previous) + next.set('settings', 'appearance') + return next + }, { preventScrollReset: true }) + } + + function close() { + if (openedHere.current) { + openedHere.current = false + void navigate(-1) + return + } + + // 直接访问或刷新设置链接时,关闭抽屉不应离开当前页面。 + setSearchParams((previous) => { + const next = new URLSearchParams(previous) + next.delete('settings') + return next + }, { replace: true, preventScrollReset: true }) + } + + return ( + <> + <Tooltip label="网站设置" events={{ hover: true, focus: true, touch: false }}> + <ActionIcon className={classes.trigger} variant="subtle" color="gray" size="xl" + aria-label="打开网站设置" aria-haspopup="dialog" aria-expanded={opened} onClick={open}> + <GearIcon size={22} aria-hidden="true" /> + </ActionIcon> + </Tooltip> + <Drawer opened={opened} onClose={close} title="网站设置" position="right" size={size} + closeButtonProps={{ 'aria-label': '关闭网站设置', size: 'lg' }} + classNames={{ header: classes.header, body: classes.body }}> + <Stack gap="md"> + <Text id="site-color-scheme-label" fw={600}>外观模式</Text> + <SegmentedControl + aria-labelledby="site-color-scheme-label" + value={colorScheme} + data={[ + { value: 'light', label: '亮色' }, + { value: 'dark', label: '暗色' }, + { value: 'auto', label: '跟随系统' }, + ]} + onChange={(value) => { + if (value === 'light' || value === 'dark' || value === 'auto') setColorScheme(value) + }} + /> + <Text size="sm" c="dimmed">设置即时生效并自动保存。选择“跟随系统”时,外观随设备主题变化。</Text> + </Stack> + </Drawer> + </> + ) +} diff --git a/src/main.tsx b/src/main.tsx index 39053ab..744c2ae 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,7 +10,7 @@ import { theme } from './theme.tsx' createRoot(document.getElementById('root')!).render( <StrictMode> - <MantineProvider theme={theme}> + <MantineProvider theme={theme} defaultColorScheme="auto"> <RouterProvider router={router} /> </MantineProvider> </StrictMode>, |
