Spaces:
Sleeping
Sleeping
File size: 2,858 Bytes
550be99 |
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
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>
);
}
|