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:
parent
3f42dc2879
commit
ebd535f106
4 changed files with 184 additions and 2 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
52
src/components/ThemeSwitcher.tsx
Normal file
52
src/components/ThemeSwitcher.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
68
src/hooks/use-theme.ts
Normal 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 };
|
||||
}
|
||||
Loading…
Reference in a new issue