@exxatdesignux/ui 0.0.5

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 (59) hide show
  1. package/package.json +72 -0
  2. package/src/components/ui/avatar.tsx +384 -0
  3. package/src/components/ui/badge.tsx +49 -0
  4. package/src/components/ui/banner.tsx +364 -0
  5. package/src/components/ui/breadcrumb.tsx +120 -0
  6. package/src/components/ui/button.tsx +66 -0
  7. package/src/components/ui/calendar.tsx +220 -0
  8. package/src/components/ui/card.tsx +136 -0
  9. package/src/components/ui/chart.tsx +378 -0
  10. package/src/components/ui/checkbox.tsx +160 -0
  11. package/src/components/ui/coach-mark.tsx +361 -0
  12. package/src/components/ui/collapsible.tsx +33 -0
  13. package/src/components/ui/command.tsx +232 -0
  14. package/src/components/ui/date-picker-field.tsx +186 -0
  15. package/src/components/ui/dialog.tsx +171 -0
  16. package/src/components/ui/drag-handle-grip.tsx +10 -0
  17. package/src/components/ui/drawer.tsx +134 -0
  18. package/src/components/ui/dropdown-menu.tsx +422 -0
  19. package/src/components/ui/field.tsx +238 -0
  20. package/src/components/ui/form.tsx +137 -0
  21. package/src/components/ui/input-group.tsx +156 -0
  22. package/src/components/ui/input-mask.tsx +135 -0
  23. package/src/components/ui/input.tsx +22 -0
  24. package/src/components/ui/kbd.tsx +55 -0
  25. package/src/components/ui/label.tsx +25 -0
  26. package/src/components/ui/payment-card-fields.tsx +65 -0
  27. package/src/components/ui/popover.tsx +46 -0
  28. package/src/components/ui/radio-group.tsx +217 -0
  29. package/src/components/ui/select.tsx +191 -0
  30. package/src/components/ui/selection-tile-grid.tsx +246 -0
  31. package/src/components/ui/separator.tsx +28 -0
  32. package/src/components/ui/sheet.tsx +147 -0
  33. package/src/components/ui/sidebar.tsx +716 -0
  34. package/src/components/ui/skeleton.tsx +13 -0
  35. package/src/components/ui/sonner.tsx +39 -0
  36. package/src/components/ui/status-badge.tsx +109 -0
  37. package/src/components/ui/table.tsx +117 -0
  38. package/src/components/ui/tabs.tsx +90 -0
  39. package/src/components/ui/textarea.tsx +18 -0
  40. package/src/components/ui/tip.tsx +21 -0
  41. package/src/components/ui/toggle-group.tsx +89 -0
  42. package/src/components/ui/toggle-switch.tsx +31 -0
  43. package/src/components/ui/toggle.tsx +48 -0
  44. package/src/components/ui/tooltip.tsx +59 -0
  45. package/src/components/ui/view-segmented-control.tsx +160 -0
  46. package/src/globals.css +1795 -0
  47. package/src/hooks/.gitkeep +0 -0
  48. package/src/hooks/use-app-theme.ts +172 -0
  49. package/src/hooks/use-coach-mark.ts +342 -0
  50. package/src/hooks/use-mobile.ts +31 -0
  51. package/src/hooks/use-mod-key-label.ts +29 -0
  52. package/src/index.ts +55 -0
  53. package/src/lib/compose-refs.ts +15 -0
  54. package/src/lib/date-filter.ts +67 -0
  55. package/src/lib/utils.ts +6 -0
  56. package/src/theme/apply-windows-contrast-theme.ts +29 -0
  57. package/src/theme/windows-contrast-theme.json +147 -0
  58. package/src/theme.css +1130 -0
  59. package/src/types/react-payment-inputs.d.ts +20 -0
