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;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
/* Default Dark Theme */
|
||||||
|
:root, .theme-dark {
|
||||||
--background: #0a0a0f;
|
--background: #0a0a0f;
|
||||||
--surface: #1a1a1f;
|
--surface: #1a1a1f;
|
||||||
--primary: #8b5cf6;
|
--primary: #8b5cf6;
|
||||||
|
|
@ -14,6 +15,64 @@
|
||||||
--secondary: #ec4899;
|
--secondary: #ec4899;
|
||||||
--accent: #06b6d4;
|
--accent: #06b6d4;
|
||||||
--border: #2a2a2f;
|
--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 {
|
body {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
color: white;
|
color: var(--foreground);
|
||||||
font-family: var(--font-inter), 'Inter', sans-serif;
|
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 { toast } from 'sonner';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { ThemeSwitcher } from './ThemeSwitcher';
|
||||||
|
|
||||||
interface ToolbarProps {
|
interface ToolbarProps {
|
||||||
code: string;
|
code: string;
|
||||||
|
|
@ -138,6 +139,8 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>About</TooltipContent>
|
<TooltipContent>About</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile: Hamburger menu with essential actions */}
|
{/* 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