feat: implement palette toggle component and update layout for dynamic theme support

This commit is contained in:
mlogclub
2026-05-24 16:09:44 +08:00
parent 17959b18b4
commit b1bc1580d9
6 changed files with 311 additions and 67 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ export function DashboardToolbar({
return (
<div
className={cn(
"flex flex-col gap-3 rounded-lg border bg-card p-3 lg:flex-row lg:items-center lg:justify-between",
"flex flex-col gap-3 rounded-lg border border-border/70 bg-card/95 p-3 shadow-[0_1px_2px_rgba(15,23,42,0.04)] lg:flex-row lg:items-center lg:justify-between dark:shadow-none",
className
)}
>
@@ -54,7 +54,7 @@ export function DashboardTableShell({
}) {
return (
<div className={cn("space-y-4", className)}>
<div className="overflow-hidden rounded-lg border bg-card text-card-foreground">
<div className="overflow-hidden rounded-lg border border-border/70 bg-card text-card-foreground shadow-[0_1px_2px_rgba(15,23,42,0.035)] dark:shadow-none">
{children}
</div>
{pagination ? <div>{pagination}</div> : null}
+96
View File
@@ -0,0 +1,96 @@
"use client"
import { useEffect, useState } from "react"
import { DropletsIcon, PaletteIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
type PaletteMode = "blue" | "green" | "gray"
const PALETTE_STORAGE_KEY = "dashboard_palette"
const DEFAULT_PALETTE: PaletteMode = "green"
const paletteOptions: Array<{
value: PaletteMode
label: string
swatch: string
}> = [
{
value: "green",
label: "温润服务绿",
swatch: "bg-teal-700",
},
{
value: "gray",
label: "中性精密灰",
swatch: "bg-slate-500",
},
{
value: "blue",
label: "清透科技蓝",
swatch: "bg-blue-600",
},
]
function readPalette(): PaletteMode {
if (typeof window === "undefined") {
return DEFAULT_PALETTE
}
const stored = window.localStorage.getItem(PALETTE_STORAGE_KEY)
return stored === "blue" || stored === "green" || stored === "gray"
? stored
: DEFAULT_PALETTE
}
function applyPalette(value: PaletteMode) {
document.documentElement.dataset.palette = value
window.localStorage.setItem(PALETTE_STORAGE_KEY, value)
}
export function PaletteToggle() {
const [palette, setPalette] = useState<PaletteMode>(DEFAULT_PALETTE)
useEffect(() => {
const storedPalette = readPalette()
setPalette(storedPalette)
applyPalette(storedPalette)
}, [])
function handleChange(value: string) {
const nextPalette: PaletteMode =
value === "blue" || value === "gray" ? value : "green"
setPalette(nextPalette)
applyPalette(nextPalette)
}
const ActiveIcon = palette === "green" ? DropletsIcon : PaletteIcon
return (
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" size="sm" />}
aria-label="切换主题色"
>
<ActiveIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 min-w-52">
<DropdownMenuRadioGroup value={palette} onValueChange={handleChange}>
{paletteOptions.map((option) => (
<DropdownMenuRadioItem key={option.value} value={option.value}>
<span className={`size-2.5 rounded-full ${option.swatch}`} />
<span className="flex-1">{option.label}</span>
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}
+4 -2
View File
@@ -6,6 +6,7 @@ import { usePathname } from "next/navigation"
import { RealtimeConnectionStatus } from "@/components/realtime-connection-status"
import { getPageTitle } from "@/lib/navigation"
import { useAgentConversationsStore } from "@/lib/stores/agent-conversations"
import { PaletteToggle } from "@/components/palette-toggle"
import { ThemeToggle } from "@/components/theme-toggle"
import {
Breadcrumb,
@@ -50,7 +51,7 @@ export function SiteHeader() {
}, [isMobile, open])
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b border-border/70 bg-background/82 backdrop-blur supports-[backdrop-filter]:bg-background/72 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<div className="flex w-full items-center justify-between gap-3 px-4 lg:px-6">
<div className="flex min-w-0 items-center gap-2">
<SidebarTrigger className="-ml-1" />
@@ -73,7 +74,8 @@ export function SiteHeader() {
</Breadcrumb>
</div>
</div>
<div className="flex items-center justify-end gap-3">
<div className="flex items-center justify-end gap-2">
<PaletteToggle />
<ThemeToggle />
</div>
</div>