@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,29 +1,26 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- /**
7
- * Skeleton loading placeholder
8
- *
9
- * @example
10
- * <Skeleton className="h-4 w-[200px]" />
11
- *
12
- * @example
13
- * // Circle skeleton for avatars
14
- * <Skeleton className="h-12 w-12 rounded-full" />
15
- */
16
- const Skeleton = React.forwardRef<
17
- HTMLDivElement,
18
- React.HTMLAttributes<HTMLDivElement>
19
- >(({ className, ...props }, ref) => (
20
- <div
21
- ref={ref}
22
- className={cn("animate-pulse rounded-md bg-primary/10", className)}
23
- {...props}
24
- />
25
- ))
26
- Skeleton.displayName = "Skeleton"
27
-
28
- export { Skeleton }
29
-
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ /**
5
+ * Skeleton loading placeholder
6
+ *
7
+ * @example
8
+ * <Skeleton className="h-4 w-[200px]" />
9
+ *
10
+ * @example
11
+ * // Circle skeleton for avatars
12
+ * <Skeleton className="h-12 w-12 rounded-full" />
13
+ */
14
+ const Skeleton = React.forwardRef<
15
+ HTMLDivElement,
16
+ React.HTMLAttributes<HTMLDivElement>
17
+ >(({ className, ...props }, ref) => (
18
+ <div
19
+ ref={ref}
20
+ className={cn("animate-pulse rounded-md bg-primary/10", className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Skeleton.displayName = "Skeleton"
25
+
26
+ export { Skeleton }
@@ -1,197 +1,196 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- interface SliderProps {
7
- value?: number[]
8
- onValueChange?: (value: number[]) => void
9
- defaultValue?: number[]
10
- min?: number
11
- max?: number
12
- step?: number
13
- disabled?: boolean
14
- minStepsBetweenThumbs?: number
15
- className?: string
16
- }
17
-
18
- /**
19
- * Slider component with support for multiple thumbs (range selection)
20
- *
21
- * @example
22
- * // Single value
23
- * <Slider value={[50]} onValueChange={setValue} max={100} step={1} />
24
- *
25
- * @example
26
- * // Range
27
- * <Slider value={[25, 75]} onValueChange={setValue} max={100} step={1} />
28
- */
29
- const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
30
- ({
31
- className,
32
- value: controlledValue,
33
- onValueChange,
34
- defaultValue = [0],
35
- min = 0,
36
- max = 100,
37
- step = 1,
38
- disabled,
39
- minStepsBetweenThumbs = 0,
40
- ...props
41
- }, ref) => {
42
- const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
43
- const trackRef = React.useRef<HTMLDivElement>(null)
44
- const activeThumbIndex = React.useRef<number | null>(null)
45
-
46
- const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
47
- const setValue = (newValue: number[]) => {
48
- if (controlledValue === undefined) {
49
- setUncontrolledValue(newValue)
50
- }
51
- onValueChange?.(newValue)
52
- }
53
-
54
- const updateValue = (clientX: number, thumbIndex: number) => {
55
- if (!trackRef.current) return
56
-
57
- const rect = trackRef.current.getBoundingClientRect()
58
- const percentage = (clientX - rect.left) / rect.width
59
- const rawValue = min + percentage * (max - min)
60
- const steppedValue = Math.round(rawValue / step) * step
61
- const clampedValue = Math.min(Math.max(steppedValue, min), max)
62
-
63
- const newValue = [...value]
64
- newValue[thumbIndex] = clampedValue
65
-
66
- // Sort logic to prevent crossover if preferred, or just allow it but sorted
67
- newValue.sort((a, b) => a - b)
68
-
69
- setValue(newValue)
70
- }
71
-
72
- // Find closest thumb to a point
73
- const getClosestThumbIndex = (clientX: number) => {
74
- if (!trackRef.current) return 0
75
- const rect = trackRef.current.getBoundingClientRect()
76
- const percentage = (clientX - rect.left) / rect.width
77
- const clickedValue = min + percentage * (max - min)
78
-
79
- let closestIndex = 0
80
- let minDiff = Infinity
81
-
82
- value.forEach((val, index) => {
83
- const diff = Math.abs(val - clickedValue)
84
- if (diff < minDiff) {
85
- minDiff = diff
86
- closestIndex = index
87
- }
88
- })
89
-
90
- return closestIndex
91
- }
92
-
93
- const handleMouseDown = (e: React.MouseEvent) => {
94
- if (disabled) return
95
- const thumbIndex = getClosestThumbIndex(e.clientX)
96
- activeThumbIndex.current = thumbIndex
97
-
98
- updateValue(e.clientX, thumbIndex)
99
-
100
- document.addEventListener('mousemove', handleMouseMove)
101
- document.addEventListener('mouseup', handleMouseUp)
102
- }
103
-
104
- const handleMouseMove = (e: MouseEvent) => {
105
- if (activeThumbIndex.current === null) return
106
- updateValue(e.clientX, activeThumbIndex.current)
107
- }
108
-
109
- const handleMouseUp = () => {
110
- activeThumbIndex.current = null
111
- document.removeEventListener('mousemove', handleMouseMove)
112
- document.removeEventListener('mouseup', handleMouseUp)
113
- }
114
-
115
- const handleTouchStart = (e: React.TouchEvent) => {
116
- if (disabled) return
117
- const thumbIndex = getClosestThumbIndex(e.touches[0].clientX)
118
- activeThumbIndex.current = thumbIndex
119
-
120
- updateValue(e.touches[0].clientX, thumbIndex)
121
-
122
- document.addEventListener('touchmove', handleTouchMove)
123
- document.addEventListener('touchend', handleTouchEnd)
124
- }
125
-
126
- const handleTouchMove = (e: TouchEvent) => {
127
- if (activeThumbIndex.current === null) return
128
- updateValue(e.touches[0].clientX, activeThumbIndex.current)
129
- e.preventDefault()
130
- }
131
-
132
- const handleTouchEnd = () => {
133
- activeThumbIndex.current = null
134
- document.removeEventListener('touchmove', handleTouchMove)
135
- document.removeEventListener('touchend', handleTouchEnd)
136
- }
137
-
138
- return (
139
- <div
140
- ref={ref}
141
- role="group"
142
- className={cn(
143
- "relative flex w-full touch-none select-none items-center py-4 cursor-pointer",
144
- disabled && "opacity-50 cursor-not-allowed",
145
- className
146
- )}
147
- onMouseDown={handleMouseDown}
148
- onTouchStart={handleTouchStart}
149
- {...props}
150
- >
151
- <div
152
- ref={trackRef}
153
- className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary"
154
- >
155
- {/* Render active tracks between ranges if multiple, or from 0 if single */}
156
- {value.length > 1 ? (
157
- <div
158
- className="absolute h-full bg-primary transition-all duration-75"
159
- style={{
160
- left: `${((value[0] - min) / (max - min)) * 100}%`,
161
- right: `${100 - ((value[value.length - 1] - min) / (max - min)) * 100}%`,
162
- }}
163
- />
164
- ) : (
165
- <div
166
- className="absolute h-full bg-primary transition-all duration-75"
167
- style={{ width: `${((value[0] - min) / (max - min)) * 100}%` }}
168
- />
169
- )}
170
- </div>
171
-
172
- {value.map((val, index) => {
173
- const percentage = ((val - min) / (max - min)) * 100
174
- return (
175
- <div
176
- key={index}
177
- role="slider"
178
- aria-valuemin={min}
179
- aria-valuemax={max}
180
- aria-valuenow={val}
181
- tabIndex={disabled ? -1 : 0}
182
- className={cn(
183
- "absolute block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
184
- "hover:bg-accent hover:border-primary"
185
- )}
186
- style={{ left: `calc(${percentage}% - 8px)` }}
187
- />
188
- )
189
- })}
190
- </div>
191
- )
192
- }
193
- )
194
- Slider.displayName = "Slider"
195
-
196
- export { Slider }
197
-
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ interface SliderProps {
7
+ value?: number[]
8
+ onValueChange?: (value: number[]) => void
9
+ defaultValue?: number[]
10
+ min?: number
11
+ max?: number
12
+ step?: number
13
+ disabled?: boolean
14
+ minStepsBetweenThumbs?: number
15
+ className?: string
16
+ }
17
+
18
+ /**
19
+ * Slider component with support for multiple thumbs (range selection)
20
+ *
21
+ * @example
22
+ * // Single value
23
+ * <Slider value={[50]} onValueChange={setValue} max={100} step={1} />
24
+ *
25
+ * @example
26
+ * // Range
27
+ * <Slider value={[25, 75]} onValueChange={setValue} max={100} step={1} />
28
+ */
29
+ const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
30
+ ({
31
+ className,
32
+ value: controlledValue,
33
+ onValueChange,
34
+ defaultValue = [0],
35
+ min = 0,
36
+ max = 100,
37
+ step = 1,
38
+ disabled,
39
+ minStepsBetweenThumbs = 0,
40
+ ...props
41
+ }, ref) => {
42
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
43
+ const trackRef = React.useRef<HTMLDivElement>(null)
44
+ const activeThumbIndex = React.useRef<number | null>(null)
45
+
46
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
47
+ const setValue = (newValue: number[]) => {
48
+ if (controlledValue === undefined) {
49
+ setUncontrolledValue(newValue)
50
+ }
51
+ onValueChange?.(newValue)
52
+ }
53
+
54
+ const updateValue = (clientX: number, thumbIndex: number) => {
55
+ if (!trackRef.current) return
56
+
57
+ const rect = trackRef.current.getBoundingClientRect()
58
+ const percentage = (clientX - rect.left) / rect.width
59
+ const rawValue = min + percentage * (max - min)
60
+ const steppedValue = Math.round(rawValue / step) * step
61
+ const clampedValue = Math.min(Math.max(steppedValue, min), max)
62
+
63
+ const newValue = [...value]
64
+ newValue[thumbIndex] = clampedValue
65
+
66
+ // Sort logic to prevent crossover if preferred, or just allow it but sorted
67
+ newValue.sort((a, b) => a - b)
68
+
69
+ setValue(newValue)
70
+ }
71
+
72
+ // Find closest thumb to a point
73
+ const getClosestThumbIndex = (clientX: number) => {
74
+ if (!trackRef.current) return 0
75
+ const rect = trackRef.current.getBoundingClientRect()
76
+ const percentage = (clientX - rect.left) / rect.width
77
+ const clickedValue = min + percentage * (max - min)
78
+
79
+ let closestIndex = 0
80
+ let minDiff = Infinity
81
+
82
+ value.forEach((val, index) => {
83
+ const diff = Math.abs(val - clickedValue)
84
+ if (diff < minDiff) {
85
+ minDiff = diff
86
+ closestIndex = index
87
+ }
88
+ })
89
+
90
+ return closestIndex
91
+ }
92
+
93
+ const handleMouseDown = (e: React.MouseEvent) => {
94
+ if (disabled) return
95
+ const thumbIndex = getClosestThumbIndex(e.clientX)
96
+ activeThumbIndex.current = thumbIndex
97
+
98
+ updateValue(e.clientX, thumbIndex)
99
+
100
+ document.addEventListener('mousemove', handleMouseMove)
101
+ document.addEventListener('mouseup', handleMouseUp)
102
+ }
103
+
104
+ const handleMouseMove = (e: MouseEvent) => {
105
+ if (activeThumbIndex.current === null) return
106
+ updateValue(e.clientX, activeThumbIndex.current)
107
+ }
108
+
109
+ const handleMouseUp = () => {
110
+ activeThumbIndex.current = null
111
+ document.removeEventListener('mousemove', handleMouseMove)
112
+ document.removeEventListener('mouseup', handleMouseUp)
113
+ }
114
+
115
+ const handleTouchStart = (e: React.TouchEvent) => {
116
+ if (disabled) return
117
+ const thumbIndex = getClosestThumbIndex(e.touches[0].clientX)
118
+ activeThumbIndex.current = thumbIndex
119
+
120
+ updateValue(e.touches[0].clientX, thumbIndex)
121
+
122
+ document.addEventListener('touchmove', handleTouchMove)
123
+ document.addEventListener('touchend', handleTouchEnd)
124
+ }
125
+
126
+ const handleTouchMove = (e: TouchEvent) => {
127
+ if (activeThumbIndex.current === null) return
128
+ updateValue(e.touches[0].clientX, activeThumbIndex.current)
129
+ e.preventDefault()
130
+ }
131
+
132
+ const handleTouchEnd = () => {
133
+ activeThumbIndex.current = null
134
+ document.removeEventListener('touchmove', handleTouchMove)
135
+ document.removeEventListener('touchend', handleTouchEnd)
136
+ }
137
+
138
+ return (
139
+ <div
140
+ ref={ref}
141
+ role="group"
142
+ className={cn(
143
+ "relative flex w-full touch-none select-none items-center py-4 cursor-pointer",
144
+ disabled && "opacity-50 cursor-not-allowed",
145
+ className
146
+ )}
147
+ onMouseDown={handleMouseDown}
148
+ onTouchStart={handleTouchStart}
149
+ {...props}
150
+ >
151
+ <div
152
+ ref={trackRef}
153
+ className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary"
154
+ >
155
+ {/* Render active tracks between ranges if multiple, or from 0 if single */}
156
+ {value.length > 1 ? (
157
+ <div
158
+ className="absolute h-full bg-primary transition-all duration-75"
159
+ style={{
160
+ left: `${((value[0] - min) / (max - min)) * 100}%`,
161
+ right: `${100 - ((value[value.length - 1] - min) / (max - min)) * 100}%`,
162
+ }}
163
+ />
164
+ ) : (
165
+ <div
166
+ className="absolute h-full bg-primary transition-all duration-75"
167
+ style={{ width: `${((value[0] - min) / (max - min)) * 100}%` }}
168
+ />
169
+ )}
170
+ </div>
171
+
172
+ {value.map((val, index) => {
173
+ const percentage = ((val - min) / (max - min)) * 100
174
+ return (
175
+ <div
176
+ key={index}
177
+ role="slider"
178
+ aria-valuemin={min}
179
+ aria-valuemax={max}
180
+ aria-valuenow={val}
181
+ tabIndex={disabled ? -1 : 0}
182
+ className={cn(
183
+ "absolute block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
184
+ "hover:bg-accent hover:border-primary"
185
+ )}
186
+ style={{ left: `calc(${percentage}% - 8px)` }}
187
+ />
188
+ )
189
+ })}
190
+ </div>
191
+ )
192
+ }
193
+ )
194
+ Slider.displayName = "Slider"
195
+
196
+ export { Slider }
@@ -1,72 +1,69 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- export interface SlotProps extends React.HTMLAttributes<HTMLElement> {
7
- children?: React.ReactNode
8
- }
9
-
10
- const Slot = React.forwardRef<HTMLElement, SlotProps>(
11
- ({ children, className, ...props }, ref) => {
12
- if (React.isValidElement(children)) {
13
- const child = children as React.ReactElement<any>
14
-
15
- const mergedProps: Record<string, any> = { ...props }
16
- if (className) {
17
- mergedProps.className = className
18
- }
19
-
20
- for (const propName in child.props) {
21
- const slotPropValue = mergedProps[propName]
22
- const childPropValue = child.props[propName]
23
-
24
- if (
25
- /^on[A-Z]/.test(propName) &&
26
- typeof slotPropValue === "function" &&
27
- typeof childPropValue === "function"
28
- ) {
29
- mergedProps[propName] = (...args: any[]) => {
30
- childPropValue(...args)
31
- if (!args[0]?.defaultPrevented) {
32
- slotPropValue(...args)
33
- }
34
- }
35
- } else if (propName === "style") {
36
- mergedProps[propName] = { ...slotPropValue, ...childPropValue }
37
- } else if (propName === "className") {
38
- mergedProps[propName] = cn(slotPropValue, childPropValue)
39
- } else {
40
- mergedProps[propName] = childPropValue
41
- }
42
- }
43
-
44
- return React.cloneElement(child, {
45
- ...mergedProps,
46
- ref: (node: HTMLElement | null) => {
47
- // Handle both function and object refs for the forwarded ref
48
- if (typeof ref === "function") {
49
- ref(node)
50
- } else if (ref && "current" in ref) {
51
- (ref as any).current = node
52
- }
53
-
54
- // Handle the child's existing ref
55
- const childRef = (child as any).ref
56
- if (typeof childRef === "function") {
57
- childRef(node)
58
- } else if (childRef && "current" in childRef) {
59
- (childRef as any).current = node
60
- }
61
- },
62
- })
63
- }
64
-
65
- return null
66
- }
67
- )
68
-
69
- Slot.displayName = "Slot"
70
-
71
- export { Slot }
72
-
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ export interface SlotProps extends React.HTMLAttributes<HTMLElement> {
5
+ children?: React.ReactNode
6
+ }
7
+
8
+ const Slot = React.forwardRef<HTMLElement, SlotProps>(
9
+ ({ children, className, ...props }, ref) => {
10
+ if (React.isValidElement(children)) {
11
+ const child = children as React.ReactElement<any>
12
+
13
+ const mergedProps: Record<string, any> = { ...props }
14
+ if (className) {
15
+ mergedProps.className = className
16
+ }
17
+
18
+ for (const propName in child.props) {
19
+ const slotPropValue = mergedProps[propName]
20
+ const childPropValue = child.props[propName]
21
+
22
+ if (
23
+ /^on[A-Z]/.test(propName) &&
24
+ typeof slotPropValue === "function" &&
25
+ typeof childPropValue === "function"
26
+ ) {
27
+ mergedProps[propName] = (...args: any[]) => {
28
+ childPropValue(...args)
29
+ if (!args[0]?.defaultPrevented) {
30
+ slotPropValue(...args)
31
+ }
32
+ }
33
+ } else if (propName === "style") {
34
+ mergedProps[propName] = { ...slotPropValue, ...childPropValue }
35
+ } else if (propName === "className") {
36
+ mergedProps[propName] = cn(slotPropValue, childPropValue)
37
+ } else {
38
+ mergedProps[propName] = childPropValue
39
+ }
40
+ }
41
+
42
+ return React.cloneElement(child, {
43
+ ...mergedProps,
44
+ ref: (node: HTMLElement | null) => {
45
+ // Handle both function and object refs for the forwarded ref
46
+ if (typeof ref === "function") {
47
+ ref(node)
48
+ } else if (ref && "current" in ref) {
49
+ (ref as any).current = node
50
+ }
51
+
52
+ // Handle the child's existing ref
53
+ const childRef = (child as any).ref
54
+ if (typeof childRef === "function") {
55
+ childRef(node)
56
+ } else if (childRef && "current" in childRef) {
57
+ (childRef as any).current = node
58
+ }
59
+ },
60
+ })
61
+ }
62
+
63
+ return null
64
+ }
65
+ )
66
+
67
+ Slot.displayName = "Slot"
68
+
69
+ export { Slot }