@srcroot/ui 0.0.2 → 0.0.3
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/package.json +1 -1
- package/registry/badge.tsx +9 -25
- package/registry/breadcrumb.tsx +1 -1
- package/registry/button-group.tsx +9 -29
- package/registry/button.tsx +20 -46
- package/registry/card.tsx +21 -47
- package/registry/combobox.tsx +0 -3
- package/registry/command.tsx +6 -4
- package/registry/container.tsx +9 -25
- package/registry/drawer.tsx +36 -12
- package/registry/dropdown-menu.tsx +92 -44
- package/registry/hover-card.tsx +1 -1
- package/registry/image.tsx +2 -2
- package/registry/menubar.tsx +1 -1
- package/registry/resizable.tsx +71 -33
- package/registry/scroll-area.tsx +66 -7
- package/registry/sheet.tsx +62 -18
- package/registry/sidebar.tsx +7 -0
- package/registry/slider.tsx +101 -86
- package/registry/text.tsx +7 -16
package/registry/slider.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { cn } from "@/lib/utils"
|
|
3
3
|
|
|
4
|
-
interface SliderProps
|
|
4
|
+
interface SliderProps {
|
|
5
5
|
value?: number[]
|
|
6
6
|
onValueChange?: (value: number[]) => void
|
|
7
7
|
defaultValue?: number[]
|
|
@@ -9,17 +9,21 @@ interface SliderProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChan
|
|
|
9
9
|
max?: number
|
|
10
10
|
step?: number
|
|
11
11
|
disabled?: boolean
|
|
12
|
+
minStepsBetweenThumbs?: number
|
|
13
|
+
className?: string
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Slider component with
|
|
17
|
+
* Slider component with support for multiple thumbs (range selection)
|
|
16
18
|
*
|
|
17
19
|
* @example
|
|
18
|
-
*
|
|
19
|
-
* <Slider value={
|
|
20
|
+
* // Single value
|
|
21
|
+
* <Slider value={[50]} onValueChange={setValue} max={100} step={1} />
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Range
|
|
25
|
+
* <Slider value={[25, 75]} onValueChange={setValue} max={100} step={1} />
|
|
20
26
|
*/
|
|
21
|
-
export { Slider }
|
|
22
|
-
|
|
23
27
|
const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
24
28
|
({
|
|
25
29
|
className,
|
|
@@ -30,19 +34,22 @@ const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
30
34
|
max = 100,
|
|
31
35
|
step = 1,
|
|
32
36
|
disabled,
|
|
37
|
+
minStepsBetweenThumbs = 0,
|
|
33
38
|
...props
|
|
34
39
|
}, ref) => {
|
|
35
40
|
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
|
|
36
41
|
const trackRef = React.useRef<HTMLDivElement>(null)
|
|
37
|
-
const
|
|
42
|
+
const activeThumbIndex = React.useRef<number | null>(null)
|
|
38
43
|
|
|
39
44
|
const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
|
|
40
|
-
const setValue =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const setValue = (newValue: number[]) => {
|
|
46
|
+
if (controlledValue === undefined) {
|
|
47
|
+
setUncontrolledValue(newValue)
|
|
48
|
+
}
|
|
49
|
+
onValueChange?.(newValue)
|
|
50
|
+
}
|
|
44
51
|
|
|
45
|
-
const updateValue = (clientX: number) => {
|
|
52
|
+
const updateValue = (clientX: number, thumbIndex: number) => {
|
|
46
53
|
if (!trackRef.current) return
|
|
47
54
|
|
|
48
55
|
const rect = trackRef.current.getBoundingClientRect()
|
|
@@ -51,129 +58,137 @@ const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
|
51
58
|
const steppedValue = Math.round(rawValue / step) * step
|
|
52
59
|
const clampedValue = Math.min(Math.max(steppedValue, min), max)
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
const newValue = [...value]
|
|
62
|
+
newValue[thumbIndex] = clampedValue
|
|
63
|
+
|
|
64
|
+
// Sort logic to prevent crossover if preferred, or just allow it but sorted
|
|
65
|
+
newValue.sort((a, b) => a - b)
|
|
66
|
+
|
|
67
|
+
setValue(newValue)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Find closest thumb to a point
|
|
71
|
+
const getClosestThumbIndex = (clientX: number) => {
|
|
72
|
+
if (!trackRef.current) return 0
|
|
73
|
+
const rect = trackRef.current.getBoundingClientRect()
|
|
74
|
+
const percentage = (clientX - rect.left) / rect.width
|
|
75
|
+
const clickedValue = min + percentage * (max - min)
|
|
76
|
+
|
|
77
|
+
let closestIndex = 0
|
|
78
|
+
let minDiff = Infinity
|
|
79
|
+
|
|
80
|
+
value.forEach((val, index) => {
|
|
81
|
+
const diff = Math.abs(val - clickedValue)
|
|
82
|
+
if (diff < minDiff) {
|
|
83
|
+
minDiff = diff
|
|
84
|
+
closestIndex = index
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return closestIndex
|
|
55
89
|
}
|
|
56
90
|
|
|
57
91
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
58
92
|
if (disabled) return
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
const thumbIndex = getClosestThumbIndex(e.clientX)
|
|
94
|
+
activeThumbIndex.current = thumbIndex
|
|
95
|
+
|
|
96
|
+
updateValue(e.clientX, thumbIndex)
|
|
61
97
|
|
|
62
98
|
document.addEventListener('mousemove', handleMouseMove)
|
|
63
99
|
document.addEventListener('mouseup', handleMouseUp)
|
|
64
100
|
}
|
|
65
101
|
|
|
66
102
|
const handleMouseMove = (e: MouseEvent) => {
|
|
67
|
-
if (
|
|
68
|
-
updateValue(e.clientX)
|
|
103
|
+
if (activeThumbIndex.current === null) return
|
|
104
|
+
updateValue(e.clientX, activeThumbIndex.current)
|
|
69
105
|
}
|
|
70
106
|
|
|
71
107
|
const handleMouseUp = () => {
|
|
72
|
-
|
|
108
|
+
activeThumbIndex.current = null
|
|
73
109
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
74
110
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
75
111
|
}
|
|
76
112
|
|
|
77
|
-
// Touch support
|
|
78
113
|
const handleTouchStart = (e: React.TouchEvent) => {
|
|
79
114
|
if (disabled) return
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
const thumbIndex = getClosestThumbIndex(e.touches[0].clientX)
|
|
116
|
+
activeThumbIndex.current = thumbIndex
|
|
117
|
+
|
|
118
|
+
updateValue(e.touches[0].clientX, thumbIndex)
|
|
82
119
|
|
|
83
120
|
document.addEventListener('touchmove', handleTouchMove)
|
|
84
121
|
document.addEventListener('touchend', handleTouchEnd)
|
|
85
122
|
}
|
|
86
123
|
|
|
87
124
|
const handleTouchMove = (e: TouchEvent) => {
|
|
88
|
-
if (
|
|
89
|
-
updateValue(e.touches[0].clientX)
|
|
90
|
-
e.preventDefault()
|
|
125
|
+
if (activeThumbIndex.current === null) return
|
|
126
|
+
updateValue(e.touches[0].clientX, activeThumbIndex.current)
|
|
127
|
+
e.preventDefault()
|
|
91
128
|
}
|
|
92
129
|
|
|
93
130
|
const handleTouchEnd = () => {
|
|
94
|
-
|
|
131
|
+
activeThumbIndex.current = null
|
|
95
132
|
document.removeEventListener('touchmove', handleTouchMove)
|
|
96
133
|
document.removeEventListener('touchend', handleTouchEnd)
|
|
97
134
|
}
|
|
98
135
|
|
|
99
|
-
|
|
100
|
-
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
101
|
-
if (disabled) return
|
|
102
|
-
|
|
103
|
-
let newValue = currentValue
|
|
104
|
-
|
|
105
|
-
switch (e.key) {
|
|
106
|
-
case "ArrowRight":
|
|
107
|
-
case "ArrowUp":
|
|
108
|
-
newValue = Math.min(currentValue + step, max)
|
|
109
|
-
break
|
|
110
|
-
case "ArrowLeft":
|
|
111
|
-
case "ArrowDown":
|
|
112
|
-
newValue = Math.max(currentValue - step, min)
|
|
113
|
-
break
|
|
114
|
-
case "Home":
|
|
115
|
-
newValue = min
|
|
116
|
-
break
|
|
117
|
-
case "End":
|
|
118
|
-
newValue = max
|
|
119
|
-
break
|
|
120
|
-
default:
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (newValue !== currentValue) {
|
|
125
|
-
e.preventDefault()
|
|
126
|
-
setValue([newValue])
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Cleanup on unmount
|
|
131
|
-
React.useEffect(() => {
|
|
132
|
-
return () => {
|
|
133
|
-
document.removeEventListener('mousemove', handleMouseMove)
|
|
134
|
-
document.removeEventListener('mouseup', handleMouseUp)
|
|
135
|
-
document.removeEventListener('touchmove', handleTouchMove)
|
|
136
|
-
document.removeEventListener('touchend', handleTouchEnd)
|
|
137
|
-
}
|
|
138
|
-
}, [])
|
|
139
|
-
|
|
140
136
|
return (
|
|
141
137
|
<div
|
|
142
138
|
ref={ref}
|
|
143
|
-
role="
|
|
144
|
-
aria-valuenow={currentValue}
|
|
145
|
-
aria-valuemin={min}
|
|
146
|
-
aria-valuemax={max}
|
|
147
|
-
aria-disabled={disabled}
|
|
148
|
-
tabIndex={disabled ? -1 : 0}
|
|
139
|
+
role="group"
|
|
149
140
|
className={cn(
|
|
150
|
-
"relative flex w-full touch-none select-none items-center py-4 cursor-pointer",
|
|
141
|
+
"relative flex w-full touch-none select-none items-center py-4 cursor-pointer",
|
|
151
142
|
disabled && "opacity-50 cursor-not-allowed",
|
|
152
143
|
className
|
|
153
144
|
)}
|
|
154
|
-
onKeyDown={handleKeyDown}
|
|
155
145
|
onMouseDown={handleMouseDown}
|
|
156
146
|
onTouchStart={handleTouchStart}
|
|
157
147
|
{...props}
|
|
158
148
|
>
|
|
159
149
|
<div
|
|
160
150
|
ref={trackRef}
|
|
161
|
-
className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-
|
|
151
|
+
className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary"
|
|
162
152
|
>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
153
|
+
{/* Render active tracks between ranges if multiple, or from 0 if single */}
|
|
154
|
+
{value.length > 1 ? (
|
|
155
|
+
<div
|
|
156
|
+
className="absolute h-full bg-primary transition-all duration-75"
|
|
157
|
+
style={{
|
|
158
|
+
left: `${((value[0] - min) / (max - min)) * 100}%`,
|
|
159
|
+
right: `${100 - ((value[value.length - 1] - min) / (max - min)) * 100}%`,
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
) : (
|
|
163
|
+
<div
|
|
164
|
+
className="absolute h-full bg-primary transition-all duration-75"
|
|
165
|
+
style={{ width: `${((value[0] - min) / (max - min)) * 100}%` }}
|
|
166
|
+
/>
|
|
172
167
|
)}
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{value.map((val, index) => {
|
|
171
|
+
const percentage = ((val - min) / (max - min)) * 100
|
|
172
|
+
return (
|
|
173
|
+
<div
|
|
174
|
+
key={index}
|
|
175
|
+
role="slider"
|
|
176
|
+
aria-valuemin={min}
|
|
177
|
+
aria-valuemax={max}
|
|
178
|
+
aria-valuenow={val}
|
|
179
|
+
tabIndex={disabled ? -1 : 0}
|
|
180
|
+
className={cn(
|
|
181
|
+
"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",
|
|
182
|
+
"hover:bg-accent hover:border-primary"
|
|
183
|
+
)}
|
|
184
|
+
style={{ left: `calc(${percentage}% - 8px)` }}
|
|
185
|
+
/>
|
|
186
|
+
)
|
|
187
|
+
})}
|
|
175
188
|
</div>
|
|
176
189
|
)
|
|
177
190
|
}
|
|
178
191
|
)
|
|
179
192
|
Slider.displayName = "Slider"
|
|
193
|
+
|
|
194
|
+
export { Slider }
|
package/registry/text.tsx
CHANGED
|
@@ -26,13 +26,14 @@ const textVariants = cva("", {
|
|
|
26
26
|
|
|
27
27
|
type TextVariants = VariantProps<typeof textVariants>
|
|
28
28
|
|
|
29
|
-
interface
|
|
29
|
+
interface TextProps extends TextVariants {
|
|
30
30
|
className?: string
|
|
31
31
|
children?: React.ReactNode
|
|
32
|
+
as?: "p" | "span" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "label"
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
+
* Text component for typography
|
|
36
37
|
*
|
|
37
38
|
* @example
|
|
38
39
|
* // As a heading
|
|
@@ -44,22 +45,12 @@ interface TextBaseProps extends TextVariants {
|
|
|
44
45
|
* // Muted text
|
|
45
46
|
* <Text variant="muted">Secondary information</Text>
|
|
46
47
|
*/
|
|
47
|
-
const Text = React.forwardRef(
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
as,
|
|
51
|
-
className,
|
|
52
|
-
variant,
|
|
53
|
-
...props
|
|
54
|
-
}: TextBaseProps & { as?: T } & Omit<React.ComponentPropsWithoutRef<T>, keyof TextBaseProps | "as">,
|
|
55
|
-
ref: React.ForwardedRef<React.ElementRef<T>>
|
|
56
|
-
) => {
|
|
57
|
-
const Comp = as || "p"
|
|
58
|
-
|
|
48
|
+
const Text = React.forwardRef<HTMLElement, TextProps>(
|
|
49
|
+
({ as: Component = "p", className, variant, ...props }, ref) => {
|
|
59
50
|
return (
|
|
60
|
-
<
|
|
51
|
+
<Component
|
|
61
52
|
ref={ref as any}
|
|
62
|
-
className={cn(textVariants({ variant, className
|
|
53
|
+
className={cn(textVariants({ variant }), className)}
|
|
63
54
|
{...props}
|
|
64
55
|
/>
|
|
65
56
|
)
|