| 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> | |
| ); | |
| } | |