rag-kb-system / src /components /ThemeSwitcher.tsx
duqing2026's picture
同步 hf
9ed89c8
"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>
);
}