feat(portal): auto-switch light/dark theme via prefers-color-scheme with top-right toggle

Browse Source
This commit is contained in:
Yin Kou committed 2026-11-07 00:11:48 +08:00
1 parent eae4596aa3
commit 4ca86da55f
9 files changed
+190 -150

No files matched your search

+42 -37
View File
@@ -1,14 +1,7 @@
import React from 'react'; import React from 'react';
import { Search, Terminal } from 'lucide-react'; import { Moon, Search, Sun, Terminal } from 'lucide-react';
import { usePortalStore } from '../store/usePortalStore'; import { usePortalStore } from '../store/usePortalStore';
import type { RouteModeId, ThemeId } from '../types/telemetry'; import type { RouteModeId } from '../types/telemetry';
const THEMES: Array<{ id: ThemeId; label: string; dot: string }> = [
{ id: 'zinc-dark', label: '深色极客', dot: '#18181b' },
{ id: 'zinc-light', label: '浅色纯净', dot: '#f8fafc' },
{ id: 'catppuccin', label: 'Catppuccin', dot: '#cba6f7' },
{ id: 'nord', label: 'Nord', dot: '#88c0d0' },
];
const MODES: Array<{ id: RouteModeId; label: string }> = [ const MODES: Array<{ id: RouteModeId; label: string }> = [
{ id: 'auto', label: '智能路由' }, { id: 'auto', label: '智能路由' },
@@ -20,9 +13,11 @@ const MODES: Array<{ id: RouteModeId; label: string }> = [
export const TopRibbon: React.FC = () => { export const TopRibbon: React.FC = () => {
const { const {
theme, theme,
resolvedTheme,
routeMode, routeMode,
telemetry, telemetry,
setTheme, setTheme,
toggleTheme,
setRouteMode, setRouteMode,
setShowAgyModal, setShowAgyModal,
setShowCmdPalette, setShowCmdPalette,
@@ -100,7 +95,7 @@ export const TopRibbon: React.FC = () => {
</div> </div>
</div> </div>
{/* Right: Search (Cmd+K), Route Switcher, Theme Switcher, Terminal Button */} {/* Right: Search (Cmd+K), Route Switcher, AGY Terminal, Top-Right Theme Switcher */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => setShowCmdPalette(true)} onClick={() => setShowCmdPalette(true)}
@@ -109,7 +104,7 @@ export const TopRibbon: React.FC = () => {
> >
<span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}> <span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}>
<Search className="w-3.5 h-3.5" /> <Search className="w-3.5 h-3.5" />
<span className="hidden sm:inline">搜索或执行指令...</span> <span className="hidden sm:inline">搜索...</span>
</span> </span>
<kbd <kbd
className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted" className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted"
@@ -140,32 +135,6 @@ export const TopRibbon: React.FC = () => {
))} ))}
</div> </div>
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
{THEMES.map((t) => (
<button
key={t.id}
onClick={() => setTheme(t.id)}
className="px-2 py-1 rounded-md font-medium transition-all flex items-center gap-1.5 cursor-pointer"
style={
theme === t.id
? {
backgroundColor: 'var(--bg-card)',
color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
}
: { color: 'var(--text-muted)' }
}
title={t.label}
>
<span
className="w-2 h-2 rounded-full border border-black/10"
style={{ backgroundColor: t.dot }}
/>
<span className="hidden lg:inline">{t.label}</span>
</button>
))}
</div>
<button <button
onClick={() => setShowAgyModal(true)} onClick={() => setShowAgyModal(true)}
className="hl-btn-primary px-3 py-1.5 text-xs flex items-center gap-1.5 cursor-pointer" className="hl-btn-primary px-3 py-1.5 text-xs flex items-center gap-1.5 cursor-pointer"
@@ -173,6 +142,42 @@ export const TopRibbon: React.FC = () => {
<Terminal className="w-3.5 h-3.5" /> <Terminal className="w-3.5 h-3.5" />
<span>AGY 终端</span> <span>AGY 终端</span>
</button> </button>
{/* Top-Right Mainstream Theme Switcher (Auto System + Light/Dark 2 Mainstream Themes) */}
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
<button
onClick={toggleTheme}
className="px-2.5 py-1 rounded-md font-medium transition-all flex items-center gap-1.5 cursor-pointer"
style={{
backgroundColor: 'var(--bg-card)',
color: 'var(--text-main)',
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
}}
title="点击切换深色 / 浅色主题"
>
{resolvedTheme === 'dark' ? (
<>
<Moon className="w-3.5 h-3.5" />
<span className="hidden sm:inline">深色</span>
</>
) : (
<>
<Sun className="w-3.5 h-3.5" />
<span className="hidden sm:inline">浅色</span>
</>
)}
</button>
{theme !== 'system' && (
<button
onClick={() => setTheme('system')}
className="px-2 py-1 rounded-md text-[11px] transition-all cursor-pointer"
style={{ color: 'var(--text-muted)' }}
title="恢复跟随设备系统深浅色自动切换"
>
自动
</button>
)}
</div>
</div> </div>
</div> </div>
</header> </header>