refactor(theme): migrate all CSS variable references and adapt third-party components

- Task 10: Bulk migrate CSS variable references from --primary/--secondary/etc to --color-primary/--color-secondary/etc across all components
- Task 11: Adapt CodeEditor to auto-detect theme from ThemeProvider context, remove hardcoded theme='dark' from plugin-config and bot config pages
pull/1496/head
DrSmoothl 2026-02-19 18:01:14 +08:00
parent b5088fa455
commit 06a88a877f
9 changed files with 86 additions and 82 deletions

View File

@ -7,6 +7,8 @@ import { EditorView } from '@codemirror/view'
import { StreamLanguage } from '@codemirror/language' import { StreamLanguage } from '@codemirror/language'
import { toml as tomlMode } from '@codemirror/legacy-modes/mode/toml' import { toml as tomlMode } from '@codemirror/legacy-modes/mode/toml'
import { useTheme } from '@/components/use-theme'
export type Language = 'python' | 'json' | 'toml' | 'text' export type Language = 'python' | 'json' | 'toml' | 'text'
interface CodeEditorProps { interface CodeEditorProps {
@ -39,10 +41,11 @@ export function CodeEditor({
minHeight, minHeight,
maxHeight, maxHeight,
placeholder, placeholder,
theme = 'dark', theme,
className = '', className = '',
}: CodeEditorProps) { }: CodeEditorProps) {
const [mounted, setMounted] = useState(false) const [mounted, setMounted] = useState(false)
const { resolvedTheme } = useTheme()
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true)
@ -81,6 +84,9 @@ export function CodeEditor({
extensions.push(EditorView.editable.of(false)) extensions.push(EditorView.editable.of(false))
} }
// 如果外部传了 theme prop 则使用,否则从 context 自动获取
const effectiveTheme = theme ?? resolvedTheme
return ( return (
<div className={`rounded-md overflow-hidden border custom-scrollbar ${className}`}> <div className={`rounded-md overflow-hidden border custom-scrollbar ${className}`}>
<CodeMirror <CodeMirror
@ -88,7 +94,7 @@ export function CodeEditor({
height={height} height={height}
minHeight={minHeight} minHeight={minHeight}
maxHeight={maxHeight} maxHeight={maxHeight}
theme={theme === 'dark' ? oneDark : undefined} theme={effectiveTheme === 'dark' ? oneDark : undefined}
extensions={extensions} extensions={extensions}
onChange={onChange} onChange={onChange}
placeholder={placeholder} placeholder={placeholder}

View File

@ -7,10 +7,10 @@ import { useTour } from './use-tour'
const joyrideStyles = { const joyrideStyles = {
options: { options: {
zIndex: 10000, zIndex: 10000,
primaryColor: 'hsl(var(--primary))', primaryColor: 'hsl(var(--color-primary))',
textColor: 'hsl(var(--foreground))', textColor: 'hsl(var(--color-foreground))',
backgroundColor: 'hsl(var(--background))', backgroundColor: 'hsl(var(--color-background))',
arrowColor: 'hsl(var(--background))', arrowColor: 'hsl(var(--color-background))',
overlayColor: 'rgba(0, 0, 0, 0.5)', overlayColor: 'rgba(0, 0, 0, 0.5)',
}, },
tooltip: { tooltip: {
@ -30,23 +30,23 @@ const joyrideStyles = {
padding: '0.5rem 0', padding: '0.5rem 0',
}, },
buttonNext: { buttonNext: {
backgroundColor: 'hsl(var(--primary))', backgroundColor: 'hsl(var(--color-primary))',
color: 'hsl(var(--primary-foreground))', color: 'hsl(var(--color-primary-foreground))',
borderRadius: 'calc(var(--radius) - 2px)', borderRadius: 'calc(var(--radius) - 2px)',
fontSize: '0.875rem', fontSize: '0.875rem',
padding: '0.5rem 1rem', padding: '0.5rem 1rem',
}, },
buttonBack: { buttonBack: {
color: 'hsl(var(--muted-foreground))', color: 'hsl(var(--color-muted-foreground))',
fontSize: '0.875rem', fontSize: '0.875rem',
marginRight: '0.5rem', marginRight: '0.5rem',
}, },
buttonSkip: { buttonSkip: {
color: 'hsl(var(--muted-foreground))', color: 'hsl(var(--color-muted-foreground))',
fontSize: '0.875rem', fontSize: '0.875rem',
}, },
buttonClose: { buttonClose: {
color: 'hsl(var(--muted-foreground))', color: 'hsl(var(--color-muted-foreground))',
}, },
spotlight: { spotlight: {
borderRadius: 'var(--radius)', borderRadius: 'var(--radius)',

View File

@ -354,7 +354,7 @@ export function WavesBackground() {
left: 0, left: 0,
width: '0.5rem', width: '0.5rem',
height: '0.5rem', height: '0.5rem',
background: 'hsl(var(--primary) / 0.3)', background: 'hsl(var(--color-primary) / 0.3)',
borderRadius: '50%', borderRadius: '50%',
transform: 'translate3d(calc(var(--x, -0.5rem) - 50%), calc(var(--y, 50%) - 50%), 0)', transform: 'translate3d(calc(var(--x, -0.5rem) - 50%), calc(var(--y, 50%) - 50%), 0)',
willChange: 'transform', willChange: 'transform',
@ -372,7 +372,7 @@ export function WavesBackground() {
<style>{` <style>{`
path { path {
fill: none; fill: none;
stroke: hsl(var(--primary) / 0.20); stroke: hsl(var(--color-primary) / 0.20);
stroke-width: 1px; stroke-width: 1px;
} }
`}</style> `}</style>

View File

@ -341,7 +341,7 @@ export function AnnualReportPage() {
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 12px rgba(0,0,0,0.1)' }} contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 12px rgba(0,0,0,0.1)' }}
cursor={{ fill: 'transparent' }} cursor={{ fill: 'transparent' }}
/> />
<Bar dataKey="count" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} /> <Bar dataKey="count" fill="hsl(var(--color-primary))" radius={[4, 4, 0, 0]} />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</CardContent> </CardContent>

View File

@ -625,7 +625,6 @@ function BotConfigPageContent() {
} }
}} }}
language="toml" language="toml"
theme="dark"
height="calc(100vh - 280px)" height="calc(100vh - 280px)"
minHeight="500px" minHeight="500px"
placeholder="TOML 配置内容" placeholder="TOML 配置内容"

View File

@ -403,15 +403,15 @@ function IndexPageContent() {
const chartConfig = { const chartConfig = {
requests: { requests: {
label: '请求数', label: '请求数',
color: 'hsl(var(--chart-1))', color: 'hsl(var(--color-chart-1))',
}, },
cost: { cost: {
label: '花费(¥)', label: '花费(¥)',
color: 'hsl(var(--chart-2))', color: 'hsl(var(--color-chart-2))',
}, },
tokens: { tokens: {
label: 'Tokens', label: 'Tokens',
color: 'hsl(var(--chart-3))', color: 'hsl(var(--color-chart-3))',
}, },
} satisfies ChartConfig } satisfies ChartConfig
@ -738,17 +738,17 @@ function IndexPageContent() {
<CardContent> <CardContent>
<ChartContainer config={chartConfig} className="h-[300px] sm:h-[400px] w-full aspect-auto"> <ChartContainer config={chartConfig} className="h-[300px] sm:h-[400px] w-full aspect-auto">
<LineChart data={hourly_data}> <LineChart data={hourly_data}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--muted-foreground) / 0.2)" /> <CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--color-muted-foreground) / 0.2)" />
<XAxis <XAxis
dataKey="timestamp" dataKey="timestamp"
tickFormatter={(value) => formatDateTime(value)} tickFormatter={(value) => formatDateTime(value)}
angle={-45} angle={-45}
textAnchor="end" textAnchor="end"
height={60} height={60}
stroke="hsl(var(--muted-foreground))" stroke="hsl(var(--color-muted-foreground))"
tick={{ fill: 'hsl(var(--muted-foreground))' }} tick={{ fill: 'hsl(var(--color-muted-foreground))' }}
/> />
<YAxis stroke="hsl(var(--muted-foreground))" tick={{ fill: 'hsl(var(--muted-foreground))' }} /> <YAxis stroke="hsl(var(--color-muted-foreground))" tick={{ fill: 'hsl(var(--color-muted-foreground))' }} />
<ChartTooltip <ChartTooltip
content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />} content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />}
/> />
@ -772,17 +772,17 @@ function IndexPageContent() {
<CardContent> <CardContent>
<ChartContainer config={chartConfig} className="h-[250px] sm:h-[300px] w-full aspect-auto"> <ChartContainer config={chartConfig} className="h-[250px] sm:h-[300px] w-full aspect-auto">
<BarChart data={hourly_data}> <BarChart data={hourly_data}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--muted-foreground) / 0.2)" /> <CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--color-muted-foreground) / 0.2)" />
<XAxis <XAxis
dataKey="timestamp" dataKey="timestamp"
tickFormatter={(value) => formatDateTime(value)} tickFormatter={(value) => formatDateTime(value)}
angle={-45} angle={-45}
textAnchor="end" textAnchor="end"
height={60} height={60}
stroke="hsl(var(--muted-foreground))" stroke="hsl(var(--color-muted-foreground))"
tick={{ fill: 'hsl(var(--muted-foreground))' }} tick={{ fill: 'hsl(var(--color-muted-foreground))' }}
/> />
<YAxis stroke="hsl(var(--muted-foreground))" tick={{ fill: 'hsl(var(--muted-foreground))' }} /> <YAxis stroke="hsl(var(--color-muted-foreground))" tick={{ fill: 'hsl(var(--color-muted-foreground))' }} />
<ChartTooltip <ChartTooltip
content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />} content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />}
/> />
@ -800,17 +800,17 @@ function IndexPageContent() {
<CardContent> <CardContent>
<ChartContainer config={chartConfig} className="h-[250px] sm:h-[300px] w-full aspect-auto"> <ChartContainer config={chartConfig} className="h-[250px] sm:h-[300px] w-full aspect-auto">
<BarChart data={hourly_data}> <BarChart data={hourly_data}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--muted-foreground) / 0.2)" /> <CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--color-muted-foreground) / 0.2)" />
<XAxis <XAxis
dataKey="timestamp" dataKey="timestamp"
tickFormatter={(value) => formatDateTime(value)} tickFormatter={(value) => formatDateTime(value)}
angle={-45} angle={-45}
textAnchor="end" textAnchor="end"
height={60} height={60}
stroke="hsl(var(--muted-foreground))" stroke="hsl(var(--color-muted-foreground))"
tick={{ fill: 'hsl(var(--muted-foreground))' }} tick={{ fill: 'hsl(var(--color-muted-foreground))' }}
/> />
<YAxis stroke="hsl(var(--muted-foreground))" tick={{ fill: 'hsl(var(--muted-foreground))' }} /> <YAxis stroke="hsl(var(--color-muted-foreground))" tick={{ fill: 'hsl(var(--color-muted-foreground))' }} />
<ChartTooltip <ChartTooltip
content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />} content={<ChartTooltipContent labelFormatter={(value) => formatDateTime(value as string)} />}
/> />
@ -889,7 +889,7 @@ function IndexPageContent() {
<div <div
className="w-3 h-3 rounded-full ml-2 flex-shrink-0" className="w-3 h-3 rounded-full ml-2 flex-shrink-0"
style={{ style={{
backgroundColor: `hsl(var(--chart-${(index % 5) + 1}))`, backgroundColor: `hsl(var(--color-chart-${(index % 5) + 1}))`,
}} }}
/> />
</div> </div>
@ -992,28 +992,28 @@ function IndexPageContent() {
config={{ config={{
requests: { requests: {
label: '请求数', label: '请求数',
color: 'hsl(var(--chart-1))', color: 'hsl(var(--color-chart-1))',
}, },
cost: { cost: {
label: '花费(¥)', label: '花费(¥)',
color: 'hsl(var(--chart-2))', color: 'hsl(var(--color-chart-2))',
}, },
}} }}
className="h-[400px] sm:h-[500px] w-full aspect-auto" className="h-[400px] sm:h-[500px] w-full aspect-auto"
> >
<BarChart data={daily_data}> <BarChart data={daily_data}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--muted-foreground) / 0.2)" /> <CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--color-muted-foreground) / 0.2)" />
<XAxis <XAxis
dataKey="timestamp" dataKey="timestamp"
tickFormatter={(value) => { tickFormatter={(value) => {
const date = new Date(value) const date = new Date(value)
return `${date.getMonth() + 1}/${date.getDate()}` return `${date.getMonth() + 1}/${date.getDate()}`
}} }}
stroke="hsl(var(--muted-foreground))" stroke="hsl(var(--color-muted-foreground))"
tick={{ fill: 'hsl(var(--muted-foreground))' }} tick={{ fill: 'hsl(var(--color-muted-foreground))' }}
/> />
<YAxis yAxisId="left" stroke="hsl(var(--muted-foreground))" tick={{ fill: 'hsl(var(--muted-foreground))' }} /> <YAxis yAxisId="left" stroke="hsl(var(--color-muted-foreground))" tick={{ fill: 'hsl(var(--color-muted-foreground))' }} />
<YAxis yAxisId="right" orientation="right" stroke="hsl(var(--muted-foreground))" tick={{ fill: 'hsl(var(--muted-foreground))' }} /> <YAxis yAxisId="right" orientation="right" stroke="hsl(var(--color-muted-foreground))" tick={{ fill: 'hsl(var(--color-muted-foreground))' }} />
<ChartTooltip <ChartTooltip
content={ content={
<ChartTooltipContent <ChartTooltipContent

View File

@ -602,20 +602,19 @@ function PluginConfigEditor({ plugin, onBack }: PluginConfigEditorProps) {
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<CodeEditor <CodeEditor
value={sourceCode} value={sourceCode}
onChange={(value) => { onChange={(value) => {
setSourceCode(value) setSourceCode(value)
if (hasTomlError) { if (hasTomlError) {
setHasTomlError(false) setHasTomlError(false)
} }
}} }}
language="toml" language="toml"
theme="dark" height="calc(100vh - 350px)"
height="calc(100vh - 350px)" minHeight="500px"
minHeight="500px" placeholder="TOML 配置内容"
placeholder="TOML 配置内容" />
/>
</div> </div>
)} )}

