@annondeveloper/ui-kit 0.1.0 → 0.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/dist/{chunk-5OKSXPWK.js → chunk-2DWZVHZS.js} +2 -2
- package/dist/chunk-2DWZVHZS.js.map +1 -0
- package/dist/form.d.ts +6 -6
- package/dist/form.js +1 -1
- package/dist/form.js.map +1 -1
- package/dist/index.d.ts +508 -52
- package/dist/index.js +2927 -4
- package/dist/index.js.map +1 -1
- package/dist/{select-nnBJUO8U.d.ts → select-B2wXqqSM.d.ts} +2 -2
- package/package.json +1 -1
- package/src/components/animated-counter.tsx +2 -1
- package/src/components/avatar.tsx +2 -1
- package/src/components/badge.tsx +3 -2
- package/src/components/button.tsx +3 -2
- package/src/components/card.tsx +13 -12
- package/src/components/checkbox.tsx +3 -2
- package/src/components/color-input.tsx +414 -0
- package/src/components/command-bar.tsx +434 -0
- package/src/components/confidence-bar.tsx +115 -0
- package/src/components/confirm-dialog.tsx +2 -1
- package/src/components/copy-block.tsx +229 -0
- package/src/components/data-table.tsx +2 -1
- package/src/components/diff-viewer.tsx +319 -0
- package/src/components/dropdown-menu.tsx +2 -1
- package/src/components/empty-state.tsx +2 -1
- package/src/components/filter-pill.tsx +2 -1
- package/src/components/form-input.tsx +5 -4
- package/src/components/heatmap-calendar.tsx +213 -0
- package/src/components/infinite-scroll.tsx +243 -0
- package/src/components/kanban-column.tsx +198 -0
- package/src/components/live-feed.tsx +220 -0
- package/src/components/log-viewer.tsx +2 -1
- package/src/components/metric-card.tsx +2 -1
- package/src/components/notification-stack.tsx +226 -0
- package/src/components/pipeline-stage.tsx +2 -1
- package/src/components/popover.tsx +2 -1
- package/src/components/port-status-grid.tsx +2 -1
- package/src/components/progress.tsx +2 -1
- package/src/components/radio-group.tsx +2 -1
- package/src/components/realtime-value.tsx +283 -0
- package/src/components/select.tsx +2 -1
- package/src/components/severity-timeline.tsx +2 -1
- package/src/components/sheet.tsx +2 -1
- package/src/components/skeleton.tsx +4 -3
- package/src/components/slider.tsx +2 -1
- package/src/components/smart-table.tsx +383 -0
- package/src/components/sortable-list.tsx +268 -0
- package/src/components/sparkline.tsx +2 -1
- package/src/components/status-badge.tsx +2 -1
- package/src/components/status-pulse.tsx +2 -1
- package/src/components/step-wizard.tsx +372 -0
- package/src/components/streaming-text.tsx +163 -0
- package/src/components/success-checkmark.tsx +2 -1
- package/src/components/tabs.tsx +2 -1
- package/src/components/threshold-gauge.tsx +2 -1
- package/src/components/time-range-selector.tsx +2 -1
- package/src/components/toast.tsx +2 -1
- package/src/components/toggle-switch.tsx +2 -1
- package/src/components/tooltip.tsx +2 -1
- package/src/components/truncated-text.tsx +2 -1
- package/src/components/typing-indicator.tsx +123 -0
- package/src/components/uptime-tracker.tsx +2 -1
- package/src/components/utilization-bar.tsx +2 -1
- package/src/utils.ts +1 -1
- package/dist/chunk-5OKSXPWK.js.map +0 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import { useState, useEffect, useRef, useMemo } from 'react'
|
|
5
|
+
import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'
|
|
6
|
+
import { ArrowUp, ArrowDown, Minus, Wifi, WifiOff, Loader2 } from 'lucide-react'
|
|
7
|
+
import { cn } from '../utils'
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/** Props for the RealtimeValue component. */
|
|
14
|
+
export interface RealtimeValueProps {
|
|
15
|
+
/** The current value to display. */
|
|
16
|
+
value: number | string
|
|
17
|
+
/** Optional label displayed above or beside the value. */
|
|
18
|
+
label?: string
|
|
19
|
+
/** Custom formatter for numeric values. */
|
|
20
|
+
format?: (v: number) => string
|
|
21
|
+
/** ISO timestamp or Date of the last data update. */
|
|
22
|
+
lastUpdated?: string | Date
|
|
23
|
+
/** Milliseconds after which the value is considered stale. Default 30000. */
|
|
24
|
+
staleAfterMs?: number
|
|
25
|
+
/** Connection state to the data source. */
|
|
26
|
+
connectionState?: 'connected' | 'reconnecting' | 'disconnected'
|
|
27
|
+
/** Previous numeric value for delta/change display. */
|
|
28
|
+
previousValue?: number
|
|
29
|
+
/** Whether to animate value changes. Default true. */
|
|
30
|
+
animate?: boolean
|
|
31
|
+
/** Display size variant. */
|
|
32
|
+
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
33
|
+
/** Additional class name for the root element. */
|
|
34
|
+
className?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function easeOutCubic(t: number): number {
|
|
42
|
+
return 1 - Math.pow(1 - t, 3)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatRelativeSeconds(ms: number): string {
|
|
46
|
+
const secs = Math.floor(ms / 1000)
|
|
47
|
+
if (secs < 1) return 'just now'
|
|
48
|
+
if (secs < 60) return `${secs}s ago`
|
|
49
|
+
if (secs < 3600) return `${Math.floor(secs / 60)}m ago`
|
|
50
|
+
return `${Math.floor(secs / 3600)}h ago`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SIZE_CLASSES = {
|
|
54
|
+
sm: { value: 'text-lg', label: 'text-[11px]', delta: 'text-[10px]', dot: 'h-1.5 w-1.5', gap: 'gap-1' },
|
|
55
|
+
md: { value: 'text-2xl', label: 'text-xs', delta: 'text-[11px]', dot: 'h-2 w-2', gap: 'gap-1.5' },
|
|
56
|
+
lg: { value: 'text-3xl', label: 'text-sm', delta: 'text-xs', dot: 'h-2.5 w-2.5', gap: 'gap-2' },
|
|
57
|
+
xl: { value: 'text-4xl', label: 'text-base', delta: 'text-sm', dot: 'h-3 w-3', gap: 'gap-2' },
|
|
58
|
+
} as const
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// AnimatedNumber (internal)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function AnimatedNumber({
|
|
65
|
+
value,
|
|
66
|
+
format,
|
|
67
|
+
duration = 400,
|
|
68
|
+
animateEnabled,
|
|
69
|
+
reduced,
|
|
70
|
+
className,
|
|
71
|
+
}: {
|
|
72
|
+
value: number
|
|
73
|
+
format?: (v: number) => string
|
|
74
|
+
duration?: number
|
|
75
|
+
animateEnabled: boolean
|
|
76
|
+
reduced: boolean | null
|
|
77
|
+
className?: string
|
|
78
|
+
}): React.JSX.Element {
|
|
79
|
+
const prevRef = useRef(value)
|
|
80
|
+
const rafRef = useRef<number | null>(null)
|
|
81
|
+
const [displayed, setDisplayed] = useState(value)
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const from = prevRef.current
|
|
85
|
+
const to = value
|
|
86
|
+
prevRef.current = value
|
|
87
|
+
|
|
88
|
+
if (reduced || !animateEnabled || from === to) {
|
|
89
|
+
setDisplayed(to)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const start = performance.now()
|
|
94
|
+
function tick(now: number) {
|
|
95
|
+
const elapsed = now - start
|
|
96
|
+
const progress = Math.min(elapsed / duration, 1)
|
|
97
|
+
const eased = easeOutCubic(progress)
|
|
98
|
+
setDisplayed(from + (to - from) * eased)
|
|
99
|
+
if (progress < 1) {
|
|
100
|
+
rafRef.current = requestAnimationFrame(tick)
|
|
101
|
+
} else {
|
|
102
|
+
setDisplayed(to)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
rafRef.current = requestAnimationFrame(tick)
|
|
107
|
+
return () => {
|
|
108
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)
|
|
109
|
+
}
|
|
110
|
+
}, [value, duration, reduced, animateEnabled])
|
|
111
|
+
|
|
112
|
+
const formatted = format
|
|
113
|
+
? format(displayed)
|
|
114
|
+
: Number.isInteger(value)
|
|
115
|
+
? Math.round(displayed).toString()
|
|
116
|
+
: displayed.toFixed(value.toString().split('.')[1]?.length ?? 1)
|
|
117
|
+
|
|
118
|
+
return <span className={cn('tabular-nums', className)}>{formatted}</span>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// RealtimeValue
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @description A live data display component with built-in freshness tracking,
|
|
127
|
+
* connection state indicators, animated value transitions, delta display,
|
|
128
|
+
* and auto-updating relative timestamps. Designed for real-time monitoring dashboards.
|
|
129
|
+
*/
|
|
130
|
+
export function RealtimeValue({
|
|
131
|
+
value,
|
|
132
|
+
label,
|
|
133
|
+
format,
|
|
134
|
+
lastUpdated,
|
|
135
|
+
staleAfterMs = 30000,
|
|
136
|
+
connectionState = 'connected',
|
|
137
|
+
previousValue,
|
|
138
|
+
animate = true,
|
|
139
|
+
size = 'md',
|
|
140
|
+
className,
|
|
141
|
+
}: RealtimeValueProps): React.JSX.Element {
|
|
142
|
+
const prefersReducedMotion = useReducedMotion()
|
|
143
|
+
const sizeClasses = SIZE_CLASSES[size]
|
|
144
|
+
|
|
145
|
+
// Auto-updating staleness
|
|
146
|
+
const [staleness, setStaleness] = useState(0)
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (!lastUpdated) return
|
|
149
|
+
const getMs = () => Date.now() - new Date(lastUpdated).getTime()
|
|
150
|
+
setStaleness(getMs())
|
|
151
|
+
const interval = setInterval(() => setStaleness(getMs()), 1000)
|
|
152
|
+
return () => clearInterval(interval)
|
|
153
|
+
}, [lastUpdated])
|
|
154
|
+
|
|
155
|
+
const isStale = lastUpdated ? staleness > staleAfterMs : false
|
|
156
|
+
const isVeryStale = lastUpdated ? staleness > staleAfterMs * 2 : false
|
|
157
|
+
|
|
158
|
+
// Freshness dot color
|
|
159
|
+
const freshnessColor = useMemo(() => {
|
|
160
|
+
if (connectionState === 'disconnected') return 'bg-[hsl(var(--status-critical))]'
|
|
161
|
+
if (isVeryStale) return 'bg-[hsl(var(--status-critical))]'
|
|
162
|
+
if (isStale) return 'bg-[hsl(var(--status-warning))]'
|
|
163
|
+
return 'bg-[hsl(var(--status-ok))]'
|
|
164
|
+
}, [connectionState, isStale, isVeryStale])
|
|
165
|
+
|
|
166
|
+
// Delta calculation
|
|
167
|
+
const delta = typeof value === 'number' && previousValue !== undefined ? value - previousValue : null
|
|
168
|
+
const deltaSign = delta !== null ? (delta > 0 ? '+' : delta < 0 ? '' : '') : null
|
|
169
|
+
|
|
170
|
+
const isNumeric = typeof value === 'number'
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div
|
|
174
|
+
className={cn(
|
|
175
|
+
'relative inline-flex flex-col',
|
|
176
|
+
sizeClasses.gap,
|
|
177
|
+
isStale && 'opacity-60',
|
|
178
|
+
className,
|
|
179
|
+
)}
|
|
180
|
+
title={
|
|
181
|
+
lastUpdated
|
|
182
|
+
? `Last updated: ${new Date(lastUpdated).toLocaleString()} (${formatRelativeSeconds(staleness)})`
|
|
183
|
+
: undefined
|
|
184
|
+
}
|
|
185
|
+
>
|
|
186
|
+
{/* Label row */}
|
|
187
|
+
{label && (
|
|
188
|
+
<div className="flex items-center gap-1.5">
|
|
189
|
+
<span className={cn('font-medium text-[hsl(var(--text-secondary))]', sizeClasses.label)}>
|
|
190
|
+
{label}
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
{/* Value row */}
|
|
196
|
+
<div className="flex items-center gap-2">
|
|
197
|
+
{/* Freshness indicator */}
|
|
198
|
+
<div className="relative flex items-center justify-center">
|
|
199
|
+
<span className={cn('rounded-full', sizeClasses.dot, freshnessColor)} />
|
|
200
|
+
{connectionState === 'connected' && !isStale && (
|
|
201
|
+
<span
|
|
202
|
+
className={cn(
|
|
203
|
+
'absolute rounded-full animate-ping',
|
|
204
|
+
sizeClasses.dot,
|
|
205
|
+
'bg-[hsl(var(--status-ok)/0.5)]',
|
|
206
|
+
)}
|
|
207
|
+
style={{ animationDuration: '2s' }}
|
|
208
|
+
/>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* Main value */}
|
|
213
|
+
<div className="flex items-baseline gap-1.5">
|
|
214
|
+
{isNumeric ? (
|
|
215
|
+
<AnimatedNumber
|
|
216
|
+
value={value}
|
|
217
|
+
format={format}
|
|
218
|
+
animateEnabled={animate}
|
|
219
|
+
reduced={prefersReducedMotion}
|
|
220
|
+
className={cn('font-semibold text-[hsl(var(--text-primary))]', sizeClasses.value)}
|
|
221
|
+
/>
|
|
222
|
+
) : (
|
|
223
|
+
<span className={cn('font-semibold text-[hsl(var(--text-primary))] tabular-nums', sizeClasses.value)}>
|
|
224
|
+
{value}
|
|
225
|
+
</span>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{/* Delta indicator */}
|
|
229
|
+
<AnimatePresence>
|
|
230
|
+
{delta !== null && delta !== 0 && (
|
|
231
|
+
<motion.span
|
|
232
|
+
initial={prefersReducedMotion ? undefined : { opacity: 0, x: -4 }}
|
|
233
|
+
animate={prefersReducedMotion ? undefined : { opacity: 1, x: 0 }}
|
|
234
|
+
exit={prefersReducedMotion ? undefined : { opacity: 0, x: -4 }}
|
|
235
|
+
transition={{ duration: 0.15 }}
|
|
236
|
+
className={cn(
|
|
237
|
+
'inline-flex items-center gap-0.5 font-medium tabular-nums',
|
|
238
|
+
sizeClasses.delta,
|
|
239
|
+
delta > 0 ? 'text-[hsl(var(--status-ok))]' : 'text-[hsl(var(--status-critical))]',
|
|
240
|
+
)}
|
|
241
|
+
>
|
|
242
|
+
{delta > 0 ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />}
|
|
243
|
+
{deltaSign}{format ? format(Math.abs(delta)) : Math.abs(delta).toLocaleString()}
|
|
244
|
+
</motion.span>
|
|
245
|
+
)}
|
|
246
|
+
{delta === 0 && (
|
|
247
|
+
<motion.span
|
|
248
|
+
initial={prefersReducedMotion ? undefined : { opacity: 0 }}
|
|
249
|
+
animate={prefersReducedMotion ? undefined : { opacity: 1 }}
|
|
250
|
+
className={cn(
|
|
251
|
+
'inline-flex items-center gap-0.5 font-medium text-[hsl(var(--text-tertiary))]',
|
|
252
|
+
sizeClasses.delta,
|
|
253
|
+
)}
|
|
254
|
+
>
|
|
255
|
+
<Minus className="h-3 w-3" />
|
|
256
|
+
0
|
|
257
|
+
</motion.span>
|
|
258
|
+
)}
|
|
259
|
+
</AnimatePresence>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{/* Connection state icon */}
|
|
263
|
+
{connectionState !== 'connected' && (
|
|
264
|
+
<span className="ml-1">
|
|
265
|
+
{connectionState === 'reconnecting' && (
|
|
266
|
+
<Loader2 className="h-4 w-4 text-[hsl(var(--status-warning))] animate-spin" />
|
|
267
|
+
)}
|
|
268
|
+
{connectionState === 'disconnected' && (
|
|
269
|
+
<WifiOff className="h-4 w-4 text-[hsl(var(--status-critical))]" />
|
|
270
|
+
)}
|
|
271
|
+
</span>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{/* Relative timestamp */}
|
|
276
|
+
{lastUpdated && (
|
|
277
|
+
<span className={cn('text-[hsl(var(--text-tertiary))] tabular-nums', sizeClasses.delta)}>
|
|
278
|
+
{formatRelativeSeconds(staleness)}
|
|
279
|
+
</span>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type React from 'react'
|
|
3
4
|
import * as RadixSelect from '@radix-ui/react-select'
|
|
4
5
|
import { ChevronDown, Check } from 'lucide-react'
|
|
5
6
|
import { cn } from '../utils'
|
|
@@ -29,7 +30,7 @@ export interface SelectProps {
|
|
|
29
30
|
*/
|
|
30
31
|
export function Select({
|
|
31
32
|
value, onValueChange, options, placeholder, className, disabled,
|
|
32
|
-
}: SelectProps) {
|
|
33
|
+
}: SelectProps): React.JSX.Element {
|
|
33
34
|
return (
|
|
34
35
|
<RadixSelect.Root value={value} onValueChange={onValueChange} disabled={disabled}>
|
|
35
36
|
<RadixSelect.Trigger
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type React from 'react'
|
|
3
4
|
import { useRef } from 'react'
|
|
4
5
|
import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'
|
|
5
6
|
import { cn } from '../utils'
|
|
@@ -58,7 +59,7 @@ export function SeverityTimeline({
|
|
|
58
59
|
maxVisible = 20,
|
|
59
60
|
onEventClick,
|
|
60
61
|
className,
|
|
61
|
-
}: SeverityTimelineProps) {
|
|
62
|
+
}: SeverityTimelineProps): React.JSX.Element | null {
|
|
62
63
|
const reduced = useReducedMotion()
|
|
63
64
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
64
65
|
const visible = events.slice(0, maxVisible)
|
package/src/components/sheet.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type React from 'react'
|
|
3
4
|
import { useEffect, useCallback, useRef, type ReactNode } from 'react'
|
|
4
5
|
import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'
|
|
5
6
|
import { X } from 'lucide-react'
|
|
@@ -52,7 +53,7 @@ export function Sheet({
|
|
|
52
53
|
width = 'max-w-md',
|
|
53
54
|
children,
|
|
54
55
|
className,
|
|
55
|
-
}: SheetProps) {
|
|
56
|
+
}: SheetProps): React.JSX.Element {
|
|
56
57
|
const prefersReducedMotion = useReducedMotion()
|
|
57
58
|
const panelRef = useRef<HTMLDivElement>(null)
|
|
58
59
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type React from 'react'
|
|
3
4
|
import { cn } from '../utils'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @description A shimmer skeleton loader block. Requires the `skeleton-shimmer` CSS class
|
|
7
8
|
* from theme.css to animate.
|
|
8
9
|
*/
|
|
9
|
-
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
10
|
+
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element {
|
|
10
11
|
return (
|
|
11
12
|
<div
|
|
12
13
|
className={cn('skeleton-shimmer rounded-md', className)}
|
|
@@ -25,7 +26,7 @@ export interface SkeletonTextProps {
|
|
|
25
26
|
* @description A multi-line skeleton text placeholder. The last line renders shorter
|
|
26
27
|
* for a natural paragraph appearance.
|
|
27
28
|
*/
|
|
28
|
-
export function SkeletonText({ lines = 1, className }: SkeletonTextProps) {
|
|
29
|
+
export function SkeletonText({ lines = 1, className }: SkeletonTextProps): React.JSX.Element {
|
|
29
30
|
return (
|
|
30
31
|
<div className={cn('space-y-2', className)}>
|
|
31
32
|
{Array.from({ length: lines }).map((_, i) => (
|
|
@@ -41,7 +42,7 @@ export function SkeletonText({ lines = 1, className }: SkeletonTextProps) {
|
|
|
41
42
|
/**
|
|
42
43
|
* @description A card-shaped skeleton placeholder with header area and content bars.
|
|
43
44
|
*/
|
|
44
|
-
export function SkeletonCard({ className }: { className?: string }) {
|
|
45
|
+
export function SkeletonCard({ className }: { className?: string }): React.JSX.Element {
|
|
45
46
|
return (
|
|
46
47
|
<div className={cn('rounded-lg border border-[hsl(var(--border-subtle))] bg-[hsl(var(--bg-surface))] p-4 space-y-3', className)}>
|
|
47
48
|
<div className="flex items-center gap-3">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type React from 'react'
|
|
3
4
|
import { useCallback, useRef, useState, type KeyboardEvent, type MouseEvent, type TouchEvent } from 'react'
|
|
4
5
|
import { cn } from '../utils'
|
|
5
6
|
|
|
@@ -36,7 +37,7 @@ export function Slider({
|
|
|
36
37
|
label,
|
|
37
38
|
showValue = false,
|
|
38
39
|
className,
|
|
39
|
-
}: SliderProps) {
|
|
40
|
+
}: SliderProps): React.JSX.Element {
|
|
40
41
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
41
42
|
const [isDragging, setIsDragging] = useState(false)
|
|
42
43
|
const [isHovering, setIsHovering] = useState(false)
|