File size: 1,662 Bytes
db764ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | import { useState, useRef, useEffect } from "react";
interface Option {
value: string;
label: string;
}
interface Props {
options: Option[];
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export default function Select({ options, value, onChange, placeholder }: Props) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const selected = options.find((o) => o.value === value);
return (
<div className="custom-select" ref={ref}>
<button
className="custom-select-trigger"
onClick={() => setOpen(!open)}
type="button"
>
<span>{selected?.label || placeholder || "Select..."}</span>
<span className="custom-select-arrow">{open ? "\u25b4" : "\u25be"}</span>
</button>
{open && (
<div className="custom-select-dropdown">
{options.map((opt) => (
<button
key={opt.value}
className={`custom-select-option ${opt.value === value ? "custom-select-option-active" : ""}`}
onClick={() => {
onChange(opt.value);
setOpen(false);
}}
type="button"
>
{opt.label}
</button>
))}
</div>
)}
</div>
);
}
|