Seth0330's picture
Create frontend/src/components/ui/select.jsx
550be99 verified
import React, {
createContext,
useContext,
useState,
useRef,
useEffect,
} from "react";
import { cn } from "@/lib/utils";
const SelectContext = createContext(null);
export function Select({ value, onValueChange, children }) {
const [open, setOpen] = useState(false);
const [items, setItems] = useState({});
const triggerRef = useRef(null);
// Close on outside click
useEffect(() => {
if (!open) return;
function handleClick(e) {
if (!triggerRef.current) return;
if (!triggerRef.current.parentElement.contains(e.target)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, [open]);
const registerItem = (val, label) => {
setItems((prev) => ({ ...prev, [val]: label }));
};
return (
<SelectContext.Provider
value={{
value,
onValueChange,
open,
setOpen,
items,
registerItem,
triggerRef,
}}
>
<div className="relative inline-block">{children}</div>
</SelectContext.Provider>
);
}
export function SelectTrigger({ className, children }) {
const { setOpen, triggerRef } = useContext(SelectContext);
return (
<button
type="button"
ref={triggerRef}
onClick={() => setOpen((o) => !o)}
className={cn(
"flex items-center justify-between rounded-md border bg-white px-3 py-2 text-sm text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500",
className
)}
>
{children}
</button>
);
}
export function SelectValue({ placeholder }) {
const { value, items } = useContext(SelectContext);
const label = value ? items[value] : null;
return (
<span className={cn("truncate text-sm", !label && "text-slate-400")}>
{label || placeholder}
</span>
);
}
export function SelectContent({ className, children }) {
const { open } = useContext(SelectContext);
if (!open) return null;
return (
<div
className={cn(
"absolute z-50 mt-2 min-w-[8rem] rounded-md border border-slate-200 bg-white shadow-lg",
className
)}
>
{children}
</div>
);
}
export function SelectItem({ value, children, className }) {
const { onValueChange, setOpen, registerItem } = useContext(SelectContext);
useEffect(() => {
registerItem(value, typeof children === "string" ? children : String(children));
}, [value, children, registerItem]);
const handleClick = () => {
onValueChange?.(value);
setOpen(false);
};
return (
<div
onClick={handleClick}
className={cn(
"cursor-pointer select-none px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-100",
className
)}
>
{children}
</div>
);
}