Seth0330 commited on
Commit
550be99
·
verified ·
1 Parent(s): 9a66b73

Create frontend/src/components/ui/select.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/components/ui/select.jsx +116 -0
frontend/src/components/ui/select.jsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useRef,
6
+ useEffect,
7
+ } from "react";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ const SelectContext = createContext(null);
11
+
12
+ export function Select({ value, onValueChange, children }) {
13
+ const [open, setOpen] = useState(false);
14
+ const [items, setItems] = useState({});
15
+ const triggerRef = useRef(null);
16
+
17
+ // Close on outside click
18
+ useEffect(() => {
19
+ if (!open) return;
20
+ function handleClick(e) {
21
+ if (!triggerRef.current) return;
22
+ if (!triggerRef.current.parentElement.contains(e.target)) {
23
+ setOpen(false);
24
+ }
25
+ }
26
+ document.addEventListener("mousedown", handleClick);
27
+ return () => document.removeEventListener("mousedown", handleClick);
28
+ }, [open]);
29
+
30
+ const registerItem = (val, label) => {
31
+ setItems((prev) => ({ ...prev, [val]: label }));
32
+ };
33
+
34
+ return (
35
+ <SelectContext.Provider
36
+ value={{
37
+ value,
38
+ onValueChange,
39
+ open,
40
+ setOpen,
41
+ items,
42
+ registerItem,
43
+ triggerRef,
44
+ }}
45
+ >
46
+ <div className="relative inline-block">{children}</div>
47
+ </SelectContext.Provider>
48
+ );
49
+ }
50
+
51
+ export function SelectTrigger({ className, children }) {
52
+ const { setOpen, triggerRef } = useContext(SelectContext);
53
+ return (
54
+ <button
55
+ type="button"
56
+ ref={triggerRef}
57
+ onClick={() => setOpen((o) => !o)}
58
+ className={cn(
59
+ "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",
60
+ className
61
+ )}
62
+ >
63
+ {children}
64
+ </button>
65
+ );
66
+ }
67
+
68
+ export function SelectValue({ placeholder }) {
69
+ const { value, items } = useContext(SelectContext);
70
+ const label = value ? items[value] : null;
71
+ return (
72
+ <span className={cn("truncate text-sm", !label && "text-slate-400")}>
73
+ {label || placeholder}
74
+ </span>
75
+ );
76
+ }
77
+
78
+ export function SelectContent({ className, children }) {
79
+ const { open } = useContext(SelectContext);
80
+ if (!open) return null;
81
+ return (
82
+ <div
83
+ className={cn(
84
+ "absolute z-50 mt-2 min-w-[8rem] rounded-md border border-slate-200 bg-white shadow-lg",
85
+ className
86
+ )}
87
+ >
88
+ {children}
89
+ </div>
90
+ );
91
+ }
92
+
93
+ export function SelectItem({ value, children, className }) {
94
+ const { onValueChange, setOpen, registerItem } = useContext(SelectContext);
95
+
96
+ useEffect(() => {
97
+ registerItem(value, typeof children === "string" ? children : String(children));
98
+ }, [value, children, registerItem]);
99
+
100
+ const handleClick = () => {
101
+ onValueChange?.(value);
102
+ setOpen(false);
103
+ };
104
+
105
+ return (
106
+ <div
107
+ onClick={handleClick}
108
+ className={cn(
109
+ "cursor-pointer select-none px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-100",
110
+ className
111
+ )}
112
+ >
113
+ {children}
114
+ </div>
115
+ );
116
+ }