diff --git a/frontend/src/app/layout/AppShell.tsx b/frontend/src/app/layout/AppShell.tsx
index b06357e..50967bb 100644
--- a/frontend/src/app/layout/AppShell.tsx
+++ b/frontend/src/app/layout/AppShell.tsx
@@ -1,4 +1,4 @@
-import type * as React from 'react'
+import * as React from 'react'
import { AppSidebar } from '@/components/app/AppSidebar'
import { AppTopbar } from '@/components/app/AppTopbar'
@@ -8,11 +8,13 @@ type AppShellProps = {
}
export function AppShell({ children }: AppShellProps) {
+ const [mobileNavOpen, setMobileNavOpen] = React.useState(false)
+
return (
-
+
-
+
setMobileNavOpen(true)} />
{children}
diff --git a/frontend/src/components/app/AppField.tsx b/frontend/src/components/app/AppField.tsx
index 2a160cc..f1d324f 100644
--- a/frontend/src/components/app/AppField.tsx
+++ b/frontend/src/components/app/AppField.tsx
@@ -1,15 +1,25 @@
import type * as React from 'react'
+import { Field, FieldDescription, FieldLabel } from '@/components/ui/field'
+
type AppFieldProps = {
label: string
+ htmlFor?: string
+ description?: string
+ invalid?: boolean
+ required?: boolean
children: React.ReactNode
}
-export function AppField({ label, children }: AppFieldProps) {
+export function AppField({ label, htmlFor, description, invalid, required, children }: AppFieldProps) {
return (
-
+ {description ? {description} : null}
+
)
}
diff --git a/frontend/src/components/app/AppSidebar.tsx b/frontend/src/components/app/AppSidebar.tsx
index 3304a94..b26fcc5 100644
--- a/frontend/src/components/app/AppSidebar.tsx
+++ b/frontend/src/components/app/AppSidebar.tsx
@@ -19,6 +19,7 @@ import {
import { useAuth } from '@/features/auth/AuthProvider'
import type { AuthMenu } from '@/features/auth/types'
import { cn } from '@/lib/utils'
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
const iconMap = {
audit: ScrollText,
@@ -56,44 +57,83 @@ function visibleChildren(menu: AuthMenu) {
return menu.children.filter((item) => item.is_visible && item.is_enabled)
}
-export function AppSidebar() {
+type SidebarNavContentProps = {
+ groups: AuthMenu[]
+ currentHash: string
+ onNavigate?: () => void
+}
+
+function SidebarBrand() {
+ return (
+
+ )
+}
+
+function SidebarNavContent({ groups, currentHash, onNavigate }: SidebarNavContentProps) {
+ return (
+
+ )
+}
+
+type AppSidebarProps = {
+ mobileOpen?: boolean
+ onMobileOpenChange?: (open: boolean) => void
+}
+
+export function AppSidebar({ mobileOpen = false, onMobileOpenChange }: AppSidebarProps) {
const { user } = useAuth()
const currentHash = useCurrentHash()
const groups = (user?.menus ?? []).filter((menu) => menu.is_visible && menu.is_enabled)
return (
-
+ <>
+
+
+
+
+ 移动端导航
+
+
+ onMobileOpenChange?.(false)}
+ />
+
+
+ >
)
}
diff --git a/frontend/src/components/app/AppTopbar.tsx b/frontend/src/components/app/AppTopbar.tsx
index 24a4ccb..fdbd390 100644
--- a/frontend/src/components/app/AppTopbar.tsx
+++ b/frontend/src/components/app/AppTopbar.tsx
@@ -1,16 +1,29 @@
-import { Bell, ChevronRight, LogOut, UserRound } from 'lucide-react'
+import { Bell, ChevronRight, LogOut, Menu, UserRound } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { useAuth } from '@/features/auth/AuthProvider'
-export function AppTopbar() {
+type AppTopbarProps = {
+ onMobileMenuClick?: () => void
+}
+
+export function AppTopbar({ onMobileMenuClick }: AppTopbarProps) {
const { logout, user } = useAuth()
return (