Add theme customization system with 5 themes

Implemented comprehensive theme switching:
- 5 beautiful themes: Dark, Light, Synthwave, Forest, Ocean
- Persistent theme preference in localStorage
- Theme switcher in toolbar with descriptions
- Custom CSS variables for each theme
- Smooth theme transitions
- Mobile-friendly theme selector

Users can now customize their IDE appearance to match their preference.
This commit is contained in:
Claude 2026-01-17 22:24:34 +00:00
parent 3f42dc2879
commit ebd535f106
No known key found for this signature in database
4 changed files with 184 additions and 2 deletions

View file

@ -5,7 +5,8 @@
@tailwind utilities;
@layer base {
:root {
/* Default Dark Theme */
:root, .theme-dark {
--background: #0a0a0f;
--surface: #1a1a1f;
--primary: #8b5cf6;
@ -14,6 +15,64 @@
--secondary: #ec4899;
--accent: #06b6d4;
--border: #2a2a2f;
--foreground: #ffffff;
--muted: #6b7280;
}
/* Light Theme */
.theme-light {
--background: #ffffff;
--surface: #f9fafb;
--primary: #7c3aed;
--primary-light: #8b5cf6;
--primary-dark: #6d28d9;
--secondary: #db2777;
--accent: #0891b2;
--border: #e5e7eb;
--foreground: #111827;
--muted: #6b7280;
}
/* Synthwave Theme */
.theme-synthwave {
--background: #2b213a;
--surface: #241b2f;
--primary: #ff6ac1;
--primary-light: #ff8ad8;
--primary-dark: #ff4aaa;
--secondary: #9d72ff;
--accent: #72f1b8;
--border: #495495;
--foreground: #f8f8f2;
--muted: #a599e9;
}
/* Forest Theme */
.theme-forest {
--background: #0d1b1e;
--surface: #1a2f33;
--primary: #2dd4bf;
--primary-light: #5eead4;
--primary-dark: #14b8a6;
--secondary: #34d399;
--accent: #a7f3d0;
--border: #234e52;
--foreground: #ecfdf5;
--muted: #6ee7b7;
}
/* Ocean Theme */
.theme-ocean {
--background: #0c1821;
--surface: #1b2838;
--primary: #3b82f6;
--primary-light: #60a5fa;
--primary-dark: #2563eb;
--secondary: #06b6d4;
--accent: #38bdf8;
--border: #1e3a5f;
--foreground: #dbeafe;
--muted: #7dd3fc;
}
* {
@ -22,7 +81,7 @@
body {
background-color: var(--background);
color: white;
color: var(--foreground);
font-family: var(--font-inter), 'Inter', sans-serif;
}

View file

@ -0,0 +1,52 @@
import { Palette } from '@phosphor-icons/react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useTheme, Theme } from '@/hooks/use-theme';
import { Check } from '@phosphor-icons/react';
export function ThemeSwitcher() {
const { theme, setTheme, themes } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
aria-label="Change theme"
>
<Palette size={18} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel className="text-xs">Choose Theme</DropdownMenuLabel>
<DropdownMenuSeparator />
{Object.entries(themes).map(([key, config]) => (
<DropdownMenuItem
key={key}
onClick={() => setTheme(key as Theme)}
className="flex items-start gap-2 cursor-pointer"
>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium text-sm">{config.label}</span>
{theme === key && <Check size={14} className="text-accent" weight="bold" />}
</div>
<p className="text-xs text-muted-foreground mt-0.5">
{config.description}
</p>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View file

@ -12,6 +12,7 @@ import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List }
import { toast } from 'sonner';
import { useState, useEffect } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ThemeSwitcher } from './ThemeSwitcher';
interface ToolbarProps {
code: string;
@ -138,6 +139,8 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
</TooltipTrigger>
<TooltipContent>About</TooltipContent>
</Tooltip>
<ThemeSwitcher />
</div>
{/* Mobile: Hamburger menu with essential actions */}

68
src/hooks/use-theme.ts Normal file
View file

@ -0,0 +1,68 @@
import { useEffect, useState } from 'react';
export type Theme = 'dark' | 'light' | 'synthwave' | 'forest' | 'ocean';
interface ThemeConfig {
name: string;
label: string;
description: string;
}
export const themes: Record<Theme, ThemeConfig> = {
dark: {
name: 'dark',
label: 'Dark',
description: 'Classic dark theme for comfortable coding',
},
light: {
name: 'light',
label: 'Light',
description: 'Clean light theme for bright environments',
},
synthwave: {
name: 'synthwave',
label: 'Synthwave',
description: 'Retro neon aesthetic with vibrant colors',
},
forest: {
name: 'forest',
label: 'Forest',
description: 'Calming green tones inspired by nature',
},
ocean: {
name: 'ocean',
label: 'Ocean',
description: 'Deep blue theme for focused work',
},
};
export function useTheme() {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window === 'undefined') return 'dark';
const stored = localStorage.getItem('aethex-theme');
return (stored as Theme) || 'dark';
});
useEffect(() => {
if (typeof window === 'undefined') return;
const root = document.documentElement;
// Remove all theme classes
Object.keys(themes).forEach((t) => {
root.classList.remove(`theme-${t}`);
});
// Add current theme class
root.classList.add(`theme-${theme}`);
// Save to localStorage
localStorage.setItem('aethex-theme', theme);
}, [theme]);
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
};
return { theme, setTheme, themes };
}