178 lines
7.1 KiB
TypeScript
178 lines
7.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Moon, Sun, Palette } from "lucide-react";
|
|
|
|
interface ThemeOption {
|
|
name: string;
|
|
label: string;
|
|
colors: {
|
|
background: string;
|
|
card: string;
|
|
foreground: string;
|
|
primaryCta: string;
|
|
secondaryCta: string;
|
|
accent: string;
|
|
backgroundAccent: string;
|
|
};
|
|
}
|
|
|
|
const PRESET_THEMES: Record<string, ThemeOption> = {
|
|
light: {
|
|
name: "light", label: "Light Mode", colors: {
|
|
background: "#ffffff", card: "#f9f9f9", foreground: "#000612e6", primaryCta: "#15479c", secondaryCta: "#f9f9f9", accent: "#e2e2e2", backgroundAccent: "#c4c4c4"},
|
|
},
|
|
dark: {
|
|
name: "dark", label: "Dark Mode", colors: {
|
|
background: "#0a0a0a", card: "#1a1a1a", foreground: "#ffffffe6", primaryCta: "#e6e6e6", secondaryCta: "#1a1a1a", accent: "#737373", backgroundAccent: "#737373"},
|
|
},
|
|
darkBlue: {
|
|
name: "darkBlue", label: "Dark Blue", colors: {
|
|
background: "#010912", card: "#152840", foreground: "#e6f0ff", primaryCta: "#cee7ff", secondaryCta: "#0e1a29", accent: "#3f5c79", backgroundAccent: "#004a93"},
|
|
},
|
|
emerald: {
|
|
name: "emerald", label: "Emerald", colors: {
|
|
background: "#000000", card: "#1f4035", foreground: "#ffffff", primaryCta: "#ffffff", secondaryCta: "#0d2b1f", accent: "#0d5238", backgroundAccent: "#10b981"},
|
|
},
|
|
violet: {
|
|
name: "violet", label: "Violet", colors: {
|
|
background: "#030128", card: "#241f48", foreground: "#ffffff", primaryCta: "#ffffff", secondaryCta: "#131136", accent: "#44358a", backgroundAccent: "#b597fe"},
|
|
},
|
|
ruby: {
|
|
name: "ruby", label: "Ruby", colors: {
|
|
background: "#000000", card: "#481f1f", foreground: "#ffffff", primaryCta: "#ffffff", secondaryCta: "#361311", accent: "#51000b", backgroundAccent: "#ff2231"},
|
|
},
|
|
};
|
|
|
|
export default function ThemeSwitcher() {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [currentTheme, setCurrentTheme] = useState<string>("light");
|
|
const [customColors, setCustomColors] = useState<Record<string, string> | null>(null);
|
|
const [showCustom, setShowCustom] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem("theme");
|
|
const savedCustom = localStorage.getItem("customTheme");
|
|
if (saved) setCurrentTheme(saved);
|
|
if (savedCustom) {
|
|
setCustomColors(JSON.parse(savedCustom));
|
|
setShowCustom(true);
|
|
}
|
|
}, []);
|
|
|
|
const applyTheme = (theme: string) => {
|
|
const themeOption = PRESET_THEMES[theme];
|
|
if (themeOption) {
|
|
Object.entries(themeOption.colors).forEach(([key, value]) => {
|
|
const cssKey = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
|
|
document.documentElement.style.setProperty(cssKey, value);
|
|
});
|
|
setCurrentTheme(theme);
|
|
setShowCustom(false);
|
|
localStorage.setItem("theme", theme);
|
|
localStorage.removeItem("customTheme");
|
|
}
|
|
};
|
|
|
|
const applyCustomTheme = (colors: Record<string, string>) => {
|
|
Object.entries(colors).forEach(([key, value]) => {
|
|
const cssKey = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
|
|
if (value) document.documentElement.style.setProperty(cssKey, value);
|
|
});
|
|
setCustomColors(colors);
|
|
setShowCustom(true);
|
|
localStorage.setItem("customTheme", JSON.stringify(colors));
|
|
};
|
|
|
|
const handleColorChange = (
|
|
colorKey: string,
|
|
value: string
|
|
) => {
|
|
const updated = { ...customColors, [colorKey]: value };
|
|
applyCustomTheme(updated);
|
|
};
|
|
|
|
return (
|
|
<div className="fixed bottom-6 right-6 z-50">
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="w-12 h-12 rounded-full bg-primary-cta text-white flex items-center justify-center shadow-lg hover:shadow-xl transition-shadow"
|
|
aria-label="Toggle theme switcher"
|
|
>
|
|
<Palette size={20} />
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute bottom-16 right-0 bg-card border border-accent rounded-lg shadow-xl p-4 w-80 max-h-96 overflow-y-auto">
|
|
<div className="space-y-3">
|
|
<h3 className="text-sm font-semibold text-foreground mb-4">Select Theme</h3>
|
|
|
|
{/* Preset themes */}
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{Object.entries(PRESET_THEMES).map(([key, theme]) => (
|
|
<button
|
|
key={key}
|
|
onClick={() => applyTheme(key)}
|
|
className={`p-2 rounded text-xs font-medium transition-all ${
|
|
currentTheme === key && !showCustom
|
|
? "bg-primary-cta text-white"
|
|
: "bg-background text-foreground border border-accent hover:border-primary-cta"
|
|
}`}
|
|
>
|
|
{theme.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Custom theme toggle */}
|
|
<button
|
|
onClick={() => setShowCustom(!showCustom)}
|
|
className={`w-full p-2 rounded text-sm font-medium transition-all ${
|
|
showCustom
|
|
? "bg-accent text-foreground"
|
|
: "bg-background text-foreground border border-accent hover:border-accent"
|
|
}`}
|
|
>
|
|
{showCustom ? "Hide Custom" : "Create Custom"}
|
|
</button>
|
|
|
|
{/* Custom color picker */}
|
|
{showCustom && (
|
|
<div className="space-y-3 pt-3 border-t border-accent">
|
|
<div className="text-xs font-semibold text-foreground">Custom Colors</div>
|
|
{[
|
|
{ key: "background", label: "Background" },
|
|
{ key: "card", label: "Card" },
|
|
{ key: "foreground", label: "Foreground" },
|
|
{ key: "primaryCta", label: "Primary CTA" },
|
|
{ key: "secondaryCta", label: "Secondary CTA" },
|
|
{ key: "accent", label: "Accent" },
|
|
{ key: "backgroundAccent", label: "Background Accent" },
|
|
].map(({ key, label }) => (
|
|
<div key={key} className="flex items-center gap-2">
|
|
<label className="text-xs text-foreground w-24">{label}</label>
|
|
<input
|
|
type="color"
|
|
value={customColors?.[key] || "#000000"}
|
|
onChange={(e) => handleColorChange(key, e.target.value)}
|
|
className="w-8 h-8 rounded cursor-pointer"
|
|
aria-label={`Set ${label} color`}
|
|
/>
|
|
<input
|
|
type="text"
|
|
value={customColors?.[key] || "#000000"}
|
|
onChange={(e) => handleColorChange(key, e.target.value)}
|
|
className="flex-1 px-2 py-1 text-xs bg-background border border-accent rounded text-foreground"
|
|
placeholder="#000000"
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|