@@ -0,0 +1,364 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Banner — two types of banner for system-level and local-level messaging.
5
+ *
6
+ * 1. SystemBanner: full-width strip at the very top of the app (above sidebar).
7
+ * Used for maintenance notices, feature promotions, global alerts.
8
+ *
9
+ * 2. LocalBanner: inline alert within a page section.
10
+ * Used for page-specific errors, warnings, and informational messages.
11
+ *
12
+ * Both support: info, warning, error, success, and promo variants.
13
+ * Both are dismissible and accessible (role="alert" / role="status").
14
+ */
15
+
16
+ import * as React from "react"
17
+ import { cva, type VariantProps } from "class-variance-authority"
18
+ import { cn } from "../../lib/utils"
19
+ import { Button } from "./button"
20
+ import { Tip } from "./tip"
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Variant definitions
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+
26
+ const VARIANT_CONFIG = {
27
+ info: {
28
+ icon: "fa-circle-info",
29
+ prefix: "fa-light",
30
+ role: "status" as const,
31
+ live: "polite" as const,
32
+ },
33
+ warning: {
34
+ icon: "fa-triangle-exclamation",
35
+ prefix: "fa-light",
36
+ role: "alert" as const,
37
+ live: "assertive" as const,
38
+ },
39
+ error: {
40
+ icon: "fa-circle-exclamation",
41
+ prefix: "fa-light",
42
+ role: "alert" as const,
43
+ live: "assertive" as const,
44
+ },
45
+ success: {
46
+ icon: "fa-circle-check",
47
+ prefix: "fa-light",
48
+ role: "status" as const,
49
+ live: "polite" as const,
50
+ },
51
+ promo: {
52
+ icon: "fa-star-christmas text-brand",
53
+ prefix: "fa-duotone fa-solid",
54
+ role: "status" as const,
55
+ live: "polite" as const,
56
+ },
57
+ }
58
+
59
+ type BannerVariant = keyof typeof VARIANT_CONFIG
60
+
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ // SystemBanner — inline at the top of the main content area
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ const systemBannerVariants = cva(
66
+ "relative flex overflow-hidden rounded-lg border text-sm transition-all",
67
+ {
68
+ variants: {
69
+ variant: {
70
+ /* Prominent defaults — darker shells + light foreground copy for ≥4.5:1 on the fill. */
71
+ info: "bg-blue-900 text-blue-50 border-blue-950/35 dark:bg-blue-950 dark:text-blue-50 dark:border-blue-900/60",
72
+ warning: "bg-amber-900 text-amber-50 border-amber-950/35 dark:bg-amber-950 dark:text-amber-50 dark:border-amber-900/60",
73
+ error: "bg-red-900 text-red-50 border-red-950/35 dark:bg-red-950 dark:text-red-50 dark:border-red-900/60",
74
+ success: "bg-emerald-900 text-emerald-50 border-emerald-950/35 dark:bg-emerald-950 dark:text-emerald-50 dark:border-emerald-900/60",
75
+ promo: "bg-gradient-to-r from-brand/14 via-brand/8 to-brand/5 text-foreground border-brand/22 dark:from-brand/20 dark:via-brand/12 dark:to-brand/7 dark:border-brand/26",
76
+ },
77
+ /** Emphasis — "prominent" uses dark solid fills; "subtle" uses soft tinted bg. */
78
+ emphasis: {
79
+ prominent: "",
80
+ subtle: "",
81
+ },
82
+ /** Action placement: "inline" puts the action to the right; "bottom" puts it below the text */
83
+ actionPosition: {
84
+ // flex-wrap so the action button drops to a second line at 200%+ zoom instead of overflowing
85
+ inline: "flex-wrap items-center gap-3 px-4 py-2.5",
86
+ bottom: "flex-col gap-2 px-4 py-3",
87
+ },
88
+ },
89
+ compoundVariants: [
90
+ { emphasis: "subtle", variant: "info", class: "bg-blue-500/5 text-blue-800 border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-200 dark:border-blue-500/20" },
91
+ { emphasis: "subtle", variant: "warning", class: "bg-amber-500/5 text-amber-800 border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200 dark:border-amber-500/20" },
92
+ { emphasis: "subtle", variant: "error", class: "bg-red-500/5 text-red-800 border-red-500/30 dark:bg-red-500/10 dark:text-red-200 dark:border-red-500/20" },
93
+ { emphasis: "subtle", variant: "success", class: "bg-emerald-500/5 text-emerald-800 border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-200 dark:border-emerald-500/20" },
94
+ { emphasis: "subtle", variant: "promo", class: "bg-brand/5 text-foreground border-brand/30 dark:bg-brand/10 dark:border-brand/20" },
95
+ ],
96
+ defaultVariants: { variant: "info", emphasis: "prominent", actionPosition: "inline" },
97
+ }
98
+ )
99
+
100
+ export interface SystemBannerProps
101
+ extends React.HTMLAttributes<HTMLDivElement>,
102
+ VariantProps<typeof systemBannerVariants> {
103
+ /** Banner title (optional — adds a bold heading) */
104
+ title?: string
105
+ /** The banner message */
106
+ children: React.ReactNode
107
+ /** Whether the banner can be dismissed */
108
+ dismissible?: boolean
109
+ /** Callback when dismissed */
110
+ onDismiss?: () => void
111
+ /** Optional action — renders as a link-style button. Use href for server components, onClick for client. */
112
+ action?: {
113
+ label: string
114
+ href?: string
115
+ onClick?: () => void
116
+ }
117
+ /** Where to place the action: "inline" (right side) or "bottom" (below text) */
118
+ actionPosition?: "inline" | "bottom"
119
+ /** Override the default icon */
120
+ icon?: string
121
+ /** Optional absolutely-positioned decorative layer (e.g. `<AiThinkingOverlay />`). */
122
+ decorativeOverlay?: React.ReactNode
123
+ }
124
+
125
+ export function SystemBanner({
126
+ children,
127
+ title,
128
+ variant = "info",
129
+ emphasis = "prominent",
130
+ dismissible = true,
131
+ onDismiss,
132
+ action,
133
+ actionPosition = "inline",
134
+ icon,
135
+ decorativeOverlay,
136
+ className,
137
+ style,
138
+ ...props
139
+ }: SystemBannerProps) {
140
+ const [dismissed, setDismissed] = React.useState(false)
141
+ const config = VARIANT_CONFIG[variant ?? "info"]
142
+
143
+ function handleDismiss() {
144
+ setDismissed(true)
145
+ onDismiss?.()
146
+ }
147
+
148
+ if (dismissed) return null
149
+
150
+ const actionEl = action && (
151
+ action.href ? (
152
+ <a
153
+ href={action.href}
154
+ className="inline-flex shrink-0 items-center gap-1 text-xs font-semibold underline underline-offset-2 hover:no-underline"
155
+ >
156
+ {action.label}
157
+ <i className="fa-light fa-arrow-right text-xs" aria-hidden="true" />
158
+ </a>
159
+ ) : (
160
+ <Button
161
+ size="xs"
162
+ variant="link"
163
+ onClick={action.onClick}
164
+ className="h-auto shrink-0 px-0 text-xs font-semibold underline underline-offset-2 hover:no-underline"
165
+ >
166
+ {action.label}
167
+ <i className="fa-light fa-arrow-right text-xs ml-0.5" aria-hidden="true" />
168
+ </Button>
169
+ )
170
+ )
171
+
172
+ /** Outside-only brand halo (no inset fill — glow does not paint inside the banner). */
173
+ const promoOuterShadow =
174
+ emphasis === "subtle"
175
+ ? "0 10px 36px -6px oklch(from var(--brand-color) l c h / 0.2), 0 2px 12px -4px oklch(from var(--brand-color) l c h / 0.12)"
176
+ : "0 14px 48px -8px oklch(from var(--brand-color) l c h / 0.26), 0 4px 18px -4px oklch(from var(--brand-color) l c h / 0.16)"
177
+
178
+ return (
179
+ <div
180
+ role={config.role}
181
+ aria-live={config.live}
182
+ className={cn(systemBannerVariants({ variant, emphasis, actionPosition }), className)}
183
+ style={{
184
+ ...(variant === "promo" ? { boxShadow: promoOuterShadow } : null),
185
+ ...style,
186
+ }}
187
+ {...props}
188
+ >
189
+ {decorativeOverlay}
190
+ {/* Icon */}
191
+ <i
192
+ className={cn(config.prefix, icon ?? config.icon, "shrink-0 text-[14px]", actionPosition === "bottom" ? "mt-0.5" : "")}
193
+ aria-hidden="true"
194
+ />
195
+
196
+ {/* Content + inline action */}
197
+ {actionPosition === "inline" ? (
198
+ <>
199
+ <div className="min-w-0 flex-1">
200
+ {title && <span className="font-semibold mr-1.5">{title}</span>}
201
+ <span className="opacity-90">{children}</span>
202
+ </div>
203
+ {actionEl}
204
+ </>
205
+ ) : (
206
+ <div className="min-w-0 flex-1">
207
+ {title && <p className="font-semibold leading-tight mb-0.5">{title}</p>}
208
+ <p className="opacity-90 leading-relaxed">{children}</p>
209
+ {actionEl && <div className="mt-1.5">{actionEl}</div>}
210
+ </div>
211
+ )}
212
+
213
+ {/* Dismiss */}
214
+ {dismissible && (
215
+ <Tip label="Dismiss" side="bottom">
216
+ <button
217
+ type="button"
218
+ aria-label="Dismiss banner"
219
+ onClick={handleDismiss}
220
+ className={cn(
221
+ "inline-flex size-5 shrink-0 items-center justify-center rounded transition-colors",
222
+ actionPosition === "bottom" ? "absolute top-2.5 right-2.5" : "",
223
+ "hover:bg-current/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
224
+ )}
225
+ >
226
+ <i className="fa-light fa-xmark text-xs" aria-hidden="true" />
227
+ </button>
228
+ </Tip>
229
+ )}
230
+ </div>
231
+ )
232
+ }
233
+
234
+ // ─────────────────────────────────────────────────────────────────────────────
235
+ // LocalBanner — inline, within page sections
236
+ // ─────────────────────────────────────────────────────────────────────────────
237
+
238
+ const localBannerVariants = cva(
239
+ "flex items-start gap-3 rounded-lg border px-4 py-3 text-sm transition-all",
240
+ {
241
+ variants: {
242
+ variant: {
243
+ info: "border-blue-500/30 bg-blue-500/5 text-blue-800 dark:bg-blue-500/10 dark:text-blue-200 dark:border-blue-500/20",
244
+ warning: "border-amber-500/30 bg-amber-500/5 text-amber-800 dark:bg-amber-500/10 dark:text-amber-200 dark:border-amber-500/20",
245
+ error: "border-red-500/30 bg-red-500/5 text-red-800 dark:bg-red-500/10 dark:text-red-200 dark:border-red-500/20",
246
+ success: "border-emerald-500/30 bg-emerald-500/5 text-emerald-800 dark:bg-emerald-500/10 dark:text-emerald-200 dark:border-emerald-500/20",
247
+ promo: "border-brand/30 bg-brand/5 text-foreground dark:bg-brand/10 dark:border-brand/20",
248
+ },
249
+ },
250
+ defaultVariants: { variant: "info" },
251
+ }
252
+ )
253
+
254
+ export interface LocalBannerProps
255
+ extends React.HTMLAttributes<HTMLDivElement>,
256
+ VariantProps<typeof localBannerVariants> {
257
+ /** Banner title (optional) */
258
+ title?: string
259
+ /** Banner message */
260
+ children: React.ReactNode
261
+ /** Whether the banner can be dismissed */
262
+ dismissible?: boolean
263
+ /** Callback when dismissed */
264
+ onDismiss?: () => void
265
+ /** Optional action — renders as link (href) or button (onClick) */
266
+ action?: {
267
+ label: string
268
+ href?: string
269
+ onClick?: () => void
270
+ }
271
+ /** Optional retry action for error states */
272
+ retry?: {
273
+ label?: string
274
+ onClick: () => void
275
+ }
276
+ /** Override the default icon */
277
+ icon?: string
278
+ }
279
+
280
+ export function LocalBanner({
281
+ children,
282
+ title,
283
+ variant = "info",
284
+ dismissible = false,
285
+ onDismiss,
286
+ action,
287
+ retry,
288
+ icon,
289
+ className,
290
+ ...props
291
+ }: LocalBannerProps) {
292
+ const [dismissed, setDismissed] = React.useState(false)
293
+ const config = VARIANT_CONFIG[variant ?? "info"]
294
+
295
+ function handleDismiss() {
296
+ setDismissed(true)
297
+ onDismiss?.()
298
+ }
299
+
300
+ if (dismissed) return null
301
+
302
+ return (
303
+ <div
304
+ role={config.role}
305
+ aria-live={config.live}
306
+ className={cn(localBannerVariants({ variant }), className)}
307
+ {...props}
308
+ >
309
+ {/* Icon */}
310
+ <i
311
+ className={cn(config.prefix, icon ?? config.icon, "text-[15px] shrink-0 mt-0.5")}
312
+ aria-hidden="true"
313
+ />
314
+
315
+ {/* Content */}
316
+ <div className="flex-1 min-w-0">
317
+ {title && (
318
+ <p className="font-semibold leading-tight mb-0.5">{title}</p>
319
+ )}
320
+ <div className="text-sm leading-relaxed opacity-90">{children}</div>
321
+
322
+ {/* Actions */}
323
+ {(action || retry) && (
324
+ <div className="flex items-center gap-2 mt-2.5">
325
+ {retry && (
326
+ <Button size="xs" variant="outline" onClick={retry.onClick}>
327
+ <i className="fa-light fa-arrow-rotate-right text-xs" aria-hidden="true" />
328
+ {retry.label ?? "Retry"}
329
+ </Button>
330
+ )}
331
+ {action && (
332
+ action.href ? (
333
+ <a href={action.href} className="inline-flex items-center gap-1 text-xs font-semibold underline underline-offset-2 hover:no-underline">
334
+ {action.label} <i className="fa-light fa-arrow-right text-xs" aria-hidden="true" />
335
+ </a>
336
+ ) : (
337
+ <Button size="xs" variant="outline" onClick={action.onClick}>
338
+ {action.label}
339
+ </Button>
340
+ )
341
+ )}
342
+ </div>
343
+ )}
344
+ </div>
345
+
346
+ {/* Dismiss */}
347
+ {dismissible && (
348
+ <Tip label="Dismiss" side="left">
349
+ <button
350
+ type="button"
351
+ aria-label="Dismiss"
352
+ onClick={handleDismiss}
353
+ className={cn(
354
+ "inline-flex items-center justify-center size-5 rounded transition-colors shrink-0",
355
+ "hover:bg-current/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
356
+ )}
357
+ >
358
+ <i className="fa-light fa-xmark text-xs" aria-hidden="true" />
359
+ </button>
360
+ </Tip>
361
+ )}
362
+ </div>
363
+ )
364
+ }
@@ -0,0 +1,120 @@
1
+ import * as React from "react"
2
+ import { Slot } from "radix-ui"
3
+
4
+ import { cn } from "../../lib/utils"
5
+
6
+ function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
7
+ return (
8
+ <nav
9
+ aria-label="breadcrumb"
10
+ data-slot="breadcrumb"
11
+ className={cn(className)}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
18
+ return (
19
+ <ol
20
+ data-slot="breadcrumb-list"
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
31
+ return (
32
+ <li
33
+ data-slot="breadcrumb-item"
34
+ className={cn("inline-flex items-center gap-1", className)}
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ function BreadcrumbLink({
41
+ asChild,
42
+ className,
43
+ ...props
44
+ }: React.ComponentProps<"a"> & {
45
+ asChild?: boolean
46
+ }) {
47
+ const Comp = asChild ? Slot.Root : "a"
48
+
49
+ return (
50
+ <Comp
51
+ data-slot="breadcrumb-link"
52
+ className={cn("transition-colors hover:text-interactive-hover-foreground", className)}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
59
+ return (
60
+ <span
61
+ data-slot="breadcrumb-page"
62
+ role="link"
63
+ aria-disabled="true"
64
+ aria-current="page"
65
+ className={cn("font-normal text-foreground", className)}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function BreadcrumbSeparator({
72
+ children,
73
+ className,
74
+ ...props
75
+ }: React.ComponentProps<"li">) {
76
+ return (
77
+ <li
78
+ data-slot="breadcrumb-separator"
79
+ role="presentation"
80
+ aria-hidden="true"
81
+ className={cn("[&>svg]:size-3.5", className)}
82
+ {...props}
83
+ >
84
+ {children ?? (
85
+ <i className="fa-light fa-chevron-right rtl:rotate-180" aria-hidden="true" />
86
+ )}
87
+ </li>
88
+ )
89
+ }
90
+
91
+ function BreadcrumbEllipsis({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) {
95
+ return (
96
+ <span
97
+ data-slot="breadcrumb-ellipsis"
98
+ role="presentation"
99
+ aria-hidden="true"
100
+ className={cn(
101
+ "flex size-5 items-center justify-center [&>svg]:size-4",
102
+ className
103
+ )}
104
+ {...props}
105
+ >
106
+ <i className="fa-light fa-ellipsis" aria-hidden="true" />
107
+ <span className="sr-only">More</span>
108
+ </span>
109
+ )
110
+ }
111
+
112
+ export {
113
+ Breadcrumb,
114
+ BreadcrumbList,
115
+ BreadcrumbItem,
116
+ BreadcrumbLink,
117
+ BreadcrumbPage,
118
+ BreadcrumbSeparator,
119
+ BreadcrumbEllipsis,
120
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ outline:
14
+ "border-input bg-background hover:bg-interactive-hover hover:text-interactive-hover-foreground aria-expanded:bg-interactive-hover aria-expanded:text-interactive-hover-foreground dark:bg-input/15 dark:hover:bg-input/25",
15
+ secondary:
16
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
17
+ ghost:
18
+ "hover:bg-interactive-hover hover:text-interactive-hover-foreground aria-expanded:bg-interactive-hover aria-expanded:text-interactive-hover-foreground dark:hover:bg-interactive-hover-subtle",
19
+ destructive:
20
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default:
25
+ "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pe-2.5 has-data-[icon=inline-start]:ps-2.5",
26
+ xs: "h-6 gap-1 px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-8 gap-1 px-3 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pe-3.5 has-data-[icon=inline-start]:ps-3.5",
29
+ icon: "size-9",
30
+ "icon-xs":
31
+ "size-6 in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
32
+ "icon-sm":
33
+ "size-8 in-data-[slot=button-group]:rounded-lg",
34
+ "icon-lg": "size-10",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ }
42
+ )
43
+
44
+ const Button = React.forwardRef<
45
+ HTMLButtonElement,
46
+ React.ComponentProps<"button"> &
47
+ VariantProps<typeof buttonVariants> & {
48
+ asChild?: boolean
49
+ }
50
+ >(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => {
51
+ const Comp = asChild ? Slot.Root : "button"
52
+
53
+ return (
54
+ <Comp
55
+ ref={ref}
56
+ data-slot="button"
57
+ data-variant={variant}
58
+ data-size={size}
59
+ className={cn(buttonVariants({ variant, size }), className)}
60
+ {...props}
61
+ />
62
+ )
63
+ })
64
+ Button.displayName = "Button"
65
+
66
+ export { Button, buttonVariants }