feat(frontend): integrate ThemeSwitcher, restore custom palette on page load

This commit is contained in:
ZhenYi 2026-04-20 19:33:04 +08:00
parent ce29eb3062
commit 7736869fc4
2 changed files with 109 additions and 78 deletions

View File

@ -10,9 +10,24 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import {BookOpen, Box, ChevronDown, Compass, Home, LayoutGrid, Monitor, Moon, Plus, Sun, Users} from 'lucide-react'; import {
BookOpen,
Box,
ChevronDown,
Compass,
Home,
LayoutGrid,
Monitor,
Moon,
Plus,
Sliders,
Sun,
Users
} from 'lucide-react';
import {useNavigate} from 'react-router-dom'; import {useNavigate} from 'react-router-dom';
import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar'; import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar';
import {useState} from 'react';
import {ThemeSwitcher} from '@/components/room/ThemeSwitcher';
const btnClass = 'flex w-full h-9 justify-start items-center rounded-md font-medium hover:bg-muted cursor-pointer bg-transparent border-0 text-left text-sm'; const btnClass = 'flex w-full h-9 justify-start items-center rounded-md font-medium hover:bg-muted cursor-pointer bg-transparent border-0 text-left text-sm';
@ -22,10 +37,10 @@ export function SidebarSystem({collapsed}: {collapsed: boolean}) {
const workspaceCtx = tryUseWorkspace(); const workspaceCtx = tryUseWorkspace();
const workspaces = workspaceCtx?.workspaces; const workspaces = workspaceCtx?.workspaces;
const currentWorkspace = workspaceCtx?.currentWorkspace; const currentWorkspace = workspaceCtx?.currentWorkspace;
const [themeSheetOpen, setThemeSheetOpen] = useState(false);
return ( return (
<div className="w-full"> <div className="w-full">
{/* Workspace switcher — only shown when inside WorkspaceProvider */}
{workspaceCtx && ( {workspaceCtx && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger <DropdownMenuTrigger
@ -88,29 +103,33 @@ export function SidebarSystem({collapsed}: {collapsed: boolean}) {
</DropdownMenu> </DropdownMenu>
)} )}
<button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')} onClick={() => navigate('/')}> <button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
<span className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}> onClick={() => navigate('/')}>
<span className={cn('flex h-6 w-6 items-center justify-center shrink-0')}>
<Home className="h-4 w-4"/> <Home className="h-4 w-4"/>
</span> </span>
{!collapsed && <span className="text-sm leading-none">Home</span>} {!collapsed && <span className="text-sm leading-none">Home</span>}
</button> </button>
<button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')} onClick={() => navigate('/explore')}> <button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
<span className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}> onClick={() => navigate('/explore')}>
<span className={cn('flex h-6 w-6 items-center justify-center shrink-0')}>
<Compass className="h-4 w-4"/> <Compass className="h-4 w-4"/>
</span> </span>
{!collapsed && <span className="text-sm leading-none">Explore</span>} {!collapsed && <span className="text-sm leading-none">Explore</span>}
</button> </button>
<button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')} onClick={() => navigate('/market')}> <button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
<span className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}> onClick={() => navigate('/market')}>
<span className={cn('flex h-6 w-6 items-center justify-center shrink-0')}>
<Box className="h-4 w-4"/> <Box className="h-4 w-4"/>
</span> </span>
{!collapsed && <span className="text-sm leading-none">Marketplace</span>} {!collapsed && <span className="text-sm leading-none">Marketplace</span>}
</button> </button>
<button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')} onClick={() => window.open('/docs', '_blank')}> <button type="button" className={cn(btnClass, collapsed ? 'justify-center px-0' : 'px-2')}
<span className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}> onClick={() => window.open('/docs', '_blank')}>
<span className={cn('flex h-6 w-6 items-center justify-center shrink-0')}>
<BookOpen className="h-4 w-4"/> <BookOpen className="h-4 w-4"/>
</span> </span>
{!collapsed && <span className="text-sm leading-none">Docs</span>} {!collapsed && <span className="text-sm leading-none">Docs</span>}
@ -125,7 +144,7 @@ export function SidebarSystem({collapsed}: {collapsed: boolean}) {
/> />
} }
> >
<span className={cn('flex h-6 items-center shrink-0', collapsed ? 'w-6 justify-center' : 'w-6')}> <span className={cn('flex h-6 w-6 items-center justify-center shrink-0')}>
{theme === 'dark' ? ( {theme === 'dark' ? (
<Moon className="h-4 w-4"/> <Moon className="h-4 w-4"/>
) : theme === 'light' ? ( ) : theme === 'light' ? (
@ -139,7 +158,12 @@ export function SidebarSystem({collapsed}: {collapsed: boolean}) {
<DropdownMenuContent side="right" align="start"> <DropdownMenuContent side="right" align="start">
{!collapsed && ( {!collapsed && (
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuLabel>Theme Settings</DropdownMenuLabel> <DropdownMenuLabel>Theme</DropdownMenuLabel>
<DropdownMenuItem onClick={() => setThemeSheetOpen(true)}>
<Sliders className="mr-2 h-4 w-4"/>
<span>Design System</span>
</DropdownMenuItem>
<DropdownMenuSeparator/>
<DropdownMenuItem onClick={() => setTheme('light')}> <DropdownMenuItem onClick={() => setTheme('light')}>
<Sun className="mr-2 h-4 w-4"/> <Sun className="mr-2 h-4 w-4"/>
<span>Light</span> <span>Light</span>
@ -155,20 +179,13 @@ export function SidebarSystem({collapsed}: {collapsed: boolean}) {
</DropdownMenuGroup> </DropdownMenuGroup>
)} )}
{collapsed && ( {collapsed && (
<> <DropdownMenuItem onClick={() => setThemeSheetOpen(true)}>
<DropdownMenuItem onClick={() => setTheme('light')}> <Sliders className="mr-2 h-4 w-4"/>
<Sun className="mr-2 h-4 w-4" />
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
<Moon className="mr-2 h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
<Monitor className="mr-2 h-4 w-4" />
</DropdownMenuItem>
</>
)} )}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<ThemeSwitcher open={themeSheetOpen} onOpenChange={setThemeSheetOpen}/>
</div> </div>
); );
} }

View File

@ -6,6 +6,20 @@ import {UserProvider} from '@/contexts';
import {ThemeProvider} from '@/contexts/theme-context'; import {ThemeProvider} from '@/contexts/theme-context';
import './index.css'; import './index.css';
import App from './App.tsx'; import App from './App.tsx';
import {applyPaletteToDOM, loadActivePresetId} from '@/components/room/design-system';
// Restore custom palette on page load (before first render)
const activePreset = loadActivePresetId();
if (activePreset === 'custom') {
const customPalette = localStorage.getItem('theme-custom-palette');
if (customPalette) {
try {
applyPaletteToDOM(JSON.parse(customPalette));
} catch {
// ignore malformed stored palette
}
}
}
const queryClient = new QueryClient(); const queryClient = new QueryClient();