@traky-app/ui 1.0.0 → 1.2.0
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 +12 -19
- package/package.json +50 -3
- package/registry/traky/json/command.json +1 -1
- package/registry/traky/json/icon.json +17 -0
- package/registry/traky/json/logo.json +17 -0
- package/registry/traky/registry.json +20 -0
- package/registry/traky/ui/command.tsx +2 -0
- package/registry/traky/ui/icon.tsx +71 -0
- package/registry/traky/ui/logo.tsx +77 -0
- package/src/hooks/use-document-theme.ts +25 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/disable-input-autofill.ts +23 -0
- package/src/lib/sidebar-persistence.ts +76 -0
- package/src/lib/utils.ts +30 -0
- package/src/ui/accordion.tsx +65 -0
- package/src/ui/alert-dialog.tsx +163 -0
- package/src/ui/alert.tsx +66 -0
- package/src/ui/animated-beam.tsx +185 -0
- package/src/ui/animated-list.tsx +71 -0
- package/src/ui/avatar.tsx +53 -0
- package/src/ui/badge.tsx +46 -0
- package/src/ui/bento-grid.tsx +109 -0
- package/src/ui/breadcrumb.tsx +108 -0
- package/src/ui/bubble.tsx +125 -0
- package/src/ui/button-group.tsx +83 -0
- package/src/ui/button.tsx +64 -0
- package/src/ui/calendar.tsx +213 -0
- package/src/ui/card.tsx +92 -0
- package/src/ui/carousel.tsx +240 -0
- package/src/ui/chart.tsx +380 -0
- package/src/ui/checkbox.tsx +31 -0
- package/src/ui/checkpoint.tsx +94 -0
- package/src/ui/code-block.tsx +232 -0
- package/src/ui/collapsible.tsx +33 -0
- package/src/ui/command.tsx +192 -0
- package/src/ui/context-menu.tsx +251 -0
- package/src/ui/dialog.tsx +142 -0
- package/src/ui/drawer.tsx +135 -0
- package/src/ui/dropdown-menu.tsx +256 -0
- package/src/ui/empty.tsx +104 -0
- package/src/ui/field.tsx +248 -0
- package/src/ui/globe.tsx +234 -0
- package/src/ui/hover-card.tsx +44 -0
- package/src/ui/icon.tsx +71 -0
- package/src/ui/input-group.tsx +170 -0
- package/src/ui/input-otp.tsx +77 -0
- package/src/ui/input.tsx +24 -0
- package/src/ui/item.tsx +193 -0
- package/src/ui/kbd.tsx +28 -0
- package/src/ui/label.tsx +24 -0
- package/src/ui/logo.tsx +77 -0
- package/src/ui/map.tsx +2226 -0
- package/src/ui/marker.tsx +69 -0
- package/src/ui/marquee.tsx +74 -0
- package/src/ui/message.tsx +92 -0
- package/src/ui/navigation-menu.tsx +169 -0
- package/src/ui/number-ticker.tsx +90 -0
- package/src/ui/pagination.tsx +122 -0
- package/src/ui/popover.tsx +48 -0
- package/src/ui/progress.tsx +31 -0
- package/src/ui/select.tsx +193 -0
- package/src/ui/separator.tsx +28 -0
- package/src/ui/sheet.tsx +143 -0
- package/src/ui/sidebar.tsx +764 -0
- package/src/ui/skeleton.tsx +13 -0
- package/src/ui/sonner.tsx +71 -0
- package/src/ui/spinner.tsx +15 -0
- package/src/ui/switch.tsx +41 -0
- package/src/ui/table.tsx +116 -0
- package/src/ui/tabs.tsx +66 -0
- package/src/ui/textarea.tsx +20 -0
- package/src/ui/toggle-group.tsx +83 -0
- package/src/ui/toggle.tsx +47 -0
- package/src/ui/tooltip.tsx +61 -0
- package/src/ui/trace.tsx +165 -0
package/src/ui/globe.tsx
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef } from "react"
|
|
4
|
+
import createGlobe, { type COBEOptions } from "cobe"
|
|
5
|
+
import { useMotionValue, useSpring } from "motion/react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
|
|
9
|
+
const MOVEMENT_DAMPING = 1400
|
|
10
|
+
const MARKER_HOLD_MS = 2200
|
|
11
|
+
const MARKER_TRANSITION_MS = 1800
|
|
12
|
+
|
|
13
|
+
function easeInOutCubic(t: number): number {
|
|
14
|
+
return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function lerp(a: number, b: number, t: number): number {
|
|
18
|
+
return a + (b - a) * t
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function lerpAngle(a: number, b: number, t: number): number {
|
|
22
|
+
let diff = b - a
|
|
23
|
+
while (diff > Math.PI) diff -= 2 * Math.PI
|
|
24
|
+
while (diff < -Math.PI) diff += 2 * Math.PI
|
|
25
|
+
return a + diff * t
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const GLOBE_CONFIG: COBEOptions = {
|
|
29
|
+
width: 800,
|
|
30
|
+
height: 800,
|
|
31
|
+
onRender: () => {},
|
|
32
|
+
devicePixelRatio: 2,
|
|
33
|
+
phi: 0,
|
|
34
|
+
theta: 0.3,
|
|
35
|
+
dark: 0,
|
|
36
|
+
diffuse: 0.4,
|
|
37
|
+
mapSamples: 16000,
|
|
38
|
+
mapBrightness: 1.2,
|
|
39
|
+
baseColor: [1, 1, 1],
|
|
40
|
+
markerColor: [251 / 255, 100 / 255, 21 / 255],
|
|
41
|
+
glowColor: [1, 1, 1],
|
|
42
|
+
markers: [
|
|
43
|
+
{ location: [14.5995, 120.9842], size: 0.03 },
|
|
44
|
+
{ location: [19.076, 72.8777], size: 0.1 },
|
|
45
|
+
{ location: [23.8103, 90.4125], size: 0.05 },
|
|
46
|
+
{ location: [30.0444, 31.2357], size: 0.07 },
|
|
47
|
+
{ location: [39.9042, 116.4074], size: 0.08 },
|
|
48
|
+
{ location: [-23.5505, -46.6333], size: 0.1 },
|
|
49
|
+
{ location: [19.4326, -99.1332], size: 0.1 },
|
|
50
|
+
{ location: [40.7128, -74.006], size: 0.1 },
|
|
51
|
+
{ location: [34.6937, 135.5022], size: 0.05 },
|
|
52
|
+
{ location: [41.0082, 28.9784], size: 0.06 },
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function Globe({
|
|
57
|
+
className,
|
|
58
|
+
config = GLOBE_CONFIG,
|
|
59
|
+
autoRotate = true,
|
|
60
|
+
interactive = true,
|
|
61
|
+
panBetweenMarkers = false,
|
|
62
|
+
cycleMarkers = false,
|
|
63
|
+
markerHoldMs = MARKER_HOLD_MS,
|
|
64
|
+
markerTransitionMs = MARKER_TRANSITION_MS,
|
|
65
|
+
}: {
|
|
66
|
+
className?: string
|
|
67
|
+
config?: COBEOptions
|
|
68
|
+
autoRotate?: boolean
|
|
69
|
+
interactive?: boolean
|
|
70
|
+
/** @deprecated Usa cycleMarkers */
|
|
71
|
+
panBetweenMarkers?: boolean
|
|
72
|
+
/** Centra cada marker uno por uno con pausa y transición suave. */
|
|
73
|
+
cycleMarkers?: boolean
|
|
74
|
+
markerHoldMs?: number
|
|
75
|
+
markerTransitionMs?: number
|
|
76
|
+
}) {
|
|
77
|
+
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
78
|
+
const phiRef = useRef(config.phi ?? 0)
|
|
79
|
+
const widthRef = useRef(0)
|
|
80
|
+
const pointerInteracting = useRef<number | null>(null)
|
|
81
|
+
const pointerInteractionMovement = useRef(0)
|
|
82
|
+
|
|
83
|
+
const shouldCycleMarkers = cycleMarkers || panBetweenMarkers
|
|
84
|
+
|
|
85
|
+
const fixedTheta = config.theta ?? 0.22
|
|
86
|
+
|
|
87
|
+
const markerViews = useMemo((): Array<{ phi: number }> | null => {
|
|
88
|
+
if (!shouldCycleMarkers || !config.markers?.length) return null
|
|
89
|
+
return config.markers.map((marker) => ({
|
|
90
|
+
phi: Math.PI - ((marker.location[1] * Math.PI) / 180 - Math.PI / 2),
|
|
91
|
+
}))
|
|
92
|
+
}, [shouldCycleMarkers, config.markers])
|
|
93
|
+
|
|
94
|
+
const markerPhiRange = useMemo((): [number, number] | null => {
|
|
95
|
+
if (!panBetweenMarkers || cycleMarkers || !config.markers?.length) return null
|
|
96
|
+
const phis = config.markers.map(
|
|
97
|
+
(marker) => Math.PI - ((marker.location[1] * Math.PI) / 180 - Math.PI / 2),
|
|
98
|
+
)
|
|
99
|
+
return [Math.min(...phis), Math.max(...phis)]
|
|
100
|
+
}, [panBetweenMarkers, cycleMarkers, config.markers])
|
|
101
|
+
|
|
102
|
+
const r = useMotionValue(0)
|
|
103
|
+
const rs = useSpring(r, {
|
|
104
|
+
mass: 1,
|
|
105
|
+
damping: 30,
|
|
106
|
+
stiffness: 100,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const updatePointerInteraction = (value: number | null) => {
|
|
110
|
+
if (!interactive) return
|
|
111
|
+
pointerInteracting.current = value
|
|
112
|
+
if (canvasRef.current) {
|
|
113
|
+
canvasRef.current.style.cursor = value !== null ? "grabbing" : "grab"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const updateMovement = (clientX: number) => {
|
|
118
|
+
if (!interactive) return
|
|
119
|
+
if (pointerInteracting.current !== null) {
|
|
120
|
+
const delta = clientX - pointerInteracting.current
|
|
121
|
+
pointerInteractionMovement.current = delta
|
|
122
|
+
r.set(r.get() + delta / MOVEMENT_DAMPING)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const canvas = canvasRef.current
|
|
128
|
+
if (!canvas) return
|
|
129
|
+
|
|
130
|
+
const onResize = () => {
|
|
131
|
+
widthRef.current = canvas.offsetWidth
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
onResize()
|
|
135
|
+
window.addEventListener("resize", onResize)
|
|
136
|
+
|
|
137
|
+
const resizeObserver = new ResizeObserver(onResize)
|
|
138
|
+
resizeObserver.observe(canvas)
|
|
139
|
+
|
|
140
|
+
const globe = createGlobe(canvas, {
|
|
141
|
+
...config,
|
|
142
|
+
width: widthRef.current * 2,
|
|
143
|
+
height: widthRef.current * 2,
|
|
144
|
+
onRender: (state) => {
|
|
145
|
+
if (shouldCycleMarkers && markerViews && markerViews.length > 0) {
|
|
146
|
+
const segment = markerHoldMs + markerTransitionMs
|
|
147
|
+
const total = markerViews.length * segment
|
|
148
|
+
const elapsed = Date.now() % total
|
|
149
|
+
const index = Math.floor(elapsed / segment)
|
|
150
|
+
const local = elapsed % segment
|
|
151
|
+
const from = markerViews[index % markerViews.length]
|
|
152
|
+
const to = markerViews[(index + 1) % markerViews.length]
|
|
153
|
+
|
|
154
|
+
if (local < markerHoldMs) {
|
|
155
|
+
state.phi = from.phi
|
|
156
|
+
} else {
|
|
157
|
+
const t = easeInOutCubic((local - markerHoldMs) / markerTransitionMs)
|
|
158
|
+
state.phi = lerpAngle(from.phi, to.phi, t)
|
|
159
|
+
}
|
|
160
|
+
state.theta = fixedTheta
|
|
161
|
+
} else if (panBetweenMarkers && markerPhiRange) {
|
|
162
|
+
const [phiMin, phiMax] = markerPhiRange
|
|
163
|
+
const t = Date.now() / 5200
|
|
164
|
+
state.phi = phiMin + ((Math.sin(t) + 1) / 2) * (phiMax - phiMin)
|
|
165
|
+
} else if (autoRotate && !pointerInteracting.current) {
|
|
166
|
+
phiRef.current += 0.005
|
|
167
|
+
state.phi = interactive ? phiRef.current + rs.get() : (config.phi ?? 0)
|
|
168
|
+
} else {
|
|
169
|
+
state.phi = interactive ? phiRef.current + rs.get() : (config.phi ?? 0)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
state.theta = shouldCycleMarkers ? fixedTheta : (config.theta ?? state.theta)
|
|
173
|
+
state.scale = config.scale ?? 1
|
|
174
|
+
state.width = widthRef.current * 2
|
|
175
|
+
state.height = widthRef.current * 2
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
canvas.style.opacity = "1"
|
|
181
|
+
}, 0)
|
|
182
|
+
|
|
183
|
+
return () => {
|
|
184
|
+
globe.destroy()
|
|
185
|
+
window.removeEventListener("resize", onResize)
|
|
186
|
+
resizeObserver.disconnect()
|
|
187
|
+
}
|
|
188
|
+
}, [
|
|
189
|
+
rs,
|
|
190
|
+
config,
|
|
191
|
+
autoRotate,
|
|
192
|
+
interactive,
|
|
193
|
+
panBetweenMarkers,
|
|
194
|
+
cycleMarkers,
|
|
195
|
+
shouldCycleMarkers,
|
|
196
|
+
markerViews,
|
|
197
|
+
markerPhiRange,
|
|
198
|
+
markerHoldMs,
|
|
199
|
+
markerTransitionMs,
|
|
200
|
+
])
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
className={cn(
|
|
205
|
+
"absolute inset-0 mx-auto aspect-square w-full max-w-150",
|
|
206
|
+
className
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
<canvas
|
|
210
|
+
className={cn(
|
|
211
|
+
"size-full opacity-0 transition-opacity duration-500 contain-[layout_paint_size]",
|
|
212
|
+
interactive ? "cursor-grab" : "cursor-default",
|
|
213
|
+
)}
|
|
214
|
+
ref={canvasRef}
|
|
215
|
+
onPointerDown={
|
|
216
|
+
interactive
|
|
217
|
+
? (e) => {
|
|
218
|
+
pointerInteracting.current = e.clientX
|
|
219
|
+
updatePointerInteraction(e.clientX)
|
|
220
|
+
}
|
|
221
|
+
: undefined
|
|
222
|
+
}
|
|
223
|
+
onPointerUp={interactive ? () => updatePointerInteraction(null) : undefined}
|
|
224
|
+
onPointerOut={interactive ? () => updatePointerInteraction(null) : undefined}
|
|
225
|
+
onMouseMove={interactive ? (e) => updateMovement(e.clientX) : undefined}
|
|
226
|
+
onTouchMove={
|
|
227
|
+
interactive
|
|
228
|
+
? (e) => e.touches[0] && updateMovement(e.touches[0].clientX)
|
|
229
|
+
: undefined
|
|
230
|
+
}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
function HoverCard({
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
|
11
|
+
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function HoverCardTrigger({
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
|
17
|
+
return (
|
|
18
|
+
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function HoverCardContent({
|
|
23
|
+
className,
|
|
24
|
+
align = "center",
|
|
25
|
+
sideOffset = 4,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
|
|
28
|
+
return (
|
|
29
|
+
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
|
|
30
|
+
<HoverCardPrimitive.Content
|
|
31
|
+
data-slot="hover-card-content"
|
|
32
|
+
align={align}
|
|
33
|
+
sideOffset={sideOffset}
|
|
34
|
+
className={cn(
|
|
35
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
</HoverCardPrimitive.Portal>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
package/src/ui/icon.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
const iconVariants = cva("inline-flex items-start", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
green: "text-[#a5dd64]",
|
|
10
|
+
blue: "text-[#000b27]",
|
|
11
|
+
white: "text-white",
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
xs: "[&_svg]:h-6",
|
|
15
|
+
sm: "[&_svg]:h-10",
|
|
16
|
+
md: "[&_svg]:h-14",
|
|
17
|
+
lg: "[&_svg]:h-20",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
variant: "green",
|
|
22
|
+
size: "md",
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const ICON_PATH =
|
|
27
|
+
"M137.5,137.32c32.32-32.32,31.39-85.48-2.79-116.6-30.34-27.63-78.03-27.63-108.37,0C-2.4,46.89-7.62,88.67,10.67,120.51c7.17,12.47,18.17,22.28,31.67,27.27,11.56,4.27,23.9,5.5,33.94,3.23l-20.23,19.95,23.62,23.62s55.88-55.31,57.83-57.26ZM55.69,105.19c-13.7-13.7-13.7-35.99,0-49.69,13.7-13.7,35.99-13.7,49.69,0,13.7,13.7,13.7,35.99,0,49.69-13.7,13.7-35.99,13.7-49.69,0Z"
|
|
28
|
+
|
|
29
|
+
type IconProps = React.ComponentProps<"span"> &
|
|
30
|
+
VariantProps<typeof iconVariants> & {
|
|
31
|
+
/** Muestra ® junto al isotipo. */
|
|
32
|
+
registered?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Icon({
|
|
36
|
+
className,
|
|
37
|
+
variant = "green",
|
|
38
|
+
size = "md",
|
|
39
|
+
registered = false,
|
|
40
|
+
...props
|
|
41
|
+
}: IconProps) {
|
|
42
|
+
return (
|
|
43
|
+
<span
|
|
44
|
+
data-slot="icon"
|
|
45
|
+
data-variant={variant}
|
|
46
|
+
data-size={size}
|
|
47
|
+
className={cn(iconVariants({ variant, size }), className)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<svg
|
|
51
|
+
viewBox="0 0 161.06 194.58"
|
|
52
|
+
role="img"
|
|
53
|
+
aria-label="Traky"
|
|
54
|
+
className="block w-auto fill-current"
|
|
55
|
+
>
|
|
56
|
+
<path d={ICON_PATH} />
|
|
57
|
+
</svg>
|
|
58
|
+
{registered ? (
|
|
59
|
+
<sup
|
|
60
|
+
className="ml-0.5 text-[0.55em] leading-none font-semibold select-none opacity-80"
|
|
61
|
+
aria-hidden
|
|
62
|
+
>
|
|
63
|
+
®
|
|
64
|
+
</sup>
|
|
65
|
+
) : null}
|
|
66
|
+
</span>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { Icon, iconVariants }
|
|
71
|
+
export type { IconProps }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
import { Button } from "./button"
|
|
8
|
+
import { Input } from "./input"
|
|
9
|
+
import { Textarea } from "./textarea"
|
|
10
|
+
|
|
11
|
+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-slot="input-group"
|
|
15
|
+
role="group"
|
|
16
|
+
className={cn(
|
|
17
|
+
"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
|
|
18
|
+
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
19
|
+
|
|
20
|
+
// Variants based on alignment.
|
|
21
|
+
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
22
|
+
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
23
|
+
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
24
|
+
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
25
|
+
|
|
26
|
+
// Focus state.
|
|
27
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
28
|
+
|
|
29
|
+
// Error state.
|
|
30
|
+
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
31
|
+
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const inputGroupAddonVariants = cva(
|
|
40
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
41
|
+
{
|
|
42
|
+
variants: {
|
|
43
|
+
align: {
|
|
44
|
+
"inline-start":
|
|
45
|
+
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
|
46
|
+
"inline-end":
|
|
47
|
+
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
|
48
|
+
"block-start":
|
|
49
|
+
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
|
|
50
|
+
"block-end":
|
|
51
|
+
"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: {
|
|
55
|
+
align: "inline-start",
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
function InputGroupAddon({
|
|
61
|
+
className,
|
|
62
|
+
align = "inline-start",
|
|
63
|
+
...props
|
|
64
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
role="group"
|
|
68
|
+
data-slot="input-group-addon"
|
|
69
|
+
data-align={align}
|
|
70
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
71
|
+
onClick={(e) => {
|
|
72
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
76
|
+
}}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const inputGroupButtonVariants = cva(
|
|
83
|
+
"flex items-center gap-2 text-sm shadow-none",
|
|
84
|
+
{
|
|
85
|
+
variants: {
|
|
86
|
+
size: {
|
|
87
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
|
88
|
+
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
|
|
89
|
+
"icon-xs":
|
|
90
|
+
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
|
91
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
defaultVariants: {
|
|
95
|
+
size: "xs",
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
function InputGroupButton({
|
|
101
|
+
className,
|
|
102
|
+
type = "button",
|
|
103
|
+
variant = "ghost",
|
|
104
|
+
size = "xs",
|
|
105
|
+
...props
|
|
106
|
+
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
|
107
|
+
VariantProps<typeof inputGroupButtonVariants>) {
|
|
108
|
+
return (
|
|
109
|
+
<Button
|
|
110
|
+
type={type}
|
|
111
|
+
data-size={size}
|
|
112
|
+
variant={variant}
|
|
113
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
120
|
+
return (
|
|
121
|
+
<span
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function InputGroupInput({
|
|
132
|
+
className,
|
|
133
|
+
...props
|
|
134
|
+
}: React.ComponentProps<"input">) {
|
|
135
|
+
return (
|
|
136
|
+
<Input
|
|
137
|
+
data-slot="input-group-control"
|
|
138
|
+
className={cn(
|
|
139
|
+
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function InputGroupTextarea({
|
|
148
|
+
className,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<"textarea">) {
|
|
151
|
+
return (
|
|
152
|
+
<Textarea
|
|
153
|
+
data-slot="input-group-control"
|
|
154
|
+
className={cn(
|
|
155
|
+
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
156
|
+
className
|
|
157
|
+
)}
|
|
158
|
+
{...props}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
InputGroup,
|
|
165
|
+
InputGroupAddon,
|
|
166
|
+
InputGroupButton,
|
|
167
|
+
InputGroupText,
|
|
168
|
+
InputGroupInput,
|
|
169
|
+
InputGroupTextarea,
|
|
170
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { IconMinus } from "@tabler/icons-react";
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
import { OTPInput, OTPInputContext } from "input-otp"
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
function InputOTP({
|
|
9
|
+
className,
|
|
10
|
+
containerClassName,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof OTPInput> & {
|
|
13
|
+
containerClassName?: string
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<OTPInput
|
|
17
|
+
data-slot="input-otp"
|
|
18
|
+
data-e2e="auth-otp-input"
|
|
19
|
+
containerClassName={cn(
|
|
20
|
+
"flex items-center gap-2 has-disabled:opacity-50",
|
|
21
|
+
containerClassName
|
|
22
|
+
)}
|
|
23
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
data-slot="input-otp-group"
|
|
33
|
+
className={cn("flex items-center", className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function InputOTPSlot({
|
|
40
|
+
index,
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<"div"> & {
|
|
44
|
+
index: number
|
|
45
|
+
}) {
|
|
46
|
+
const inputOTPContext = React.useContext(OTPInputContext)
|
|
47
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="input-otp-slot"
|
|
52
|
+
data-active={isActive}
|
|
53
|
+
className={cn(
|
|
54
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[3px] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",
|
|
55
|
+
className
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{char}
|
|
60
|
+
{hasFakeCaret && (
|
|
61
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
62
|
+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|
70
|
+
return (
|
|
71
|
+
<div data-slot="input-otp-separator" role="separator" {...props}>
|
|
72
|
+
<IconMinus />
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
package/src/ui/input.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { DISABLE_INPUT_AUTOFILL } from "../lib/disable-input-autofill"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
7
|
+
return (
|
|
8
|
+
<input
|
|
9
|
+
type={type}
|
|
10
|
+
data-slot="input"
|
|
11
|
+
// Auth debe pasar autoComplete explícito (email, current-password, …).
|
|
12
|
+
{...DISABLE_INPUT_AUTOFILL}
|
|
13
|
+
className={cn(
|
|
14
|
+
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
15
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
16
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Input }
|