View File

@ -178,14 +178,14 @@ function applyAccentColor(color: string) {
const selectedColor = colors[color as keyof typeof colors] const selectedColor = colors[color as keyof typeof colors]
if (selectedColor) { if (selectedColor) {
// 设置主色 // 设置主色
root.style.setProperty('--primary', selectedColor.hsl) root.style.setProperty('--color-primary', selectedColor.hsl)
// 设置渐变(如果有) // 设置渐变(如果有)
if (selectedColor.gradient) { if (selectedColor.gradient) {
root.style.setProperty('--primary-gradient', selectedColor.gradient) root.style.setProperty('--color-primary-gradient', selectedColor.gradient)
root.classList.add('has-gradient') root.classList.add('has-gradient')
} else { } else {
root.style.removeProperty('--primary-gradient') root.style.removeProperty('--color-primary-gradient')
root.classList.remove('has-gradient') root.classList.remove('has-gradient')
} }
} else if (color.startsWith('#')) { } else if (color.startsWith('#')) {
@ -219,8 +219,8 @@ function applyAccentColor(color: string) {
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%` return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
} }
root.style.setProperty('--primary', hexToHsl(color)) root.style.setProperty('--color-primary', hexToHsl(color))
root.style.removeProperty('--primary-gradient') root.style.removeProperty('--color-primary-gradient')
root.classList.remove('has-gradient') root.classList.remove('has-gradient')
} }
} }

View File

@ -16,29 +16,29 @@
/* 拖放区域样式 */ /* 拖放区域样式 */
.uppy-Dashboard-AddFiles { .uppy-Dashboard-AddFiles {
border: 2px dashed hsl(var(--border)) !important; border: 2px dashed hsl(var(--color-border)) !important;
border-radius: 0.5rem !important; border-radius: 0.5rem !important;
background: hsl(var(--muted) / 0.3) !important; background: hsl(var(--color-muted) / 0.3) !important;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.uppy-Dashboard-AddFiles:hover { .uppy-Dashboard-AddFiles:hover {
border-color: hsl(var(--primary)) !important; border-color: hsl(var(--color-primary)) !important;
background: hsl(var(--muted) / 0.5) !important; background: hsl(var(--color-muted) / 0.5) !important;
} }
.uppy-Dashboard-AddFiles-title { .uppy-Dashboard-AddFiles-title {
color: hsl(var(--foreground)) !important; color: hsl(var(--color-foreground)) !important;
font-weight: 500 !important; font-weight: 500 !important;
} }
.uppy-Dashboard-AddFiles-info { .uppy-Dashboard-AddFiles-info {
color: hsl(var(--muted-foreground)) !important; color: hsl(var(--color-muted-foreground)) !important;
} }
/* 按钮样式 */ /* 按钮样式 */
.uppy-Dashboard-browse { .uppy-Dashboard-browse {
color: hsl(var(--primary)) !important; color: hsl(var(--color-primary)) !important;
font-weight: 500 !important; font-weight: 500 !important;
} }
@ -52,63 +52,63 @@
} }
.uppy-Dashboard-Item { .uppy-Dashboard-Item {
border-bottom-color: hsl(var(--border)) !important; border-bottom-color: hsl(var(--color-border)) !important;
} }
.uppy-Dashboard-Item-name { .uppy-Dashboard-Item-name {
color: hsl(var(--foreground)) !important; color: hsl(var(--color-foreground)) !important;
} }
.uppy-Dashboard-Item-status { .uppy-Dashboard-Item-status {
color: hsl(var(--muted-foreground)) !important; color: hsl(var(--color-muted-foreground)) !important;
} }
/* 进度条样式 */ /* 进度条样式 */
.uppy-StatusBar { .uppy-StatusBar {
background: hsl(var(--muted)) !important; background: hsl(var(--color-muted)) !important;
border-top: 1px solid hsl(var(--border)) !important; border-top: 1px solid hsl(var(--color-border)) !important;
} }
.uppy-StatusBar-progress { .uppy-StatusBar-progress {
background: hsl(var(--primary)) !important; background: hsl(var(--color-primary)) !important;
} }
.uppy-StatusBar-content { .uppy-StatusBar-content {
color: hsl(var(--foreground)) !important; color: hsl(var(--color-foreground)) !important;
} }
.uppy-StatusBar-actionBtn--upload { .uppy-StatusBar-actionBtn--upload {
background: hsl(var(--primary)) !important; background: hsl(var(--color-primary)) !important;
color: hsl(var(--primary-foreground)) !important; color: hsl(var(--color-primary-foreground)) !important;
border-radius: 0.375rem !important; border-radius: 0.375rem !important;
font-weight: 500 !important; font-weight: 500 !important;
padding: 0.5rem 1rem !important; padding: 0.5rem 1rem !important;
} }
.uppy-StatusBar-actionBtn--upload:hover { .uppy-StatusBar-actionBtn--upload:hover {
background: hsl(var(--primary) / 0.9) !important; background: hsl(var(--color-primary) / 0.9) !important;
} }
/* Note 提示文字样式 */ /* Note 提示文字样式 */
.uppy-Dashboard-note { .uppy-Dashboard-note {
color: hsl(var(--muted-foreground)) !important; color: hsl(var(--color-muted-foreground)) !important;
font-size: 0.75rem !important; font-size: 0.75rem !important;
} }
/* 暗色主题适配 */ /* 暗色主题适配 */
[data-uppy-theme="dark"] .uppy-Dashboard-AddFiles, [data-uppy-theme="dark"] .uppy-Dashboard-AddFiles,
.dark .uppy-Dashboard-AddFiles { .dark .uppy-Dashboard-AddFiles {
background: hsl(var(--muted) / 0.2) !important; background: hsl(var(--color-muted) / 0.2) !important;
} }
[data-uppy-theme="dark"] .uppy-Dashboard-AddFiles-title, [data-uppy-theme="dark"] .uppy-Dashboard-AddFiles-title,
.dark .uppy-Dashboard-AddFiles-title { .dark .uppy-Dashboard-AddFiles-title {
color: hsl(var(--foreground)) !important; color: hsl(var(--color-foreground)) !important;
} }
[data-uppy-theme="dark"] .uppy-StatusBar, [data-uppy-theme="dark"] .uppy-StatusBar,
.dark .uppy-StatusBar { .dark .uppy-StatusBar {
background: hsl(var(--muted) / 0.5) !important; background: hsl(var(--color-muted) / 0.5) !important;
} }
/* 移除 Uppy 自带的边框和阴影 */ /* 移除 Uppy 自带的边框和阴影 */
@ -124,7 +124,7 @@
/* 删除按钮样式 */ /* 删除按钮样式 */
.uppy-Dashboard-Item-action--remove { .uppy-Dashboard-Item-action--remove {
color: hsl(var(--destructive)) !important; color: hsl(var(--color-destructive)) !important;
} }
.uppy-Dashboard-Item-action--remove:hover { .uppy-Dashboard-Item-action--remove:hover {
@ -137,7 +137,7 @@
} }
.uppy-Dashboard-Item.is-error .uppy-Dashboard-Item-progress { .uppy-Dashboard-Item.is-error .uppy-Dashboard-Item-progress {
color: hsl(var(--destructive)) !important; color: hsl(var(--color-destructive)) !important;
} }
/* 滚动条样式 */ /* 滚动条样式 */
@ -150,10 +150,10 @@
} }
.uppy-Dashboard-files::-webkit-scrollbar-thumb { .uppy-Dashboard-files::-webkit-scrollbar-thumb {
background: hsl(var(--muted-foreground) / 0.3); background: hsl(var(--color-muted-foreground) / 0.3);
border-radius: 3px; border-radius: 3px;
} }
.uppy-Dashboard-files::-webkit-scrollbar-thumb:hover { .uppy-Dashboard-files::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5); background: hsl(var(--color-muted-foreground) / 0.5);
} }