Skip to content

Commit d9f7884

Browse files
committed
feat: 为webui添加与一键包一致的主题
1 parent 27a8eb7 commit d9f7884

20 files changed

Lines changed: 1975 additions & 945 deletions

dashboard/src/components/layout/Header.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export function Header({
8585

8686
return (
8787
<header
88+
data-dashboard-header="true"
8889
className={cn(
8990
'sticky top-0 isolate z-10 flex h-16 min-w-0 items-center justify-between gap-2 border-b px-3 backdrop-blur-md sm:px-4',
9091
inheritsPageBackground ? 'bg-transparent' : 'bg-card/80'
@@ -129,7 +130,7 @@ export function Header({
129130
<TabsTrigger
130131
asChild
131132
value="settings"
132-
className="relative h-7 gap-1.5 bg-transparent px-2.5 text-xs font-medium data-[state=active]:bg-transparent data-[state=active]:text-primary-foreground data-[state=active]:shadow-none"
133+
className="data-[state=active]:text-primary-foreground relative h-7 gap-1.5 bg-transparent px-2.5 text-xs font-medium data-[state=active]:bg-transparent data-[state=active]:shadow-none"
133134
>
134135
<Link to="/">
135136
{workspaceMode === 'settings' && (
@@ -146,7 +147,7 @@ export function Header({
146147
<TabsTrigger
147148
asChild
148149
value="chat"
149-
className="relative h-7 gap-1.5 bg-transparent px-2.5 text-xs font-medium data-[state=active]:bg-transparent data-[state=active]:text-primary-foreground data-[state=active]:shadow-none"
150+
className="data-[state=active]:text-primary-foreground relative h-7 gap-1.5 bg-transparent px-2.5 text-xs font-medium data-[state=active]:bg-transparent data-[state=active]:shadow-none"
150151
>
151152
<Link to="/chat">
152153
{workspaceMode === 'chat' && (

dashboard/src/components/layout/Layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ export function Layout({ children }: LayoutProps) {
120120
<TooltipProvider delayDuration={300}>
121121
<SkipNav />
122122
{isElectron() && <TitleBar />}
123-
<div className={cn('relative isolate flex h-screen overflow-hidden', isElectron() && 'pt-8')}>
123+
<div
124+
data-dashboard-shell="true"
125+
className={cn('relative isolate flex h-screen overflow-hidden', isElectron() && 'pt-8')}
126+
>
124127
<BackgroundLayer config={pageBg} layerId="page" />
125128
<div className="relative z-10 flex h-full w-full overflow-hidden">
126129
{/* Sidebar:仅在设置工作区显示,伴随滑入/滑出动画 */}
@@ -198,6 +201,7 @@ export function Layout({ children }: LayoutProps) {
198201
{/* Page content */}
199202
<main
200203
id="main-content"
204+
data-dashboard-main="true"
201205
tabIndex={-1}
202206
className={cn(
203207
'relative isolate flex-1 overflow-hidden outline-none',

dashboard/src/components/layout/NavItem.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,26 @@ export function NavItem({ item, sidebarOpen, tooltipsEnabled, onMobileMenuClose
2121

2222
const menuItemContent = (
2323
<>
24-
<div className={cn(
25-
'flex items-center transition-all duration-300',
26-
sidebarOpen ? 'gap-3' : 'gap-3 lg:gap-0'
27-
)}>
24+
<div
25+
className={cn(
26+
'flex items-center transition-all duration-300',
27+
sidebarOpen ? 'gap-3' : 'gap-3 lg:gap-0'
28+
)}
29+
>
2830
<Icon
29-
className={cn(
30-
'h-5 w-5 flex-shrink-0',
31-
isActive && 'text-primary'
32-
)}
31+
className={cn('h-5 w-5 flex-shrink-0', isActive && 'text-primary')}
3332
strokeWidth={2}
3433
fill="none"
3534
/>
36-
<span className={cn(
37-
'text-sm font-medium whitespace-nowrap transition-all duration-300',
38-
isActive && 'font-semibold',
39-
sidebarOpen
40-
? 'opacity-100 max-w-[200px]'
41-
: 'opacity-100 max-w-[200px] lg:opacity-0 lg:max-w-0 lg:overflow-hidden'
42-
)}>
35+
<span
36+
className={cn(
37+
'text-sm font-medium whitespace-nowrap transition-all duration-300',
38+
isActive && 'font-semibold',
39+
sidebarOpen
40+
? 'max-w-[200px] opacity-100'
41+
: 'max-w-[200px] opacity-100 lg:max-w-0 lg:overflow-hidden lg:opacity-0'
42+
)}
43+
>
4344
{t(item.label)}
4445
</span>
4546
</div>
@@ -53,13 +54,15 @@ export function NavItem({ item, sidebarOpen, tooltipsEnabled, onMobileMenuClose
5354
<Link
5455
to={item.path}
5556
data-tour={item.tourId}
57+
data-dashboard-nav-item="true"
58+
data-active={isActive ? 'true' : 'false'}
5659
className={cn(
5760
'relative flex items-center rounded-lg py-2 transition-all duration-300',
5861
'hover:bg-accent hover:text-accent-foreground',
5962
isActive
6063
? 'bg-accent text-foreground'
6164
: 'text-muted-foreground hover:text-foreground',
62-
sidebarOpen ? 'px-3' : 'px-3 lg:px-0 lg:justify-center lg:w-12 lg:mx-auto'
65+
sidebarOpen ? 'px-3' : 'px-3 lg:mx-auto lg:w-12 lg:justify-center lg:px-0'
6366
)}
6467
onClick={onMobileMenuClose}
6568
>

dashboard/src/components/layout/Sidebar.tsx

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@ interface SidebarProps {
1616
onMobileMenuClose: () => void
1717
}
1818

19-
export function Sidebar({
20-
sidebarOpen,
21-
mobileMenuOpen,
22-
tooltipsEnabled,
23-
onMobileMenuClose
19+
export function Sidebar({
20+
sidebarOpen,
21+
mobileMenuOpen,
22+
tooltipsEnabled,
23+
onMobileMenuClose,
2424
}: SidebarProps) {
2525
const { t } = useTranslation()
2626
const { config: sidebarBg, inheritedFrom } = useBackground('sidebar')
2727
const inheritsPageBackground = inheritedFrom === 'page'
2828

2929
return (
3030
<aside
31+
data-dashboard-sidebar="true"
3132
className={cn(
32-
'fixed inset-y-0 left-0 z-50 isolate flex flex-col border-r transition-all duration-300 lg:relative lg:z-0 lg:h-full',
33+
'fixed inset-y-0 left-0 isolate z-50 flex flex-col border-r transition-all duration-300 lg:relative lg:z-0 lg:h-full',
3334
inheritsPageBackground ? 'bg-transparent' : 'bg-card',
3435
// 移动端始终显示完整宽度,桌面端根据 sidebarOpen 切换
3536
'w-52 lg:w-auto',
@@ -38,64 +39,67 @@ export function Sidebar({
3839
)}
3940
>
4041
{!inheritsPageBackground && <BackgroundLayer config={sidebarBg} layerId="sidebar" />}
41-
42+
4243
{/* Logo 区域 */}
4344
<div className="relative z-10">
4445
<LogoArea sidebarOpen={sidebarOpen} />
4546
</div>
4647

47-
<ScrollArea className={cn(
48-
'relative z-10',
49-
"min-h-0 flex-1 overflow-x-hidden",
50-
!sidebarOpen && "lg:w-16"
51-
)}
52-
viewportClassName="[&>div]:!block"
48+
<ScrollArea
49+
className={cn(
50+
'relative z-10',
51+
'min-h-0 flex-1 overflow-x-hidden',
52+
!sidebarOpen && 'lg:w-16'
53+
)}
54+
viewportClassName="[&>div]:!block"
5355
>
5456
<nav
5557
aria-label={t('a11y.sidebarNav')}
56-
className={cn(
57-
"p-4",
58-
!sidebarOpen && "lg:p-2 lg:w-16"
59-
)}>
60-
<ul className={cn(
61-
// 移动端始终使用正常间距,桌面端根据 sidebarOpen 切换
62-
"space-y-6",
63-
!sidebarOpen && "lg:space-y-3 lg:w-full"
64-
)}>
65-
{menuSections.map((section, sectionIndex) => (
66-
<li key={section.title}>
67-
{/* 块标题 - 移动端始终可见,桌面端根据 sidebarOpen 切换 */}
68-
<div className={cn(
69-
"px-3 h-[1.25rem]",
70-
// 移动端始终显示,桌面端根据状态切换
71-
"mb-2",
72-
!sidebarOpen && "lg:mb-1 lg:invisible"
73-
)}>
74-
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground/60 whitespace-nowrap">
75-
{t(section.title)}
76-
</h3>
77-
</div>
58+
className={cn('p-4', !sidebarOpen && 'lg:w-16 lg:p-2')}
59+
>
60+
<ul
61+
className={cn(
62+
// 移动端始终使用正常间距,桌面端根据 sidebarOpen 切换
63+
'space-y-6',
64+
!sidebarOpen && 'lg:w-full lg:space-y-3'
65+
)}
66+
>
67+
{menuSections.map((section, sectionIndex) => (
68+
<li key={section.title}>
69+
{/* 块标题 - 移动端始终可见,桌面端根据 sidebarOpen 切换 */}
70+
<div
71+
className={cn(
72+
'h-[1.25rem] px-3',
73+
// 移动端始终显示,桌面端根据状态切换
74+
'mb-2',
75+
!sidebarOpen && 'lg:invisible lg:mb-1'
76+
)}
77+
>
78+
<h3 className="text-muted-foreground/60 text-xs font-semibold tracking-wider whitespace-nowrap uppercase">
79+
{t(section.title)}
80+
</h3>
81+
</div>
7882

79-
{/* 分割线 - 仅在桌面端折叠时显示 */}
80-
{!sidebarOpen && sectionIndex > 0 && (
81-
<div className="hidden lg:block mb-2 border-t border-border" />
82-
)}
83+
{/* 分割线 - 仅在桌面端折叠时显示 */}
84+
{!sidebarOpen && sectionIndex > 0 && (
85+
<div className="border-border mb-2 hidden border-t lg:block" />
86+
)}
8387

84-
{/* 菜单项列表 */}
85-
<ul className="space-y-1">
86-
{section.items.map((item) => (
87-
<NavItem
88-
key={item.path}
89-
item={item}
90-
sidebarOpen={sidebarOpen}
91-
tooltipsEnabled={tooltipsEnabled}
92-
onMobileMenuClose={onMobileMenuClose}
93-
/>
94-
))}
95-
</ul>
96-
</li>
97-
))}
98-
</ul>
88+
{/* 菜单项列表 */}
89+
<ul className="space-y-1">
90+
{section.items.map((item) => (
91+
<NavItem
92+
key={item.path}
93+
item={item}
94+
sidebarOpen={sidebarOpen}
95+
tooltipsEnabled={tooltipsEnabled}
96+
onMobileMenuClose={onMobileMenuClose}
97+
/>
98+
))}
99+
</ul>
100+
</li>
101+
))}
102+
</ul>
99103
</nav>
100104
</ScrollArea>
101105
</aside>

dashboard/src/components/theme-provider.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
22
import type { ReactNode } from 'react'
33

44
import { ThemeProviderContext } from '@/lib/theme-context'
5+
import { DEFAULT_DASHBOARD_STYLE, DEFAULT_FUTURE_RETRO_STYLE_CONFIG } from '@/lib/theme/tokens'
56
import type { UserThemeConfig } from '@/lib/theme/tokens'
67
import {
78
THEME_STORAGE_KEYS,
@@ -58,6 +59,16 @@ export function ThemeProvider({
5859
root.classList.add(resolvedTheme)
5960

6061
const isDark = resolvedTheme === 'dark'
62+
const dashboardStyle = themeConfig.dashboardStyle ?? DEFAULT_DASHBOARD_STYLE
63+
const futureRetroConfig = {
64+
...DEFAULT_FUTURE_RETRO_STYLE_CONFIG,
65+
...themeConfig.styleConfig?.futureRetro,
66+
}
67+
68+
root.dataset.dashboardStyle = dashboardStyle
69+
root.dataset.retroPaperTexture = futureRetroConfig.paperTexture ? 'true' : 'false'
70+
root.dataset.retroStrongBorders = futureRetroConfig.strongBorders ? 'true' : 'false'
71+
6172
applyThemePipeline(themeConfig, isDark)
6273
}, [resolvedTheme, themeConfig])
6374

@@ -86,12 +97,8 @@ export function ThemeProvider({
8697
updateThemeConfig,
8798
resetTheme,
8899
}),
89-
[themeMode, resolvedTheme, setTheme, themeConfig, updateThemeConfig, resetTheme],
100+
[themeMode, resolvedTheme, setTheme, themeConfig, updateThemeConfig, resetTheme]
90101
)
91102

92-
return (
93-
<ThemeProviderContext value={value}>
94-
{children}
95-
</ThemeProviderContext>
96-
)
103+
return <ThemeProviderContext value={value}>{children}</ThemeProviderContext>
97104
}

dashboard/src/components/ui/badge.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
1-
import * as React from "react"
2-
import { cva, type VariantProps } from "class-variance-authority"
1+
import * as React from 'react'
2+
import { cva, type VariantProps } from 'class-variance-authority'
33

4-
import { cn } from "@/lib/utils"
4+
import { cn } from '@/lib/utils'
55

66
const badgeVariants = cva(
7-
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7+
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
88
{
99
variants: {
1010
variant: {
11-
default:
12-
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
11+
default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
1312
secondary:
14-
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
1514
destructive:
16-
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17-
outline: "text-foreground",
15+
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
16+
outline: 'text-foreground',
1817
},
1918
},
2019
defaultVariants: {
21-
variant: "default",
20+
variant: 'default',
2221
},
2322
}
2423
)
2524

2625
export interface BadgeProps
27-
extends React.HTMLAttributes<HTMLDivElement>,
28-
VariantProps<typeof badgeVariants> {}
26+
extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
2927

3028
function Badge({ className, variant, ...props }: BadgeProps) {
3129
return (
32-
<div className={cn(badgeVariants({ variant }), className)} {...props} />
30+
<div
31+
data-dashboard-badge="true"
32+
className={cn(badgeVariants({ variant }), className)}
33+
{...props}
34+
/>
3335
)
3436
}
3537

0 commit comments

Comments
 (0)