@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.
- package/README.md +151 -151
- package/dist/index.d.ts +0 -0
- package/dist/index.js +120 -93
- package/package.json +7 -2
- package/src/registry/analytics/google-analytics.tsx +36 -39
- package/src/registry/analytics/google-tag-manager.tsx +62 -65
- package/src/registry/analytics/meta-pixel.tsx +44 -47
- package/src/registry/analytics/microsoft-clarity.tsx +31 -34
- package/src/registry/analytics/tiktok-pixel.tsx +34 -37
- package/src/registry/lib/utils.ts +0 -0
- package/src/registry/themes/v3/blue.css +157 -157
- package/src/registry/themes/v3/glass.css +153 -153
- package/src/registry/themes/v3/gray.css +157 -157
- package/src/registry/themes/v3/green.css +157 -157
- package/src/registry/themes/v3/neutral.css +157 -157
- package/src/registry/themes/v3/orange.css +157 -157
- package/src/registry/themes/v3/rose.css +157 -157
- package/src/registry/themes/v3/slate.css +157 -157
- package/src/registry/themes/v3/stone.css +157 -157
- package/src/registry/themes/v3/violet.css +186 -186
- package/src/registry/themes/v3/zinc.css +157 -157
- package/src/registry/themes/v4/blue.css +184 -184
- package/src/registry/themes/v4/glass.css +180 -180
- package/src/registry/themes/v4/gray.css +184 -184
- package/src/registry/themes/v4/green.css +184 -184
- package/src/registry/themes/v4/neutral.css +184 -184
- package/src/registry/themes/v4/orange.css +184 -184
- package/src/registry/themes/v4/rose.css +184 -184
- package/src/registry/themes/v4/slate.css +184 -184
- package/src/registry/themes/v4/stone.css +184 -184
- package/src/registry/themes/v4/violet.css +184 -184
- package/src/registry/themes/v4/zinc.css +184 -184
- package/src/registry/ui/accordion.tsx +164 -165
- package/src/registry/ui/alert-dialog.tsx +213 -214
- package/src/registry/ui/alert.tsx +73 -76
- package/src/registry/ui/aspect-ratio.tsx +44 -47
- package/src/registry/ui/avatar.tsx +96 -97
- package/src/registry/ui/badge.tsx +52 -55
- package/src/registry/ui/breadcrumb.tsx +147 -150
- package/src/registry/ui/button-group.tsx +64 -67
- package/src/registry/ui/button.tsx +71 -72
- package/src/registry/ui/calendar.tsx +514 -515
- package/src/registry/ui/card.tsx +88 -91
- package/src/registry/ui/carousel.tsx +214 -214
- package/src/registry/ui/chart.tsx +373 -373
- package/src/registry/ui/chatbot.tsx +86 -13
- package/src/registry/ui/checkbox.tsx +93 -94
- package/src/registry/ui/collapsible.tsx +107 -108
- package/src/registry/ui/combobox.tsx +171 -171
- package/src/registry/ui/command.tsx +300 -300
- package/src/registry/ui/container.tsx +44 -47
- package/src/registry/ui/context-menu.tsx +221 -221
- package/src/registry/ui/date-picker.tsx +228 -228
- package/src/registry/ui/dialog.tsx +269 -270
- package/src/registry/ui/drawer.tsx +10 -4
- package/src/registry/ui/dropdown-menu.tsx +529 -530
- package/src/registry/ui/empty-state.tsx +0 -2
- package/src/registry/ui/file-upload.tsx +0 -0
- package/src/registry/ui/floating-dock.tsx +0 -0
- package/src/registry/ui/form-field.tsx +91 -94
- package/src/registry/ui/google-analytics.tsx +38 -0
- package/src/registry/ui/google-tag-manager.tsx +64 -0
- package/src/registry/ui/hover-card.tsx +223 -223
- package/src/registry/ui/image.tsx +144 -147
- package/src/registry/ui/input-group.tsx +82 -85
- package/src/registry/ui/input.tsx +125 -125
- package/src/registry/ui/kbd.tsx +60 -63
- package/src/registry/ui/label.tsx +36 -37
- package/src/registry/ui/loading-spinner.tsx +108 -111
- package/src/registry/ui/map.tsx +0 -0
- package/src/registry/ui/marquee.tsx +2 -0
- package/src/registry/ui/menubar.tsx +246 -246
- package/src/registry/ui/meta-pixel.tsx +46 -0
- package/src/registry/ui/microsoft-clarity.tsx +33 -0
- package/src/registry/ui/native-select.tsx +49 -52
- package/src/registry/ui/otp-input.tsx +163 -155
- package/src/registry/ui/pagination.tsx +149 -152
- package/src/registry/ui/patterns.tsx +28 -0
- package/src/registry/ui/popover.tsx +226 -227
- package/src/registry/ui/progress.tsx +51 -52
- package/src/registry/ui/radio.tsx +99 -102
- package/src/registry/ui/resizable.tsx +314 -314
- package/src/registry/ui/scroll-animation.tsx +45 -0
- package/src/registry/ui/scroll-area.tsx +121 -122
- package/src/registry/ui/scroll-to-top.tsx +0 -0
- package/src/registry/ui/search.tsx +162 -150
- package/src/registry/ui/select.tsx +292 -293
- package/src/registry/ui/separator.tsx +46 -47
- package/src/registry/ui/sheet.tsx +6 -3
- package/src/registry/ui/sidebar.tsx +628 -628
- package/src/registry/ui/skeleton.tsx +26 -29
- package/src/registry/ui/slider.tsx +196 -197
- package/src/registry/ui/slot.tsx +69 -72
- package/src/registry/ui/star-rating.tsx +146 -134
- package/src/registry/ui/switch.tsx +72 -73
- package/src/registry/ui/table-of-contents.tsx +96 -96
- package/src/registry/ui/table.tsx +138 -139
- package/src/registry/ui/tabs.tsx +124 -125
- package/src/registry/ui/text.tsx +61 -64
- package/src/registry/ui/textarea.tsx +41 -42
- package/src/registry/ui/theme-switcher.tsx +66 -66
- package/src/registry/ui/tiktok-pixel.tsx +36 -0
- package/src/registry/ui/toast.tsx +97 -98
- package/src/registry/ui/toggle-group.tsx +129 -129
- package/src/registry/ui/toggle.tsx +72 -72
- package/src/registry/ui/tooltip.tsx +143 -144
- package/src/registry/ui/whatsapp.tsx +0 -0
|
@@ -1,29 +1,26 @@
|
|
|
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
|
-
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
|
-
|
|
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 }
|
package/src/registry/ui/slot.tsx
CHANGED
|
@@ -1,72 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
} else if (propName === "
|
|
36
|
-
mergedProps[propName] =
|
|
37
|
-
} else
|
|
38
|
-
mergedProps[propName] =
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
ref
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
childRef
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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 }
|