fixes and architecture
This commit is contained in:
1 parent
ce98f270d5
commit
dd43fa04c1
104 files changed
+7098
-5198
No files matched your search
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { FiChevronDown, FiCheck } from "react-icons/fi";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
import { SORT_OPTIONS } from "./sortOptions";
|
||||
|
||||
interface Props { value: string; onChange: (v: string) => void; }
|
||||
|
||||
export function SortDropdown({ value, onChange }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { isDark } = useTheme();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const active = SORT_OPTIONS.find((o) => o.value === value) ?? SORT_OPTIONS[0];
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, []);
|
||||
|
||||
const triggerCl = isDark
|
||||
? "bg-slate-900 border-slate-700 text-slate-200 hover:border-violet-500/50"
|
||||
: "bg-white border-slate-200 text-slate-700 hover:border-violet-400";
|
||||
const menuCl = isDark
|
||||
? "bg-slate-900 border-slate-700 shadow-xl shadow-black/40"
|
||||
: "bg-white border-slate-200 shadow-xl shadow-slate-200/60";
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
<button
|
||||
onClick={() => setOpen((p) => !p)}
|
||||
className={`flex items-center gap-2 h-9 pl-3 pr-2.5 rounded-xl border text-xs font-medium transition-all ${triggerCl} ${open ? "border-violet-500/60" : ""}`}
|
||||
>
|
||||
<active.icon className={`text-sm shrink-0 ${isDark ? "text-violet-400" : "text-violet-500"}`} />
|
||||
<span className="whitespace-nowrap">{active.label}</span>
|
||||
<FiChevronDown className={`text-slate-400 text-xs shrink-0 transition-transform duration-200 ${open ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className={`absolute right-0 top-full mt-1.5 z-50 min-w-[188px] rounded-2xl border overflow-hidden ${menuCl}`}>
|
||||
<div className="py-1.5">
|
||||
{SORT_OPTIONS.map((opt) => {
|
||||
const isActive = opt.value === value;
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => { onChange(opt.value); setOpen(false); }}
|
||||
className={`w-full flex items-center gap-3 px-3.5 py-2.5 text-xs font-medium transition-colors text-left ${
|
||||
isActive
|
||||
? isDark ? "bg-violet-600/20 text-violet-300" : "bg-violet-50 text-violet-700"
|
||||
: isDark ? "text-slate-300 hover:bg-slate-800" : "text-slate-600 hover:bg-slate-50"
|
||||
}`}
|
||||
>
|
||||
<opt.icon className={`text-sm shrink-0 ${isActive ? (isDark ? "text-violet-400" : "text-violet-500") : "text-slate-400"}`} />
|
||||
<span className="flex-1">{opt.label}</span>
|
||||
{isActive && <FiCheck className={`text-xs shrink-0 ${isDark ? "text-violet-400" : "text-violet-500"}`} />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user