@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.
- package/package.json +72 -0
- package/src/components/ui/avatar.tsx +384 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/banner.tsx +364 -0
- package/src/components/ui/breadcrumb.tsx +120 -0
- package/src/components/ui/button.tsx +66 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +136 -0
- package/src/components/ui/chart.tsx +378 -0
- package/src/components/ui/checkbox.tsx +160 -0
- package/src/components/ui/coach-mark.tsx +361 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +232 -0
- package/src/components/ui/date-picker-field.tsx +186 -0
- package/src/components/ui/dialog.tsx +171 -0
- package/src/components/ui/drag-handle-grip.tsx +10 -0
- package/src/components/ui/drawer.tsx +134 -0
- package/src/components/ui/dropdown-menu.tsx +422 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/form.tsx +137 -0
- package/src/components/ui/input-group.tsx +156 -0
- package/src/components/ui/input-mask.tsx +135 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +55 -0
- package/src/components/ui/label.tsx +25 -0
- package/src/components/ui/payment-card-fields.tsx +65 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/radio-group.tsx +217 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/selection-tile-grid.tsx +246 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +716 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +39 -0
- package/src/components/ui/status-badge.tsx +109 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tip.tsx +21 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle-switch.tsx +31 -0
- package/src/components/ui/toggle.tsx +48 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/components/ui/view-segmented-control.tsx +160 -0
- package/src/globals.css +1795 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/use-app-theme.ts +172 -0
- package/src/hooks/use-coach-mark.ts +342 -0
- package/src/hooks/use-mobile.ts +31 -0
- package/src/hooks/use-mod-key-label.ts +29 -0
- package/src/index.ts +55 -0
- package/src/lib/compose-refs.ts +15 -0
- package/src/lib/date-filter.ts +67 -0
- package/src/lib/utils.ts +6 -0
- package/src/theme/apply-windows-contrast-theme.ts +29 -0
- package/src/theme/windows-contrast-theme.json +147 -0
- package/src/theme.css +1130 -0
- 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 }
|