Spaces:
Build error
Build error
| "use client"; | |
| import { useTheme, Theme } from "@/contexts/ThemeContext"; | |
| import { Check, Palette } from "lucide-react"; | |
| import { useState, useRef, useEffect, ReactNode } from "react"; | |
| interface ThemeSwitcherProps { | |
| customTrigger?: ReactNode; | |
| position?: 'top' | 'bottom' | 'right'; | |
| isPopover?: boolean; | |
| label?: string; | |
| className?: string; | |
| } | |
| export function ThemeSwitcher({ customTrigger, position = 'top', isPopover, label, className }: ThemeSwitcherProps) { | |
| const { theme, setTheme } = useTheme(); | |
| const [isOpen, setIsOpen] = useState(false); | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| const themes: { id: Theme; color: string; label: string }[] = [ | |
| { id: 'zinc', color: 'bg-zinc-500', label: '默' }, | |
| { id: 'green', color: 'bg-emerald-500', label: '绿' }, | |
| { id: 'blue', color: 'bg-sky-500', label: '蓝' }, | |
| { id: 'violet', color: 'bg-violet-500', label: '紫' }, | |
| { id: 'amber', color: 'bg-amber-500', label: '暖' }, | |
| ]; | |
| useEffect(() => { | |
| function handleClickOutside(event: MouseEvent) { | |
| if (containerRef.current && !containerRef.current.contains(event.target as Node)) { | |
| setIsOpen(false); | |
| } | |
| } | |
| document.addEventListener("mousedown", handleClickOutside); | |
| return () => document.removeEventListener("mousedown", handleClickOutside); | |
| }, []); | |
| return ( | |
| <div className="relative" ref={containerRef}> | |
| {customTrigger ? ( | |
| <div onClick={(e) => { | |
| e.stopPropagation(); | |
| setIsOpen(!isOpen); | |
| }}> | |
| {customTrigger} | |
| </div> | |
| ) : isPopover ? ( | |
| <div | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setIsOpen(!isOpen); | |
| }} | |
| className={className} | |
| > | |
| <div className="flex items-center gap-3"> | |
| <span className="w-6 h-6 flex items-center justify-center shrink-0"> | |
| <Palette className="w-5 h-5" /> | |
| </span> | |
| <span>{label || '主题风格'}</span> | |
| </div> | |
| </div> | |
| ) : ( | |
| <button | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setIsOpen(!isOpen); | |
| }} | |
| className={`w-8 h-8 flex items-center justify-center rounded-lg hover:bg-gray-200 text-primary-600 hover:text-primary-700 transition-colors cursor-pointer ${isOpen ? 'bg-gray-200 text-primary-900' : ''}`} | |
| title="切换主题" | |
| > | |
| <Palette className="w-5 h-5" /> | |
| </button> | |
| )} | |
| {isOpen && ( | |
| <div | |
| className={`absolute bg-white rounded-xl shadow-xl border border-gray-100 flex flex-col gap-2 z-50 min-w-[160px] p-3 | |
| ${position === 'top' ? 'left-1/2 -translate-x-1/2 bottom-full mb-2' : ''} | |
| ${position === 'bottom' ? 'left-1/2 -translate-x-1/2 top-full mt-2' : ''} | |
| ${position === 'right' ? 'left-full top-0 ml-2' : ''} | |
| `} | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div className="text-xs font-medium text-gray-500 px-1">{label || '主题风格'}</div> | |
| <div className="flex justify-between items-center gap-1"> | |
| {themes.map((t) => ( | |
| <button | |
| key={t.id} | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setTheme(t.id); | |
| // Optional: Close on select? Maybe not, so user can try different ones. | |
| }} | |
| className={`w-6 h-6 rounded-full flex items-center justify-center transition-all ${t.color} ${ | |
| theme === t.id ? 'ring-2 ring-offset-2 ring-gray-400 scale-110' : 'opacity-70 hover:opacity-100 hover:scale-110' | |
| }`} | |
| title={t.label} | |
| > | |
| {theme === t.id && <Check className="w-3.5 h-3.5 text-white" />} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |