From 2f1057a9d9d51d0e657c1203d3781173e8efc611 Mon Sep 17 00:00:00 2001 From: shu Date: Sun, 6 Sep 2026 17:04:49 +0800 Subject: 初始化 React TypeScript 模板并实现 Mantine 响应式基础布局 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 Vite、React 和 TypeScript 初始化项目,配置 pnpm、Oxlint 与 Mantine PostCSS - 接入 React Router Data Mode,将页面路由、菜单标题和图标统一维护在路由配置中 - 添加首页、关于概览与关于我页面,使用 Nested NavLinks 根据路由生成多级菜单 - 实现桌面侧栏展开和图标模式、移动端全宽抽屉以及与品牌区对齐的顶栏 - 将布局自定义样式拆分为 CSS Modules,统一 xs 间距并固定菜单图标列位置 - 添加文档流页脚,展示自动更新年份的示例版权说明 - 配置 Mantine 默认圆角、项目说明及中文提交约定 验证:pnpm build、pnpm lint、git diff --cached --check 均通过。 --- src/layouts/BasicLayout.tsx | 225 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/layouts/BasicLayout.tsx (limited to 'src/layouts/BasicLayout.tsx') diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx new file mode 100644 index 0000000..6af8564 --- /dev/null +++ b/src/layouts/BasicLayout.tsx @@ -0,0 +1,225 @@ +import { useEffect, useState } from 'react' +import { + AppShell, + ActionIcon, + Box, + Burger, + Drawer, + Group, + NavLink, + Stack, + Text, + Tooltip, + rem, + useMantineTheme, +} from '@mantine/core' +import { useDisclosure, useMediaQuery } from '@mantine/hooks' +import { CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react' +import { NavLink as RouterNavLink, Outlet, useLocation, useMatches } from 'react-router' +import { pageRoutes } from '../routes.ts' +import type { PageRoute } from '../routes.ts' +import classes from './BasicLayout.module.css' + +const collapsedNavbarWidth = rem(64) + +type NavigationProps = { + onNavigate?: () => void + collapsed?: boolean + routes?: PageRoute[] + parentPath?: string +} + +function NavigationItem({ + route, + parentPath, + collapsed, + onNavigate, +}: { + route: PageRoute + parentPath: string + collapsed: boolean + onNavigate?: () => void +}) { + const matches = useMatches() + const active = matches.some((match) => match.id === route.id) + const selected = matches.at(-1)?.id === route.id + const [opened, setOpened] = useState(active) + + if (!route.handle) return null + + const { title: label, icon: Icon } = route.handle + const to = !route.path + ? parentPath + : route.path.startsWith('/') + ? route.path + : `${parentPath.replace(/\/$/, '')}/${route.path}` + const hasChildren = route.children?.some((child) => child.handle) + const sharedProps = { + label, + noWrap: true, + p: 'xs', + 'aria-label': label, + leftSection: