@srcroot/ui 0.0.55 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +151 -151
  2. package/dist/index.d.ts +0 -0
  3. package/dist/index.js +120 -93
  4. package/package.json +7 -2
  5. package/src/registry/analytics/google-analytics.tsx +36 -39
  6. package/src/registry/analytics/google-tag-manager.tsx +62 -65
  7. package/src/registry/analytics/meta-pixel.tsx +44 -47
  8. package/src/registry/analytics/microsoft-clarity.tsx +31 -34
  9. package/src/registry/analytics/tiktok-pixel.tsx +34 -37
  10. package/src/registry/lib/utils.ts +0 -0
  11. package/src/registry/themes/v3/blue.css +157 -157
  12. package/src/registry/themes/v3/glass.css +153 -153
  13. package/src/registry/themes/v3/gray.css +157 -157
  14. package/src/registry/themes/v3/green.css +157 -157
  15. package/src/registry/themes/v3/neutral.css +157 -157
  16. package/src/registry/themes/v3/orange.css +157 -157
  17. package/src/registry/themes/v3/rose.css +157 -157
  18. package/src/registry/themes/v3/slate.css +157 -157
  19. package/src/registry/themes/v3/stone.css +157 -157
  20. package/src/registry/themes/v3/violet.css +186 -186
  21. package/src/registry/themes/v3/zinc.css +157 -157
  22. package/src/registry/themes/v4/blue.css +184 -184
  23. package/src/registry/themes/v4/glass.css +180 -180
  24. package/src/registry/themes/v4/gray.css +184 -184
  25. package/src/registry/themes/v4/green.css +184 -184
  26. package/src/registry/themes/v4/neutral.css +184 -184
  27. package/src/registry/themes/v4/orange.css +184 -184
  28. package/src/registry/themes/v4/rose.css +184 -184
  29. package/src/registry/themes/v4/slate.css +184 -184
  30. package/src/registry/themes/v4/stone.css +184 -184
  31. package/src/registry/themes/v4/violet.css +184 -184
  32. package/src/registry/themes/v4/zinc.css +184 -184
  33. package/src/registry/ui/accordion.tsx +164 -165
  34. package/src/registry/ui/alert-dialog.tsx +213 -214
  35. package/src/registry/ui/alert.tsx +73 -76
  36. package/src/registry/ui/aspect-ratio.tsx +44 -47
  37. package/src/registry/ui/avatar.tsx +96 -97
  38. package/src/registry/ui/badge.tsx +52 -55
  39. package/src/registry/ui/breadcrumb.tsx +147 -150
  40. package/src/registry/ui/button-group.tsx +64 -67
  41. package/src/registry/ui/button.tsx +71 -72
  42. package/src/registry/ui/calendar.tsx +514 -515
  43. package/src/registry/ui/card.tsx +88 -91
  44. package/src/registry/ui/carousel.tsx +214 -214
  45. package/src/registry/ui/chart.tsx +373 -373
  46. package/src/registry/ui/chatbot.tsx +86 -13
  47. package/src/registry/ui/checkbox.tsx +93 -94
  48. package/src/registry/ui/collapsible.tsx +107 -108
  49. package/src/registry/ui/combobox.tsx +171 -171
  50. package/src/registry/ui/command.tsx +300 -300
  51. package/src/registry/ui/container.tsx +44 -47
  52. package/src/registry/ui/context-menu.tsx +221 -221
  53. package/src/registry/ui/date-picker.tsx +228 -228
  54. package/src/registry/ui/dialog.tsx +269 -270
  55. package/src/registry/ui/drawer.tsx +10 -4
  56. package/src/registry/ui/dropdown-menu.tsx +529 -530
  57. package/src/registry/ui/empty-state.tsx +0 -2
  58. package/src/registry/ui/file-upload.tsx +0 -0
  59. package/src/registry/ui/floating-dock.tsx +0 -0
  60. package/src/registry/ui/form-field.tsx +91 -94
  61. package/src/registry/ui/google-analytics.tsx +38 -0
  62. package/src/registry/ui/google-tag-manager.tsx +64 -0
  63. package/src/registry/ui/hover-card.tsx +223 -223
  64. package/src/registry/ui/image.tsx +144 -147
  65. package/src/registry/ui/input-group.tsx +82 -85
  66. package/src/registry/ui/input.tsx +125 -125
  67. package/src/registry/ui/kbd.tsx +60 -63
  68. package/src/registry/ui/label.tsx +36 -37
  69. package/src/registry/ui/loading-spinner.tsx +108 -111
  70. package/src/registry/ui/map.tsx +0 -0
  71. package/src/registry/ui/marquee.tsx +2 -0
  72. package/src/registry/ui/menubar.tsx +246 -246
  73. package/src/registry/ui/meta-pixel.tsx +46 -0
  74. package/src/registry/ui/microsoft-clarity.tsx +33 -0
  75. package/src/registry/ui/native-select.tsx +49 -52
  76. package/src/registry/ui/otp-input.tsx +163 -155
  77. package/src/registry/ui/pagination.tsx +149 -152
  78. package/src/registry/ui/patterns.tsx +28 -0
  79. package/src/registry/ui/popover.tsx +226 -227
  80. package/src/registry/ui/progress.tsx +51 -52
  81. package/src/registry/ui/radio.tsx +99 -102
  82. package/src/registry/ui/resizable.tsx +314 -314
  83. package/src/registry/ui/scroll-animation.tsx +45 -0
  84. package/src/registry/ui/scroll-area.tsx +121 -122
  85. package/src/registry/ui/scroll-to-top.tsx +0 -0
  86. package/src/registry/ui/search.tsx +162 -150
  87. package/src/registry/ui/select.tsx +292 -293
  88. package/src/registry/ui/separator.tsx +46 -47
  89. package/src/registry/ui/sheet.tsx +6 -3
  90. package/src/registry/ui/sidebar.tsx +628 -628
  91. package/src/registry/ui/skeleton.tsx +26 -29
  92. package/src/registry/ui/slider.tsx +196 -197
  93. package/src/registry/ui/slot.tsx +69 -72
  94. package/src/registry/ui/star-rating.tsx +146 -134
  95. package/src/registry/ui/switch.tsx +72 -73
  96. package/src/registry/ui/table-of-contents.tsx +96 -96
  97. package/src/registry/ui/table.tsx +138 -139
  98. package/src/registry/ui/tabs.tsx +124 -125
  99. package/src/registry/ui/text.tsx +61 -64
  100. package/src/registry/ui/textarea.tsx +41 -42
  101. package/src/registry/ui/theme-switcher.tsx +66 -66
  102. package/src/registry/ui/tiktok-pixel.tsx +36 -0
  103. package/src/registry/ui/toast.tsx +97 -98
  104. package/src/registry/ui/toggle-group.tsx +129 -129
  105. package/src/registry/ui/toggle.tsx +72 -72
  106. package/src/registry/ui/tooltip.tsx +143 -144
  107. package/src/registry/ui/whatsapp.tsx +0 -0
@@ -1,293 +1,292 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { createPortal } from "react-dom"
5
- import { cn } from "@/lib/utils"
6
-
7
- interface SelectContextValue {
8
- value: string
9
- onValueChange: (value: string) => void
10
- open: boolean
11
- setOpen: (open: boolean) => void
12
- triggerRef: React.RefObject<HTMLButtonElement | null>
13
- }
14
-
15
- const SelectContext = React.createContext<SelectContextValue | null>(null)
16
-
17
- interface SelectProps {
18
- children: React.ReactNode
19
- value?: string
20
- onValueChange?: (value: string) => void
21
- defaultValue?: string
22
- }
23
-
24
- /**
25
- * Custom Select dropdown with keyboard navigation
26
- *
27
- * @example
28
- * <Select value={value} onValueChange={setValue}>
29
- * <SelectTrigger>
30
- * <SelectValue placeholder="Select option" />
31
- * </SelectTrigger>
32
- * <SelectContent>
33
- * <SelectItem value="option1">Option 1</SelectItem>
34
- * <SelectItem value="option2">Option 2</SelectItem>
35
- * </SelectContent>
36
- * </Select>
37
- */
38
- function Select({ children, value: controlledValue, onValueChange, defaultValue = "" }: SelectProps) {
39
- const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
40
- const [open, setOpen] = React.useState(false)
41
- const triggerRef = React.useRef<HTMLButtonElement>(null)
42
-
43
- const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
44
- const setValue = onValueChange || setUncontrolledValue
45
-
46
- return (
47
- <SelectContext.Provider value={{ value, onValueChange: setValue, open, setOpen, triggerRef }}>
48
- <div className="relative">
49
- {children}
50
- </div>
51
- </SelectContext.Provider>
52
- )
53
- }
54
-
55
- const SelectTrigger = React.forwardRef<
56
- HTMLButtonElement,
57
- React.ButtonHTMLAttributes<HTMLButtonElement>
58
- >(({ className, children, ...props }, ref) => {
59
- const context = React.useContext(SelectContext)
60
- if (!context) throw new Error("SelectTrigger must be used within Select")
61
-
62
- // Combine refs
63
- const combinedRef = (node: HTMLButtonElement | null) => {
64
- (context.triggerRef as any).current = node
65
- if (typeof ref === 'function') ref(node)
66
- else if (ref) ref.current = node
67
- }
68
-
69
- return (
70
- <button
71
- ref={combinedRef}
72
- type="button"
73
- role="combobox"
74
- aria-expanded={context.open}
75
- aria-haspopup="listbox"
76
- className={cn(
77
- "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
78
- className
79
- )}
80
- onClick={() => context.setOpen(!context.open)}
81
- {...props}
82
- >
83
- {children}
84
- <svg
85
- className={cn("h-4 w-4 opacity-50 transition-transform", context.open && "rotate-180")}
86
- fill="none"
87
- viewBox="0 0 24 24"
88
- stroke="currentColor"
89
- strokeWidth={2}
90
- >
91
- <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
92
- </svg>
93
- </button>
94
- )
95
- })
96
- SelectTrigger.displayName = "SelectTrigger"
97
-
98
- interface SelectValueProps {
99
- placeholder?: string
100
- }
101
-
102
- function SelectValue({ placeholder }: SelectValueProps) {
103
- const context = React.useContext(SelectContext)
104
- if (!context) throw new Error("SelectValue must be used within Select")
105
-
106
- return (
107
- <span className={cn(!context.value && "text-muted-foreground")}>
108
- {context.value || placeholder}
109
- </span>
110
- )
111
- }
112
-
113
- interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
114
- sideOffset?: number
115
- portal?: boolean
116
- align?: "start" | "center" | "end"
117
- side?: "top" | "bottom"
118
- }
119
-
120
- const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(
121
- ({ className, children, sideOffset = 4, portal = true, align = "start", side = "bottom", ...props }, ref) => {
122
- const context = React.useContext(SelectContext)
123
- if (!context) throw new Error("SelectContent must be used within Select")
124
- const contentRef = React.useRef<HTMLDivElement>(null)
125
- const [position, setPosition] = React.useState({ top: 0, left: 0 })
126
- const [mounted, setMounted] = React.useState(false)
127
-
128
- React.useEffect(() => {
129
- setMounted(true)
130
- }, [])
131
-
132
- React.useEffect(() => {
133
- const handleClickOutside = (e: MouseEvent) => {
134
- if (context.open) {
135
- const target = e.target as Node
136
- const content = contentRef.current
137
- const trigger = context.triggerRef.current
138
-
139
- if (content?.contains(target) || (trigger && trigger.contains(target))) {
140
- return
141
- }
142
- context.setOpen(false)
143
- }
144
- }
145
-
146
- const handleEscape = (e: KeyboardEvent) => {
147
- if (e.key === "Escape" && context.open) {
148
- context.setOpen(false)
149
- }
150
- }
151
-
152
- const checkPosition = () => {
153
- if (context.open && contentRef.current && context.triggerRef.current) {
154
- const triggerRect = context.triggerRef.current.getBoundingClientRect()
155
- const contentRect = contentRef.current.getBoundingClientRect()
156
- const viewportHeight = window.innerHeight
157
- const viewportWidth = window.innerWidth
158
-
159
- let top = 0
160
- let left = 0
161
-
162
- // Vertical
163
- const spaceBelow = viewportHeight - triggerRect.bottom
164
- const spaceAbove = triggerRect.top
165
- const neededHeight = contentRect.height + sideOffset
166
- const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
167
-
168
- if (onBottom) {
169
- top = triggerRect.bottom + sideOffset
170
- } else {
171
- top = triggerRect.top - contentRect.height - sideOffset
172
- }
173
-
174
- // Horizontal - Select usually matches trigger width or starts aligned
175
- left = triggerRect.left
176
-
177
- // Clamping
178
- if (left < 4) left = 4
179
- if (left + contentRect.width > viewportWidth - 4) {
180
- left = viewportWidth - contentRect.width - 4
181
- }
182
-
183
- // Match width if it fits, or at least min-width of trigger
184
- // For now keeping simple positioning
185
-
186
- setPosition({ top, left })
187
- }
188
- }
189
-
190
- if (context.open) {
191
- requestAnimationFrame(checkPosition)
192
- }
193
-
194
- const timer = setTimeout(() => {
195
- document.addEventListener("mousedown", handleClickOutside)
196
- }, 0)
197
- document.addEventListener("keydown", handleEscape)
198
- window.addEventListener("resize", checkPosition)
199
- window.addEventListener("scroll", checkPosition, true)
200
-
201
- return () => {
202
- clearTimeout(timer)
203
- document.removeEventListener("mousedown", handleClickOutside)
204
- document.removeEventListener("keydown", handleEscape)
205
- window.removeEventListener("resize", checkPosition)
206
- window.removeEventListener("scroll", checkPosition, true)
207
- }
208
- }, [context.open, context, sideOffset])
209
-
210
- if (!context.open) return null
211
- if (portal && !mounted) return null
212
-
213
- const content = (
214
- <div
215
- ref={(node) => {
216
- (contentRef as any).current = node
217
- if (typeof ref === 'function') ref(node)
218
- else if (ref) ref.current = node
219
- }}
220
- role="listbox"
221
- className={cn(
222
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
223
- "animate-in fade-in-0 zoom-in-95",
224
- !portal && "absolute",
225
- !portal && "mt-1",
226
- portal && "fixed",
227
- className
228
- )}
229
- style={{
230
- top: portal ? position.top : undefined,
231
- left: portal ? position.left : undefined,
232
- width: context.triggerRef.current ? context.triggerRef.current.offsetWidth : undefined,
233
- ...props.style
234
- }}
235
- onClick={(e) => e.stopPropagation()}
236
- {...props}
237
- >
238
- {children}
239
- </div>
240
- )
241
-
242
- if (portal) {
243
- return createPortal(content, document.body)
244
- }
245
-
246
- return content
247
- }
248
- )
249
- SelectContent.displayName = "SelectContent"
250
-
251
- interface SelectItemProps extends React.HTMLAttributes<HTMLDivElement> {
252
- value: string
253
- }
254
-
255
- const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
256
- ({ className, children, value, ...props }, ref) => {
257
- const context = React.useContext(SelectContext)
258
- if (!context) throw new Error("SelectItem must be used within Select")
259
-
260
- const isSelected = context.value === value
261
-
262
- return (
263
- <div
264
- ref={ref}
265
- role="option"
266
- aria-selected={isSelected}
267
- className={cn(
268
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
269
- isSelected && "bg-accent text-accent-foreground",
270
- className
271
- )}
272
- onClick={() => {
273
- context.onValueChange(value)
274
- context.setOpen(false)
275
- }}
276
- {...props}
277
- >
278
- {children}
279
- {isSelected && (
280
- <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
281
- <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
282
- <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
283
- </svg>
284
- </span>
285
- )}
286
- </div>
287
- )
288
- }
289
- )
290
- SelectItem.displayName = "SelectItem"
291
-
292
- export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem }
293
-
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { createPortal } from "react-dom"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ interface SelectContextValue {
8
+ value: string
9
+ onValueChange: (value: string) => void
10
+ open: boolean
11
+ setOpen: (open: boolean) => void
12
+ triggerRef: React.RefObject<HTMLButtonElement | null>
13
+ }
14
+
15
+ const SelectContext = React.createContext<SelectContextValue | null>(null)
16
+
17
+ interface SelectProps {
18
+ children: React.ReactNode
19
+ value?: string
20
+ onValueChange?: (value: string) => void
21
+ defaultValue?: string
22
+ }
23
+
24
+ /**
25
+ * Custom Select dropdown with keyboard navigation
26
+ *
27
+ * @example
28
+ * <Select value={value} onValueChange={setValue}>
29
+ * <SelectTrigger>
30
+ * <SelectValue placeholder="Select option" />
31
+ * </SelectTrigger>
32
+ * <SelectContent>
33
+ * <SelectItem value="option1">Option 1</SelectItem>
34
+ * <SelectItem value="option2">Option 2</SelectItem>
35
+ * </SelectContent>
36
+ * </Select>
37
+ */
38
+ function Select({ children, value: controlledValue, onValueChange, defaultValue = "" }: SelectProps) {
39
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
40
+ const [open, setOpen] = React.useState(false)
41
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
42
+
43
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
44
+ const setValue = onValueChange || setUncontrolledValue
45
+
46
+ return (
47
+ <SelectContext.Provider value={{ value, onValueChange: setValue, open, setOpen, triggerRef }}>
48
+ <div className="relative">
49
+ {children}
50
+ </div>
51
+ </SelectContext.Provider>
52
+ )
53
+ }
54
+
55
+ const SelectTrigger = React.forwardRef<
56
+ HTMLButtonElement,
57
+ React.ButtonHTMLAttributes<HTMLButtonElement>
58
+ >(({ className, children, ...props }, ref) => {
59
+ const context = React.useContext(SelectContext)
60
+ if (!context) throw new Error("SelectTrigger must be used within Select")
61
+
62
+ // Combine refs
63
+ const combinedRef = (node: HTMLButtonElement | null) => {
64
+ (context.triggerRef as any).current = node
65
+ if (typeof ref === 'function') ref(node)
66
+ else if (ref) ref.current = node
67
+ }
68
+
69
+ return (
70
+ <button
71
+ ref={combinedRef}
72
+ type="button"
73
+ role="combobox"
74
+ aria-expanded={context.open}
75
+ aria-haspopup="listbox"
76
+ className={cn(
77
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
78
+ className
79
+ )}
80
+ onClick={() => context.setOpen(!context.open)}
81
+ {...props}
82
+ >
83
+ {children}
84
+ <svg
85
+ className={cn("h-4 w-4 opacity-50 transition-transform", context.open && "rotate-180")}
86
+ fill="none"
87
+ viewBox="0 0 24 24"
88
+ stroke="currentColor"
89
+ strokeWidth={2}
90
+ >
91
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
92
+ </svg>
93
+ </button>
94
+ )
95
+ })
96
+ SelectTrigger.displayName = "SelectTrigger"
97
+
98
+ interface SelectValueProps {
99
+ placeholder?: string
100
+ }
101
+
102
+ function SelectValue({ placeholder }: SelectValueProps) {
103
+ const context = React.useContext(SelectContext)
104
+ if (!context) throw new Error("SelectValue must be used within Select")
105
+
106
+ return (
107
+ <span className={cn(!context.value && "text-muted-foreground")}>
108
+ {context.value || placeholder}
109
+ </span>
110
+ )
111
+ }
112
+
113
+ interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
114
+ sideOffset?: number
115
+ portal?: boolean
116
+ align?: "start" | "center" | "end"
117
+ side?: "top" | "bottom"
118
+ }
119
+
120
+ const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(
121
+ ({ className, children, sideOffset = 4, portal = true, align = "start", side = "bottom", ...props }, ref) => {
122
+ const context = React.useContext(SelectContext)
123
+ if (!context) throw new Error("SelectContent must be used within Select")
124
+ const contentRef = React.useRef<HTMLDivElement>(null)
125
+ const [position, setPosition] = React.useState({ top: 0, left: 0 })
126
+ const [mounted, setMounted] = React.useState(false)
127
+
128
+ React.useEffect(() => {
129
+ setMounted(true)
130
+ }, [])
131
+
132
+ React.useEffect(() => {
133
+ const handleClickOutside = (e: MouseEvent) => {
134
+ if (context.open) {
135
+ const target = e.target as Node
136
+ const content = contentRef.current
137
+ const trigger = context.triggerRef.current
138
+
139
+ if (content?.contains(target) || (trigger && trigger.contains(target))) {
140
+ return
141
+ }
142
+ context.setOpen(false)
143
+ }
144
+ }
145
+
146
+ const handleEscape = (e: KeyboardEvent) => {
147
+ if (e.key === "Escape" && context.open) {
148
+ context.setOpen(false)
149
+ }
150
+ }
151
+
152
+ const checkPosition = () => {
153
+ if (context.open && contentRef.current && context.triggerRef.current) {
154
+ const triggerRect = context.triggerRef.current.getBoundingClientRect()
155
+ const contentRect = contentRef.current.getBoundingClientRect()
156
+ const viewportHeight = window.innerHeight
157
+ const viewportWidth = window.innerWidth
158
+
159
+ let top = 0
160
+ let left = 0
161
+
162
+ // Vertical
163
+ const spaceBelow = viewportHeight - triggerRect.bottom
164
+ const spaceAbove = triggerRect.top
165
+ const neededHeight = contentRect.height + sideOffset
166
+ const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
167
+
168
+ if (onBottom) {
169
+ top = triggerRect.bottom + sideOffset
170
+ } else {
171
+ top = triggerRect.top - contentRect.height - sideOffset
172
+ }
173
+
174
+ // Horizontal - Select usually matches trigger width or starts aligned
175
+ left = triggerRect.left
176
+
177
+ // Clamping
178
+ if (left < 4) left = 4
179
+ if (left + contentRect.width > viewportWidth - 4) {
180
+ left = viewportWidth - contentRect.width - 4
181
+ }
182
+
183
+ // Match width if it fits, or at least min-width of trigger
184
+ // For now keeping simple positioning
185
+
186
+ setPosition({ top, left })
187
+ }
188
+ }
189
+
190
+ if (context.open) {
191
+ requestAnimationFrame(checkPosition)
192
+ }
193
+
194
+ const timer = setTimeout(() => {
195
+ document.addEventListener("mousedown", handleClickOutside)
196
+ }, 0)
197
+ document.addEventListener("keydown", handleEscape)
198
+ window.addEventListener("resize", checkPosition)
199
+ window.addEventListener("scroll", checkPosition, true)
200
+
201
+ return () => {
202
+ clearTimeout(timer)
203
+ document.removeEventListener("mousedown", handleClickOutside)
204
+ document.removeEventListener("keydown", handleEscape)
205
+ window.removeEventListener("resize", checkPosition)
206
+ window.removeEventListener("scroll", checkPosition, true)
207
+ }
208
+ }, [context.open, context, sideOffset])
209
+
210
+ if (!context.open) return null
211
+ if (portal && !mounted) return null
212
+
213
+ const content = (
214
+ <div
215
+ ref={(node) => {
216
+ (contentRef as any).current = node
217
+ if (typeof ref === 'function') ref(node)
218
+ else if (ref) ref.current = node
219
+ }}
220
+ role="listbox"
221
+ className={cn(
222
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
223
+ "animate-in fade-in-0 zoom-in-95",
224
+ !portal && "absolute",
225
+ !portal && "mt-1",
226
+ portal && "fixed",
227
+ className
228
+ )}
229
+ style={{
230
+ top: portal ? position.top : undefined,
231
+ left: portal ? position.left : undefined,
232
+ width: context.triggerRef.current ? context.triggerRef.current.offsetWidth : undefined,
233
+ ...props.style
234
+ }}
235
+ onClick={(e) => e.stopPropagation()}
236
+ {...props}
237
+ >
238
+ {children}
239
+ </div>
240
+ )
241
+
242
+ if (portal) {
243
+ return createPortal(content, document.body)
244
+ }
245
+
246
+ return content
247
+ }
248
+ )
249
+ SelectContent.displayName = "SelectContent"
250
+
251
+ interface SelectItemProps extends React.HTMLAttributes<HTMLDivElement> {
252
+ value: string
253
+ }
254
+
255
+ const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
256
+ ({ className, children, value, ...props }, ref) => {
257
+ const context = React.useContext(SelectContext)
258
+ if (!context) throw new Error("SelectItem must be used within Select")
259
+
260
+ const isSelected = context.value === value
261
+
262
+ return (
263
+ <div
264
+ ref={ref}
265
+ role="option"
266
+ aria-selected={isSelected}
267
+ className={cn(
268
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
269
+ isSelected && "bg-accent text-accent-foreground",
270
+ className
271
+ )}
272
+ onClick={() => {
273
+ context.onValueChange(value)
274
+ context.setOpen(false)
275
+ }}
276
+ {...props}
277
+ >
278
+ {children}
279
+ {isSelected && (
280
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
281
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
282
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
283
+ </svg>
284
+ </span>
285
+ )}
286
+ </div>
287
+ )
288
+ }
289
+ )
290
+ SelectItem.displayName = "SelectItem"
291
+
292
+ export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem }