azamat-ui-kit-cli 0.2.2 → 0.3.4
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 +11 -0
- package/dist/index.cjs +452 -0
- package/package.json +2 -2
- package/vendor/src/components/actions/action-menu.tsx +21 -18
- package/vendor/src/components/calendar/calendar.tsx +153 -102
- package/vendor/src/components/calendar/date-picker.tsx +24 -14
- package/vendor/src/components/calendar/date-range-picker.tsx +137 -58
- package/vendor/src/components/charts/charts.tsx +32 -21
- package/vendor/src/components/command/command-palette.tsx +68 -57
- package/vendor/src/components/data-table/data-table-bulk-actions.tsx +23 -20
- package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +21 -10
- package/vendor/src/components/data-table/data-table-pagination.tsx +6 -6
- package/vendor/src/components/data-table/data-table-toolbar.tsx +72 -44
- package/vendor/src/components/data-table/data-table.tsx +15 -11
- package/vendor/src/components/data-table/table-export-menu.tsx +1 -1
- package/vendor/src/components/data-table/table-import-button.tsx +1 -1
- package/vendor/src/components/display/data-state.tsx +20 -8
- package/vendor/src/components/display/index.ts +19 -15
- package/vendor/src/components/display/metric-card.tsx +35 -0
- package/vendor/src/components/display/progress-circle.tsx +24 -0
- package/vendor/src/components/display/smart-card.tsx +49 -27
- package/vendor/src/components/display/status-dot.tsx +45 -0
- package/vendor/src/components/display/user-card.tsx +30 -0
- package/vendor/src/components/feedback/alert.tsx +21 -11
- package/vendor/src/components/feedback/empty-state.tsx +2 -2
- package/vendor/src/components/feedback/loading-state.tsx +2 -2
- package/vendor/src/components/feedback/page-state.tsx +19 -15
- package/vendor/src/components/feedback/status-badge.tsx +43 -43
- package/vendor/src/components/form/form-app-input.tsx +147 -0
- package/vendor/src/components/form/form-date-input.tsx +16 -19
- package/vendor/src/components/form/form-field-shell.tsx +11 -8
- package/vendor/src/components/form/form-field-utils.ts +76 -0
- package/vendor/src/components/form/form-input.tsx +423 -44
- package/vendor/src/components/form/form-number-input.tsx +16 -15
- package/vendor/src/components/form/form-phone-input.tsx +15 -9
- package/vendor/src/components/form/form-search-input.tsx +16 -19
- package/vendor/src/components/form/form-select.tsx +4 -3
- package/vendor/src/components/form/public.ts +16 -14
- package/vendor/src/components/form/smart-form-shell.tsx +13 -12
- package/vendor/src/components/inputs/app-input.tsx +27 -0
- package/vendor/src/components/inputs/async-select.tsx +113 -84
- package/vendor/src/components/inputs/clearable-input.tsx +81 -61
- package/vendor/src/components/inputs/date-input.tsx +21 -17
- package/vendor/src/components/inputs/date-range-input.tsx +10 -10
- package/vendor/src/components/inputs/index.ts +1 -0
- package/vendor/src/components/inputs/input-decorator.tsx +101 -57
- package/vendor/src/components/inputs/masked-input.tsx +20 -20
- package/vendor/src/components/inputs/money-input.tsx +2 -2
- package/vendor/src/components/inputs/number-input.tsx +29 -19
- package/vendor/src/components/inputs/password-input.tsx +82 -45
- package/vendor/src/components/inputs/phone-input.tsx +24 -2
- package/vendor/src/components/inputs/quantity-input.tsx +2 -2
- package/vendor/src/components/inputs/search-input.tsx +54 -3
- package/vendor/src/components/inputs/simple-select.tsx +110 -22
- package/vendor/src/components/layout/app-shell.tsx +2 -2
- package/vendor/src/components/layout/index.ts +5 -4
- package/vendor/src/components/layout/page-header.tsx +79 -35
- package/vendor/src/components/layout/public.ts +12 -10
- package/vendor/src/components/layout/section-header.tsx +56 -0
- package/vendor/src/components/layout/stack.tsx +106 -0
- package/vendor/src/components/layout/stat-card.tsx +66 -29
- package/vendor/src/components/navigation/index.ts +1 -0
- package/vendor/src/components/navigation/nav-tabs.tsx +60 -0
- package/vendor/src/components/navigation/page-tabs.tsx +41 -26
- package/vendor/src/components/navigation/pagination.tsx +14 -10
- package/vendor/src/components/overlay/alert-dialog.tsx +65 -0
- package/vendor/src/components/overlay/drawer.tsx +71 -0
- package/vendor/src/components/overlay/index.ts +4 -2
- package/vendor/src/components/patterns/data-view.tsx +13 -8
- package/vendor/src/components/ui/badge.tsx +96 -52
- package/vendor/src/components/ui/button.tsx +99 -61
- package/vendor/src/components/ui/card.tsx +84 -25
- package/vendor/src/components/ui/checkbox.tsx +68 -68
- package/vendor/src/components/ui/command.tsx +32 -32
- package/vendor/src/components/ui/dialog.tsx +135 -138
- package/vendor/src/components/ui/dropdown-menu.tsx +21 -21
- package/vendor/src/components/ui/hover-card.tsx +49 -0
- package/vendor/src/components/ui/input-primitive.tsx +24 -0
- package/vendor/src/components/ui/input.tsx +191 -20
- package/vendor/src/components/ui/kbd.tsx +33 -0
- package/vendor/src/components/ui/popover.tsx +11 -11
- package/vendor/src/components/ui/radio-group.tsx +102 -0
- package/vendor/src/components/ui/right-click-menu.tsx +60 -0
- package/vendor/src/components/ui/scroll-box.tsx +27 -0
- package/vendor/src/components/ui/segmented-control.tsx +21 -17
- package/vendor/src/components/ui/select.tsx +187 -189
- package/vendor/src/components/ui/skeleton.tsx +2 -2
- package/vendor/src/components/ui/switch.tsx +60 -60
- package/vendor/src/components/ui/table.tsx +114 -114
- package/vendor/src/components/ui/tabs.tsx +2 -2
- package/vendor/src/components/ui/textarea.tsx +1 -1
- package/vendor/src/components/upload/file-dropzone.tsx +38 -0
- package/vendor/src/components/upload/file-upload.tsx +4 -4
- package/vendor/src/components/upload/image-upload.tsx +22 -19
- package/vendor/src/components/upload/index.ts +2 -0
- package/vendor/src/families/catalog.ts +1 -0
- package/vendor/src/families/docs-groups.ts +10 -1
- package/vendor/src/families/member-metadata.ts +24 -0
- package/vendor/src/families/member-snippets.ts +41 -2
- package/vendor/src/families/migration-map.ts +3 -0
- package/vendor/src/index.ts +23 -18
- package/vendor/templates/styles/globals.css +253 -0
- package/dist/index.js +0 -432
|
@@ -8,11 +8,13 @@ import {
|
|
|
8
8
|
CardHeader,
|
|
9
9
|
CardTitle,
|
|
10
10
|
} from "@/components/ui/card"
|
|
11
|
+
import { Badge } from "@/components/ui/badge"
|
|
11
12
|
import { cn } from "@/lib/utils"
|
|
12
13
|
|
|
13
14
|
export type StatCardTrend = {
|
|
14
15
|
value: React.ReactNode
|
|
15
|
-
tone?: "success" | "warning" | "danger" | "muted" | "default"
|
|
16
|
+
tone?: "success" | "warning" | "danger" | "info" | "muted" | "default"
|
|
17
|
+
label?: React.ReactNode
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export type StatCardProps = React.ComponentProps<typeof Card> & {
|
|
@@ -23,7 +25,13 @@ export type StatCardProps = React.ComponentProps<typeof Card> & {
|
|
|
23
25
|
action?: React.ReactNode
|
|
24
26
|
trend?: StatCardTrend
|
|
25
27
|
footer?: React.ReactNode
|
|
28
|
+
helperText?: React.ReactNode
|
|
29
|
+
loading?: boolean
|
|
30
|
+
valuePrefix?: React.ReactNode
|
|
31
|
+
valueSuffix?: React.ReactNode
|
|
26
32
|
contentClassName?: string
|
|
33
|
+
iconClassName?: string
|
|
34
|
+
valueClassName?: string
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
const trendClassName: Record<NonNullable<StatCardTrend["tone"]>, string> = {
|
|
@@ -31,9 +39,20 @@ const trendClassName: Record<NonNullable<StatCardTrend["tone"]>, string> = {
|
|
|
31
39
|
success: "text-emerald-600 dark:text-emerald-400",
|
|
32
40
|
warning: "text-amber-600 dark:text-amber-400",
|
|
33
41
|
danger: "text-destructive",
|
|
42
|
+
info: "text-blue-600 dark:text-blue-400",
|
|
34
43
|
muted: "text-muted-foreground",
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
function StatSkeleton() {
|
|
47
|
+
return (
|
|
48
|
+
<div className="space-y-3">
|
|
49
|
+
<div className="h-4 w-28 animate-pulse rounded-md bg-muted" />
|
|
50
|
+
<div className="h-8 w-36 animate-pulse rounded-md bg-muted" />
|
|
51
|
+
<div className="h-4 w-44 animate-pulse rounded-md bg-muted" />
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
function StatCard({
|
|
38
57
|
className,
|
|
39
58
|
title,
|
|
@@ -43,43 +62,61 @@ function StatCard({
|
|
|
43
62
|
action,
|
|
44
63
|
trend,
|
|
45
64
|
footer,
|
|
65
|
+
helperText,
|
|
66
|
+
loading = false,
|
|
67
|
+
valuePrefix,
|
|
68
|
+
valueSuffix,
|
|
46
69
|
contentClassName,
|
|
70
|
+
iconClassName,
|
|
71
|
+
valueClassName,
|
|
47
72
|
...props
|
|
48
73
|
}: StatCardProps) {
|
|
49
74
|
return (
|
|
50
75
|
<Card data-slot="stat-card" className={cn("min-w-0", className)} {...props}>
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
{loading ? (
|
|
77
|
+
<CardContent>
|
|
78
|
+
<StatSkeleton />
|
|
79
|
+
</CardContent>
|
|
80
|
+
) : (
|
|
81
|
+
<>
|
|
82
|
+
<CardHeader>
|
|
83
|
+
<div className="flex min-w-0 items-start justify-between gap-3">
|
|
84
|
+
<div className="min-w-0 space-y-1">
|
|
85
|
+
{title && <CardDescription className="truncate">{title}</CardDescription>}
|
|
86
|
+
{value !== undefined && value !== null ? (
|
|
87
|
+
<CardTitle className={cn("flex items-baseline gap-1 truncate text-2xl", valueClassName)}>
|
|
88
|
+
{valuePrefix ? <span className="text-base text-muted-foreground">{valuePrefix}</span> : null}
|
|
89
|
+
<span className="truncate">{value}</span>
|
|
90
|
+
{valueSuffix ? <span className="text-base text-muted-foreground">{valueSuffix}</span> : null}
|
|
91
|
+
</CardTitle>
|
|
92
|
+
) : null}
|
|
93
|
+
</div>
|
|
94
|
+
{(icon || action) && (
|
|
95
|
+
<CardAction>
|
|
96
|
+
{action ?? <div className={cn("flex size-9 items-center justify-center rounded-lg bg-muted text-muted-foreground", iconClassName)}>{icon}</div>}
|
|
97
|
+
</CardAction>
|
|
63
98
|
)}
|
|
64
|
-
</
|
|
65
|
-
|
|
66
|
-
</div>
|
|
67
|
-
</CardHeader>
|
|
99
|
+
</div>
|
|
100
|
+
</CardHeader>
|
|
68
101
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
{(description || trend || helperText || footer) && (
|
|
103
|
+
<CardContent className={cn("space-y-2", contentClassName)}>
|
|
104
|
+
{(description || trend) && (
|
|
105
|
+
<div className="flex flex-wrap items-center gap-2 text-sm">
|
|
106
|
+
{trend && (
|
|
107
|
+
<Badge variant="ghost" tone={trend.tone === "muted" || trend.tone === "default" ? "neutral" : trend.tone} size="sm" className={cn("font-medium", trendClassName[trend.tone ?? "default"])}>
|
|
108
|
+
{trend.value}
|
|
109
|
+
</Badge>
|
|
110
|
+
)}
|
|
111
|
+
{trend?.label ? <span className="text-muted-foreground">{trend.label}</span> : null}
|
|
112
|
+
{description && <span className="text-muted-foreground">{description}</span>}
|
|
113
|
+
</div>
|
|
77
114
|
)}
|
|
78
|
-
{
|
|
79
|
-
|
|
115
|
+
{helperText ? <p className="text-xs leading-5 text-muted-foreground">{helperText}</p> : null}
|
|
116
|
+
{footer}
|
|
117
|
+
</CardContent>
|
|
80
118
|
)}
|
|
81
|
-
|
|
82
|
-
</CardContent>
|
|
119
|
+
</>
|
|
83
120
|
)}
|
|
84
121
|
</Card>
|
|
85
122
|
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
export type NavTabItem = {
|
|
6
|
+
label: React.ReactNode
|
|
7
|
+
value: string
|
|
8
|
+
href?: string
|
|
9
|
+
icon?: React.ReactNode
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type NavTabsProps = React.ComponentProps<"div"> & {
|
|
14
|
+
items: NavTabItem[]
|
|
15
|
+
value?: string
|
|
16
|
+
onValueChange?: (value: string) => void
|
|
17
|
+
size?: "sm" | "default" | "lg"
|
|
18
|
+
fullWidth?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sizeClassName = {
|
|
22
|
+
sm: "h-8 px-3 text-xs",
|
|
23
|
+
default: "h-10 px-4 text-sm",
|
|
24
|
+
lg: "h-11 px-5 text-base",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function NavTabs({ items, value, onValueChange, size = "default", fullWidth = false, className, ...props }: NavTabsProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div data-slot="nav-tabs" className={cn("inline-flex rounded-[var(--radius-xl)] border border-border/80 bg-muted/45 p-1", fullWidth && "w-full", className)} {...props}>
|
|
30
|
+
{items.map((item) => {
|
|
31
|
+
const active = item.value === value
|
|
32
|
+
const content = (
|
|
33
|
+
<>
|
|
34
|
+
{item.icon ? <span className="[&_svg]:size-4">{item.icon}</span> : null}
|
|
35
|
+
<span className="truncate">{item.label}</span>
|
|
36
|
+
</>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const itemClassName = cn(
|
|
40
|
+
"inline-flex items-center justify-center gap-2 rounded-[calc(var(--radius-xl)-4px)] font-medium text-muted-foreground transition hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
|
|
41
|
+
sizeClassName[size],
|
|
42
|
+
fullWidth && "flex-1",
|
|
43
|
+
active && "bg-background text-foreground shadow-sm"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return item.href ? (
|
|
47
|
+
<a key={item.value} href={item.href} aria-current={active ? "page" : undefined} className={itemClassName}>
|
|
48
|
+
{content}
|
|
49
|
+
</a>
|
|
50
|
+
) : (
|
|
51
|
+
<button key={item.value} type="button" disabled={item.disabled} className={itemClassName} onClick={() => onValueChange?.(item.value)}>
|
|
52
|
+
{content}
|
|
53
|
+
</button>
|
|
54
|
+
)
|
|
55
|
+
})}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { NavTabs }
|
|
@@ -28,38 +28,53 @@ function PageTabs<TValue extends string = string>({
|
|
|
28
28
|
size = "default",
|
|
29
29
|
className,
|
|
30
30
|
...props
|
|
31
|
-
}: PageTabsProps<TValue>) {
|
|
32
|
-
const visibleItems = items.filter((item) => !item.hidden)
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
}: PageTabsProps<TValue>) {
|
|
32
|
+
const visibleItems = items.filter((item) => !item.hidden)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
data-slot="page-tabs"
|
|
37
|
+
data-variant={variant}
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex min-w-0 flex-wrap gap-1.5 border-b border-border/70",
|
|
40
|
+
variant === "pills" && "rounded-full border border-border/75 bg-muted/22 p-1 shadow-[0_1px_0_rgba(255,255,255,0.04)]",
|
|
41
|
+
variant === "cards" && "gap-2 border-b-0",
|
|
42
|
+
variant !== "underline" && "border-b-0",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{visibleItems.map((item) => {
|
|
48
|
+
const active = item.value === value
|
|
49
|
+
return (
|
|
39
50
|
<button
|
|
40
51
|
key={item.value}
|
|
41
52
|
type="button"
|
|
42
53
|
disabled={item.disabled}
|
|
43
54
|
data-slot="page-tab"
|
|
44
55
|
data-active={active || undefined}
|
|
45
|
-
className={cn(
|
|
46
|
-
"inline-flex min-w-0 items-center gap-2 rounded-
|
|
47
|
-
size === "sm" ? "h-8" : "h-10",
|
|
48
|
-
variant === "underline" && "rounded-none border-b-2 border-transparent text-muted-foreground hover:text-foreground",
|
|
49
|
-
variant === "underline" && active && "border-primary text-foreground",
|
|
50
|
-
variant === "pills" && "
|
|
51
|
-
variant === "pills" && active && "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground",
|
|
52
|
-
variant === "cards" && "border bg-card text-muted-foreground shadow-sm hover:border-primary/
|
|
53
|
-
variant === "cards" && active && "border-primary/
|
|
54
|
-
)}
|
|
55
|
-
onClick={() => onValueChange?.(item.value, item)}
|
|
56
|
-
>
|
|
57
|
-
{item.icon}
|
|
58
|
-
<span className="truncate">{item.label}</span>
|
|
59
|
-
{item.badge !== undefined &&
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
className={cn(
|
|
57
|
+
"inline-flex min-w-0 items-center gap-2 rounded-full px-3 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4",
|
|
58
|
+
size === "sm" ? "h-8" : "h-10",
|
|
59
|
+
variant === "underline" && "rounded-none border-b-2 border-transparent px-1 text-muted-foreground hover:text-foreground",
|
|
60
|
+
variant === "underline" && active && "border-primary text-foreground",
|
|
61
|
+
variant === "pills" && "text-muted-foreground hover:bg-background/72 hover:text-foreground",
|
|
62
|
+
variant === "pills" && active && "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground",
|
|
63
|
+
variant === "cards" && "rounded-[var(--radius-2xl)] border border-border/75 bg-card/96 text-muted-foreground shadow-sm ring-1 ring-foreground/4 hover:border-primary/28 hover:text-foreground",
|
|
64
|
+
variant === "cards" && active && "border-primary/50 bg-primary/7 text-foreground"
|
|
65
|
+
)}
|
|
66
|
+
onClick={() => onValueChange?.(item.value, item)}
|
|
67
|
+
>
|
|
68
|
+
{item.icon}
|
|
69
|
+
<span className="truncate">{item.label}</span>
|
|
70
|
+
{item.badge !== undefined && (
|
|
71
|
+
<Badge variant={active && variant === "pills" ? "secondary" : "outline"} className="rounded-full">
|
|
72
|
+
{item.badge}
|
|
73
|
+
</Badge>
|
|
74
|
+
)}
|
|
75
|
+
</button>
|
|
76
|
+
)
|
|
77
|
+
})}
|
|
63
78
|
</div>
|
|
64
79
|
)
|
|
65
80
|
}
|
|
@@ -92,7 +92,10 @@ function Pagination({
|
|
|
92
92
|
<nav
|
|
93
93
|
data-slot="pagination"
|
|
94
94
|
aria-label="Pagination"
|
|
95
|
-
className={cn(
|
|
95
|
+
className={cn(
|
|
96
|
+
"flex items-center justify-center gap-1.5 rounded-full border border-border/75 bg-background/92 p-1 shadow-[0_1px_0_rgba(255,255,255,0.08)] backdrop-blur",
|
|
97
|
+
className
|
|
98
|
+
)}
|
|
96
99
|
{...props}
|
|
97
100
|
>
|
|
98
101
|
{showEdges && (
|
|
@@ -135,17 +138,18 @@ function Pagination({
|
|
|
135
138
|
|
|
136
139
|
return (
|
|
137
140
|
<Button
|
|
138
|
-
key={item}
|
|
139
|
-
type="button"
|
|
140
|
-
variant={item === currentPage ? "default" : "outline"}
|
|
141
|
-
size="icon-sm"
|
|
142
|
-
disabled={disabled}
|
|
141
|
+
key={item}
|
|
142
|
+
type="button"
|
|
143
|
+
variant={item === currentPage ? "default" : "outline"}
|
|
144
|
+
size="icon-sm"
|
|
145
|
+
disabled={disabled}
|
|
143
146
|
aria-current={item === currentPage ? "page" : undefined}
|
|
144
147
|
aria-label={labels?.page?.(item) ?? `Page ${item}`}
|
|
145
|
-
onClick={() => goToPage(item)}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
onClick={() => goToPage(item)}
|
|
149
|
+
className={cn(item !== currentPage && "border-border/80 bg-background/94")}
|
|
150
|
+
>
|
|
151
|
+
{item}
|
|
152
|
+
</Button>
|
|
149
153
|
)
|
|
150
154
|
})}
|
|
151
155
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { AlertTriangleIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogClose,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "@/components/ui/dialog"
|
|
14
|
+
|
|
15
|
+
export type AlertDialogProps = Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
|
16
|
+
title?: React.ReactNode
|
|
17
|
+
description?: React.ReactNode
|
|
18
|
+
icon?: React.ReactNode
|
|
19
|
+
cancelLabel?: React.ReactNode
|
|
20
|
+
actionLabel?: React.ReactNode
|
|
21
|
+
actionTone?: "default" | "destructive"
|
|
22
|
+
loading?: boolean
|
|
23
|
+
onAction?: () => void | Promise<void>
|
|
24
|
+
children?: React.ReactNode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AlertDialog({
|
|
28
|
+
title = "Are you sure?",
|
|
29
|
+
description,
|
|
30
|
+
icon = <AlertTriangleIcon />,
|
|
31
|
+
cancelLabel = "Cancel",
|
|
32
|
+
actionLabel = "Continue",
|
|
33
|
+
actionTone = "destructive",
|
|
34
|
+
loading = false,
|
|
35
|
+
onAction,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: AlertDialogProps) {
|
|
39
|
+
return (
|
|
40
|
+
<Dialog {...props}>
|
|
41
|
+
{children}
|
|
42
|
+
<DialogContent>
|
|
43
|
+
<DialogHeader>
|
|
44
|
+
<div className="mb-3 inline-flex size-11 items-center justify-center rounded-2xl border border-destructive/20 bg-destructive/10 text-destructive [&_svg]:size-5">
|
|
45
|
+
{icon}
|
|
46
|
+
</div>
|
|
47
|
+
<DialogTitle>{title}</DialogTitle>
|
|
48
|
+
{description ? <DialogDescription>{description}</DialogDescription> : null}
|
|
49
|
+
</DialogHeader>
|
|
50
|
+
<DialogFooter>
|
|
51
|
+
<DialogClose render={() => (
|
|
52
|
+
<Button type="button" variant="outline" disabled={loading}>
|
|
53
|
+
{cancelLabel}
|
|
54
|
+
</Button>
|
|
55
|
+
)} />
|
|
56
|
+
<Button type="button" variant={actionTone === "destructive" ? "destructive" : "default"} loading={loading} onClick={() => void onAction?.()}>
|
|
57
|
+
{actionLabel}
|
|
58
|
+
</Button>
|
|
59
|
+
</DialogFooter>
|
|
60
|
+
</DialogContent>
|
|
61
|
+
</Dialog>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { AlertDialog }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogClose,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogTrigger,
|
|
12
|
+
} from "@/components/ui/dialog"
|
|
13
|
+
import { cn } from "@/lib/utils"
|
|
14
|
+
|
|
15
|
+
export type DrawerSide = "left" | "right" | "top" | "bottom"
|
|
16
|
+
|
|
17
|
+
export type DrawerProps = Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
|
18
|
+
trigger?: React.ReactNode
|
|
19
|
+
title?: React.ReactNode
|
|
20
|
+
description?: React.ReactNode
|
|
21
|
+
side?: DrawerSide
|
|
22
|
+
footer?: React.ReactNode
|
|
23
|
+
contentClassName?: string
|
|
24
|
+
showCloseButton?: boolean
|
|
25
|
+
children?: React.ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sideClassName: Record<DrawerSide, string> = {
|
|
29
|
+
right: "left-auto right-0 top-0 h-dvh max-h-dvh w-full max-w-md translate-x-0 translate-y-0 rounded-none rounded-l-[var(--radius-3xl)] sm:max-w-lg",
|
|
30
|
+
left: "left-0 top-0 h-dvh max-h-dvh w-full max-w-md translate-x-0 translate-y-0 rounded-none rounded-r-[var(--radius-3xl)] sm:max-w-lg",
|
|
31
|
+
top: "left-0 top-0 h-auto max-h-[85dvh] w-full max-w-none translate-x-0 translate-y-0 rounded-none rounded-b-[var(--radius-3xl)]",
|
|
32
|
+
bottom: "bottom-0 left-0 top-auto h-auto max-h-[85dvh] w-full max-w-none translate-x-0 translate-y-0 rounded-none rounded-t-[var(--radius-3xl)]",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Drawer({
|
|
36
|
+
trigger,
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
side = "right",
|
|
40
|
+
footer,
|
|
41
|
+
contentClassName,
|
|
42
|
+
showCloseButton = true,
|
|
43
|
+
children,
|
|
44
|
+
...props
|
|
45
|
+
}: DrawerProps) {
|
|
46
|
+
return (
|
|
47
|
+
<Dialog {...props}>
|
|
48
|
+
{trigger ? <DialogTrigger render={trigger as React.ReactElement} /> : null}
|
|
49
|
+
<DialogContent showCloseButton={showCloseButton} className={cn("fixed p-0", sideClassName[side], contentClassName)}>
|
|
50
|
+
{(title || description) && (
|
|
51
|
+
<DialogHeader className="border-b border-border/70 p-6">
|
|
52
|
+
{title ? <DialogTitle>{title}</DialogTitle> : null}
|
|
53
|
+
{description ? <DialogDescription>{description}</DialogDescription> : null}
|
|
54
|
+
</DialogHeader>
|
|
55
|
+
)}
|
|
56
|
+
<div data-slot="drawer-body" className="min-h-0 flex-1 overflow-y-auto p-6">
|
|
57
|
+
{children}
|
|
58
|
+
</div>
|
|
59
|
+
{footer ? <div data-slot="drawer-footer" className="border-t border-border/70 p-5">{footer}</div> : null}
|
|
60
|
+
</DialogContent>
|
|
61
|
+
</Dialog>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function DrawerCloseButton({ children = "Close", ...props }: React.ComponentProps<typeof Button>) {
|
|
66
|
+
return (
|
|
67
|
+
<DialogClose render={() => <Button type="button" variant="outline" {...props}>{children}</Button>} />
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { Drawer, DrawerCloseButton }
|
|
@@ -5,13 +5,14 @@ import { cn } from "@/lib/utils"
|
|
|
5
5
|
|
|
6
6
|
export type DataViewState = "idle" | "loading" | "error" | "empty"
|
|
7
7
|
|
|
8
|
-
export type DataViewLabels = {
|
|
9
|
-
loadingTitle?: React.ReactNode
|
|
10
|
-
loadingDescription?: React.ReactNode
|
|
11
|
-
errorTitle?: React.ReactNode
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export type DataViewLabels = {
|
|
9
|
+
loadingTitle?: React.ReactNode
|
|
10
|
+
loadingDescription?: React.ReactNode
|
|
11
|
+
errorTitle?: React.ReactNode
|
|
12
|
+
errorDescription?: React.ReactNode
|
|
13
|
+
emptyTitle?: React.ReactNode
|
|
14
|
+
emptyDescription?: React.ReactNode
|
|
15
|
+
}
|
|
15
16
|
|
|
16
17
|
export type DataViewProps<TItem = unknown> = React.ComponentProps<"div"> & {
|
|
17
18
|
data: TItem[]
|
|
@@ -72,7 +73,11 @@ function DataView<TItem = unknown>({
|
|
|
72
73
|
{selectionBar}
|
|
73
74
|
<div data-slot="data-view-content" className={contentClassName}>
|
|
74
75
|
{state === "loading" ? loadingState ?? <InlineState tone="loading" title={labels?.loadingTitle ?? "Loading"} description={labels?.loadingDescription} /> : null}
|
|
75
|
-
{state === "error"
|
|
76
|
+
{state === "error"
|
|
77
|
+
? errorState ?? (
|
|
78
|
+
<InlineState tone="error" title={labels?.errorTitle ?? "Something went wrong"} description={labels?.errorDescription} />
|
|
79
|
+
)
|
|
80
|
+
: null}
|
|
76
81
|
{state === "empty" ? empty ?? <InlineState tone="empty" title={labels?.emptyTitle ?? "No data"} description={labels?.emptyDescription} /> : null}
|
|
77
82
|
{state === "idle" ? renderContent?.(data, { view, state }) ?? <div className="grid gap-3">{data.map((item, index) => renderItem?.(item, index) ?? <pre key={index} className="rounded-lg border bg-card p-3 text-xs">{JSON.stringify(item, null, 2)}</pre>)}</div> : null}
|
|
78
83
|
</div>
|
|
@@ -1,52 +1,96 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"bg-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
"border-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
3
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const badgeVariants = cva(
|
|
9
|
+
"group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent font-semibold tracking-[0.01em] whitespace-nowrap transition-[background-color,border-color,color,box-shadow,transform] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default:
|
|
14
|
+
"border-primary/20 bg-primary text-primary-foreground shadow-[0_8px_18px_color-mix(in_oklch,var(--primary),transparent_82%)] [a]:hover:bg-primary/88",
|
|
15
|
+
secondary:
|
|
16
|
+
"border-border/70 bg-secondary text-secondary-foreground shadow-[0_1px_0_rgba(255,255,255,0.08)] [a]:hover:bg-secondary/84",
|
|
17
|
+
destructive:
|
|
18
|
+
"border-destructive/18 bg-destructive/12 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/18",
|
|
19
|
+
outline:
|
|
20
|
+
"border-border/85 bg-background/88 text-foreground shadow-[0_1px_0_rgba(255,255,255,0.08)] [a]:hover:bg-muted [a]:hover:text-foreground",
|
|
21
|
+
ghost:
|
|
22
|
+
"bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:hover:bg-muted/50",
|
|
23
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
24
|
+
},
|
|
25
|
+
tone: {
|
|
26
|
+
neutral: "",
|
|
27
|
+
info: "border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-300",
|
|
28
|
+
success: "border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
29
|
+
warning: "border-amber-500/24 bg-amber-500/12 text-amber-700 dark:text-amber-300",
|
|
30
|
+
danger: "border-destructive/20 bg-destructive/12 text-destructive dark:bg-destructive/20",
|
|
31
|
+
},
|
|
32
|
+
size: {
|
|
33
|
+
sm: "min-h-5 px-2 py-0.5 text-[0.65rem]",
|
|
34
|
+
default: "min-h-6 px-2.5 py-1 text-[0.7rem]",
|
|
35
|
+
lg: "min-h-7 px-3 py-1 text-xs",
|
|
36
|
+
},
|
|
37
|
+
dot: {
|
|
38
|
+
true: "pl-2",
|
|
39
|
+
false: "",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
variant: "default",
|
|
44
|
+
tone: "neutral",
|
|
45
|
+
size: "default",
|
|
46
|
+
dot: false,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
type BadgeProps = useRender.ComponentProps<"span"> &
|
|
52
|
+
VariantProps<typeof badgeVariants> & {
|
|
53
|
+
leftIcon?: React.ReactNode
|
|
54
|
+
rightIcon?: React.ReactNode
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Badge({
|
|
58
|
+
className,
|
|
59
|
+
variant = "default",
|
|
60
|
+
tone = "neutral",
|
|
61
|
+
size = "default",
|
|
62
|
+
dot = false,
|
|
63
|
+
leftIcon,
|
|
64
|
+
rightIcon,
|
|
65
|
+
children,
|
|
66
|
+
render,
|
|
67
|
+
...props
|
|
68
|
+
}: BadgeProps) {
|
|
69
|
+
return useRender({
|
|
70
|
+
defaultTagName: "span",
|
|
71
|
+
props: mergeProps<"span">(
|
|
72
|
+
{
|
|
73
|
+
className: cn(badgeVariants({ variant, tone, size, dot }), className),
|
|
74
|
+
children: (
|
|
75
|
+
<>
|
|
76
|
+
{dot ? <span data-slot="badge-dot" className="size-1.5 rounded-full bg-current opacity-75" /> : null}
|
|
77
|
+
{leftIcon ? <span data-icon="inline-start" data-slot="badge-icon" className="inline-flex shrink-0 items-center">{leftIcon}</span> : null}
|
|
78
|
+
{children ? <span data-slot="badge-label">{children}</span> : null}
|
|
79
|
+
{rightIcon ? <span data-icon="inline-end" data-slot="badge-icon" className="inline-flex shrink-0 items-center">{rightIcon}</span> : null}
|
|
80
|
+
</>
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
props
|
|
84
|
+
),
|
|
85
|
+
render,
|
|
86
|
+
state: {
|
|
87
|
+
slot: "badge",
|
|
88
|
+
variant,
|
|
89
|
+
tone,
|
|
90
|
+
size,
|
|
91
|
+
dot,
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { Badge, badgeVariants, type BadgeProps }
|