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