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