feat: add shadcn theme switcher
This commit is contained in:
14
frontend/src/components/app/AppThemeProvider.tsx
Normal file
14
frontend/src/components/app/AppThemeProvider.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import type * as React from 'react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
type AppThemeProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function AppThemeProvider({ children }: AppThemeProviderProps) {
|
||||
return (
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
53
frontend/src/components/app/AppThemeToggle.tsx
Normal file
53
frontend/src/components/app/AppThemeToggle.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Laptop, Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
const themeOptions = [
|
||||
{ value: 'light', label: '亮色', icon: Sun },
|
||||
{ value: 'dark', label: '暗色', icon: Moon },
|
||||
{ value: 'system', label: '跟随系统', icon: Laptop }
|
||||
]
|
||||
|
||||
export function AppThemeToggle() {
|
||||
const { theme = 'system', setTheme } = useTheme()
|
||||
const activeTheme = themeOptions.find((option) => option.value === theme) ?? themeOptions[2]
|
||||
const ActiveIcon = activeTheme.icon
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" aria-label={`主题:${activeTheme.label}`}>
|
||||
<ActiveIcon data-icon="inline-start" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuLabel>界面主题</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
|
||||
{themeOptions.map((option) => {
|
||||
const Icon = option.icon
|
||||
return (
|
||||
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
||||
<Icon data-icon="inline-start" />
|
||||
{option.label}
|
||||
</DropdownMenuRadioItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Bell, ChevronRight, LogOut, Menu, UserRound } from 'lucide-react'
|
||||
|
||||
import { AppThemeToggle } from '@/components/app/AppThemeToggle'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
@@ -34,6 +35,7 @@ export function AppTopbar({ onMobileMenuClick }: AppTopbarProps) {
|
||||
<Button variant="ghost" size="icon" aria-label="通知">
|
||||
<Bell data-icon="inline-start" />
|
||||
</Button>
|
||||
<AppThemeToggle />
|
||||
<Button variant="ghost" className="hidden gap-2 sm:inline-flex">
|
||||
<UserRound data-icon="inline-start" />
|
||||
<span>{user?.display_name || user?.username || '当前用户'}</span>
|
||||
|
||||
Reference in New Issue
Block a user