azamat-ui-kit-cli 0.2.2 → 0.3.3
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
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
3
|
|
|
3
4
|
import { cn } from "@/lib/utils"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
const dataTableToolbarVariants = cva("flex flex-col", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: "rounded-[var(--radius-2xl)] border border-border/70 bg-card/80 shadow-sm ring-1 ring-foreground/5",
|
|
10
|
+
plain: "border-transparent bg-transparent shadow-none",
|
|
11
|
+
soft: "rounded-[var(--radius-2xl)] border border-transparent bg-muted/45 shadow-none",
|
|
12
|
+
},
|
|
13
|
+
density: {
|
|
14
|
+
compact: "gap-3 p-3",
|
|
15
|
+
default: "gap-4 p-4 md:p-5",
|
|
16
|
+
comfortable: "gap-5 p-5 md:p-6",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "plain",
|
|
21
|
+
density: "default",
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export type DataTableToolbarProps = React.ComponentProps<"div"> &
|
|
26
|
+
VariantProps<typeof dataTableToolbarVariants> & {
|
|
27
|
+
title?: React.ReactNode
|
|
28
|
+
description?: React.ReactNode
|
|
29
|
+
search?: React.ReactNode
|
|
30
|
+
filters?: React.ReactNode
|
|
31
|
+
actions?: React.ReactNode
|
|
32
|
+
selectionActions?: React.ReactNode
|
|
33
|
+
selectedCount?: number
|
|
34
|
+
totalCount?: number
|
|
35
|
+
selectedLabel?: (selectedCount: number, totalCount?: number) => React.ReactNode
|
|
36
|
+
titleClassName?: string
|
|
37
|
+
descriptionClassName?: string
|
|
38
|
+
actionsClassName?: string
|
|
39
|
+
}
|
|
16
40
|
|
|
17
41
|
function DataTableToolbar({
|
|
18
42
|
className,
|
|
43
|
+
variant,
|
|
44
|
+
density,
|
|
19
45
|
title,
|
|
20
46
|
description,
|
|
21
47
|
search,
|
|
@@ -26,44 +52,46 @@ function DataTableToolbar({
|
|
|
26
52
|
totalCount,
|
|
27
53
|
selectedLabel = (selected, total) =>
|
|
28
54
|
total === undefined ? `${selected} selected` : `${selected} of ${total} selected`,
|
|
55
|
+
titleClassName,
|
|
56
|
+
descriptionClassName,
|
|
57
|
+
actionsClassName,
|
|
29
58
|
children,
|
|
30
59
|
...props
|
|
31
60
|
}: DataTableToolbarProps) {
|
|
32
61
|
const hasHeading = Boolean(title || description)
|
|
33
62
|
const hasSelection = selectedCount > 0 && Boolean(selectionActions)
|
|
34
63
|
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
data-slot="data-table-toolbar"
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</span>
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="data-table-toolbar"
|
|
67
|
+
data-density={density ?? "default"}
|
|
68
|
+
className={cn(dataTableToolbarVariants({ variant, density }), className)}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{(hasHeading || actions) && (
|
|
72
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
73
|
+
{hasHeading && (
|
|
74
|
+
<div className="grid gap-1">
|
|
75
|
+
{title && <h2 className={cn("text-lg font-semibold tracking-tight text-foreground", titleClassName)}>{title}</h2>}
|
|
76
|
+
{description && <p className={cn("text-sm leading-6 text-muted-foreground", descriptionClassName)}>{description}</p>}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{actions && <div className={cn("flex shrink-0 flex-wrap items-center gap-2", actionsClassName)}>{actions}</div>}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{(search || filters || hasSelection || children) && (
|
|
85
|
+
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
|
|
86
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
|
|
87
|
+
{search}
|
|
88
|
+
{filters}
|
|
89
|
+
{children}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{hasSelection && (
|
|
93
|
+
<div className="flex shrink-0 items-center gap-2 rounded-full border border-border/75 bg-background/92 px-2.5 py-1.5 text-sm shadow-[0_1px_0_rgba(255,255,255,0.08)] backdrop-blur">
|
|
94
|
+
<span className="text-muted-foreground">{selectedLabel(selectedCount, totalCount)}</span>
|
|
67
95
|
{selectionActions}
|
|
68
96
|
</div>
|
|
69
97
|
)}
|
|
@@ -73,4 +101,4 @@ function DataTableToolbar({
|
|
|
73
101
|
)
|
|
74
102
|
}
|
|
75
103
|
|
|
76
|
-
export { DataTableToolbar }
|
|
104
|
+
export { DataTableToolbar, dataTableToolbarVariants }
|
|
@@ -407,26 +407,29 @@ function DataTable<TData, TValue = unknown>({
|
|
|
407
407
|
data-striped={striped || undefined}
|
|
408
408
|
data-bordered={bordered || undefined}
|
|
409
409
|
className={cn(
|
|
410
|
-
"overflow-auto rounded-[var(--radius-2xl)] border bg-card
|
|
410
|
+
"overflow-auto rounded-[var(--radius-2xl)] border bg-[linear-gradient(180deg,color-mix(in_oklch,var(--card),white_10%),var(--card))] shadow-sm ring-1 ring-foreground/5 backdrop-blur",
|
|
411
411
|
!bordered && "border-border",
|
|
412
412
|
renderMobileCard && "hidden md:block",
|
|
413
413
|
tableWrapperClassName
|
|
414
414
|
)}
|
|
415
415
|
>
|
|
416
416
|
<Table className={cn("text-[0.95rem]", tableClassName)}>
|
|
417
|
-
<TableHeader
|
|
418
|
-
{
|
|
419
|
-
|
|
420
|
-
|
|
417
|
+
<TableHeader
|
|
418
|
+
className={cn(stickyHeader && "sticky top-0 z-10 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_10%),var(--background))] shadow-sm backdrop-blur")}
|
|
419
|
+
>
|
|
420
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
421
|
+
<TableRow key={headerGroup.id}>
|
|
422
|
+
{headerGroup.headers.map((header) => (
|
|
421
423
|
<TableHead
|
|
422
424
|
key={header.id}
|
|
423
425
|
style={{ width: header.getSize() }}
|
|
424
|
-
className={cn(
|
|
425
|
-
densityHeadClassName[density],
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
className={cn(
|
|
427
|
+
densityHeadClassName[density],
|
|
428
|
+
"text-muted-foreground",
|
|
429
|
+
stickyHeader && "bg-[linear-gradient(180deg,color-mix(in_oklch,var(--background),white_10%),var(--background))] backdrop-blur",
|
|
430
|
+
bordered && "border-r last:border-r-0",
|
|
431
|
+
getHeaderCellClassName(header, headerCellClassName)
|
|
432
|
+
)}
|
|
430
433
|
>
|
|
431
434
|
{header.isPlaceholder
|
|
432
435
|
? null
|
|
@@ -452,6 +455,7 @@ function DataTable<TData, TValue = unknown>({
|
|
|
452
455
|
data-disabled={rowDisabled || undefined}
|
|
453
456
|
className={cn(
|
|
454
457
|
onRowClick && !rowDisabled && "cursor-pointer",
|
|
458
|
+
!rowDisabled && "transition-colors",
|
|
455
459
|
striped && rowIndex % 2 === 1 && "bg-muted/20",
|
|
456
460
|
rowDisabled && "pointer-events-none opacity-55",
|
|
457
461
|
getRowClassName(row, rowClassName)
|
|
@@ -12,7 +12,7 @@ export type TableExportOption = {
|
|
|
12
12
|
disabled?: boolean
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export type TableExportMenuProps = Omit<ButtonProps, "onSelect"> & {
|
|
15
|
+
export type TableExportMenuProps = Omit<ButtonProps, "onSelect" | "onClick"> & {
|
|
16
16
|
options?: TableExportOption[]
|
|
17
17
|
label?: React.ReactNode
|
|
18
18
|
menuLabel?: React.ReactNode
|
|
@@ -3,7 +3,7 @@ import { UploadIcon } from "lucide-react"
|
|
|
3
3
|
|
|
4
4
|
import { Button, type ButtonProps } from "@/components/ui/button"
|
|
5
5
|
|
|
6
|
-
export type TableImportButtonProps = Omit<ButtonProps, "onChange"> & {
|
|
6
|
+
export type TableImportButtonProps = Omit<ButtonProps, "onChange" | "onClick"> & {
|
|
7
7
|
accept?: string
|
|
8
8
|
multiple?: boolean
|
|
9
9
|
label?: React.ReactNode
|
|
@@ -42,14 +42,26 @@ function DataState({
|
|
|
42
42
|
}: DataStateProps) {
|
|
43
43
|
const content = defaultContent[status]
|
|
44
44
|
|
|
45
|
-
return (
|
|
46
|
-
<Card
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
return (
|
|
46
|
+
<Card
|
|
47
|
+
data-slot="data-state"
|
|
48
|
+
data-status={status}
|
|
49
|
+
className={cn("min-w-0 border-border/75 bg-card/96 shadow-sm ring-1 ring-foreground/4", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<CardContent className={cn("flex flex-col items-center justify-center text-center", compact ? "p-4" : "min-h-52 p-8")}>
|
|
53
|
+
<div
|
|
54
|
+
className={cn(
|
|
55
|
+
"mb-3 flex items-center justify-center rounded-full border border-border/70 bg-muted/45 text-muted-foreground shadow-[0_1px_0_rgba(255,255,255,0.05)] [&_svg]:size-5",
|
|
56
|
+
compact ? "size-9" : "size-12"
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{icon ?? content.icon}
|
|
60
|
+
</div>
|
|
61
|
+
<div className={cn("font-semibold tracking-tight text-foreground", compact ? "text-sm" : "text-base")}>{title ?? content.title}</div>
|
|
62
|
+
<p className="mt-1 max-w-md text-sm text-muted-foreground">{description ?? content.description}</p>
|
|
63
|
+
{children && <div className="mt-4 w-full">{children}</div>}
|
|
64
|
+
{(actions || onRetry) && (
|
|
53
65
|
<div className="mt-4 flex flex-wrap justify-center gap-2">
|
|
54
66
|
{onRetry && <Button size="sm" variant={status === "error" ? "default" : "outline"} onClick={onRetry}>{retryLabel}</Button>}
|
|
55
67
|
{actions}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
export * from "./description-list"
|
|
2
2
|
export * from "./progress"
|
|
3
|
+
export * from "./progress-circle"
|
|
3
4
|
export * from "./result"
|
|
4
5
|
export * from "./timeline"
|
|
5
|
-
export * from "./metric-grid"
|
|
6
|
-
export * from "./
|
|
6
|
+
export * from "./metric-grid"
|
|
7
|
+
export * from "./metric-card"
|
|
8
|
+
export * from "./activity-feed"
|
|
7
9
|
export * from "./status-legend"
|
|
10
|
+
export * from "./status-dot"
|
|
8
11
|
export * from "./avatar"
|
|
12
|
+
export * from "./user-card"
|
|
9
13
|
export * from "./data-state"
|
|
10
14
|
export * from "./statistic"
|
|
11
15
|
export * from "./list"
|
|
@@ -15,16 +19,16 @@ export * from "./tag-list"
|
|
|
15
19
|
export * from "./tree-view"
|
|
16
20
|
export * from "./keyboard-shortcut"
|
|
17
21
|
export * from "./code-block"
|
|
18
|
-
export * from "./file-card"
|
|
19
|
-
export * from "./property-grid"
|
|
20
|
-
export * from "./entity-card"
|
|
21
|
-
export {
|
|
22
|
-
SmartCard as InfoCard,
|
|
23
|
-
type SmartCardClassNames as InfoCardClassNames,
|
|
24
|
-
type SmartCardDensity as InfoCardDensity,
|
|
25
|
-
type SmartCardOrientation as InfoCardOrientation,
|
|
26
|
-
type SmartCardProps as InfoCardProps,
|
|
27
|
-
type SmartCardRenderContext as InfoCardRenderContext,
|
|
28
|
-
type SmartCardSize as InfoCardSize,
|
|
29
|
-
type SmartCardVariant as InfoCardVariant,
|
|
30
|
-
} from "./smart-card"
|
|
22
|
+
export * from "./file-card"
|
|
23
|
+
export * from "./property-grid"
|
|
24
|
+
export * from "./entity-card"
|
|
25
|
+
export {
|
|
26
|
+
SmartCard as InfoCard,
|
|
27
|
+
type SmartCardClassNames as InfoCardClassNames,
|
|
28
|
+
type SmartCardDensity as InfoCardDensity,
|
|
29
|
+
type SmartCardOrientation as InfoCardOrientation,
|
|
30
|
+
type SmartCardProps as InfoCardProps,
|
|
31
|
+
type SmartCardRenderContext as InfoCardRenderContext,
|
|
32
|
+
type SmartCardSize as InfoCardSize,
|
|
33
|
+
type SmartCardVariant as InfoCardVariant,
|
|
34
|
+
} from "./smart-card"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@/components/ui/badge"
|
|
4
|
+
import { Card } from "@/components/ui/card"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
export type MetricCardProps = React.ComponentProps<typeof Card> & {
|
|
8
|
+
title: React.ReactNode
|
|
9
|
+
value: React.ReactNode
|
|
10
|
+
description?: React.ReactNode
|
|
11
|
+
trend?: React.ReactNode
|
|
12
|
+
icon?: React.ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function MetricCard({ title, value, description, trend, icon, className, ...props }: MetricCardProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Card data-slot="metric-card" className={cn("p-5", className)} {...props}>
|
|
18
|
+
<div className="flex items-start justify-between gap-4">
|
|
19
|
+
<div className="min-w-0 space-y-1">
|
|
20
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
21
|
+
<div className="text-3xl font-semibold tracking-[-0.04em] text-foreground">{value}</div>
|
|
22
|
+
</div>
|
|
23
|
+
{icon ? <div className="inline-flex size-10 shrink-0 items-center justify-center rounded-2xl bg-muted text-muted-foreground [&_svg]:size-5">{icon}</div> : null}
|
|
24
|
+
</div>
|
|
25
|
+
{(description || trend) && (
|
|
26
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
27
|
+
{trend ? <Badge variant="secondary">{trend}</Badge> : null}
|
|
28
|
+
{description ? <span className="text-sm text-muted-foreground">{description}</span> : null}
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
</Card>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { MetricCard }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
export type ProgressCircleProps = React.ComponentProps<"div"> & {
|
|
6
|
+
value?: number
|
|
7
|
+
max?: number
|
|
8
|
+
label?: React.ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function ProgressCircle({ value = 0, max = 100, label, className, ...props }: ProgressCircleProps) {
|
|
12
|
+
const percent = max > 0 ? Math.max(0, Math.min(100, Math.round((value / max) * 100))) : 0
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div data-slot="progress-circle" className={cn("inline-flex items-center gap-3", className)} {...props}>
|
|
16
|
+
<span className="inline-flex size-14 items-center justify-center rounded-full border-4 border-muted bg-background text-xs font-semibold text-foreground">
|
|
17
|
+
{percent}%
|
|
18
|
+
</span>
|
|
19
|
+
{label ? <span className="text-sm text-muted-foreground">{label}</span> : null}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { ProgressCircle }
|
|
@@ -61,12 +61,12 @@ export type SmartCardProps = Omit<React.ComponentProps<typeof Card>, "title" | "
|
|
|
61
61
|
renderFooter?: (ctx: SmartCardRenderContext) => React.ReactNode
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const variantClassName: Record<SmartCardVariant, string> = {
|
|
65
|
-
default: "bg-card",
|
|
66
|
-
outline: "border bg-card",
|
|
67
|
-
elevated: "border bg-card shadow-
|
|
68
|
-
ghost: "border-transparent bg-transparent shadow-none",
|
|
69
|
-
}
|
|
64
|
+
const variantClassName: Record<SmartCardVariant, string> = {
|
|
65
|
+
default: "border border-border/70 bg-card/96 shadow-sm ring-1 ring-foreground/4",
|
|
66
|
+
outline: "border border-border/75 bg-card/96 shadow-sm ring-1 ring-foreground/4",
|
|
67
|
+
elevated: "border border-border/75 bg-card/98 shadow-[0_24px_80px_rgba(15,23,42,0.12)] ring-1 ring-foreground/4",
|
|
68
|
+
ghost: "border-transparent bg-transparent shadow-none",
|
|
69
|
+
}
|
|
70
70
|
|
|
71
71
|
const densityClassName: Record<SmartCardDensity, string> = {
|
|
72
72
|
compact: "p-3",
|
|
@@ -119,13 +119,13 @@ function SmartCard({
|
|
|
119
119
|
data-selected={selected || undefined}
|
|
120
120
|
data-disabled={disabled || undefined}
|
|
121
121
|
data-loading={loading || undefined}
|
|
122
|
-
className={cn(
|
|
123
|
-
"overflow-hidden transition-colors data-[selected=true]:border-primary data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-55",
|
|
124
|
-
variantClassName[variant],
|
|
125
|
-
clickable && "cursor-pointer hover:bg-muted/35",
|
|
126
|
-
orientation === "horizontal" && "flex",
|
|
127
|
-
className,
|
|
128
|
-
classNames?.root
|
|
122
|
+
className={cn(
|
|
123
|
+
"overflow-hidden transition-colors data-[selected=true]:border-primary data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-55",
|
|
124
|
+
variantClassName[variant],
|
|
125
|
+
clickable && "cursor-pointer hover:bg-muted/35",
|
|
126
|
+
orientation === "horizontal" && "flex",
|
|
127
|
+
className,
|
|
128
|
+
classNames?.root
|
|
129
129
|
)}
|
|
130
130
|
onClick={disabled ? undefined : onClick}
|
|
131
131
|
{...props}
|
|
@@ -136,20 +136,42 @@ function SmartCard({
|
|
|
136
136
|
<SkeletonText rows={3} />
|
|
137
137
|
</div>
|
|
138
138
|
) : (
|
|
139
|
-
<>
|
|
140
|
-
{media &&
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
<>
|
|
140
|
+
{media &&
|
|
141
|
+
(renderMedia?.(ctx) ?? (
|
|
142
|
+
<div
|
|
143
|
+
data-slot="smart-card-media"
|
|
144
|
+
className={cn(
|
|
145
|
+
"bg-muted/50",
|
|
146
|
+
orientation === "horizontal" ? "w-40 shrink-0 border-r border-border/70" : "aspect-video border-b border-border/70",
|
|
147
|
+
classNames?.media
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
{media}
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
<div data-slot="smart-card-body" className={cn("grid min-w-0 flex-1 gap-3", densityClassName[density], classNames?.body)}>
|
|
154
|
+
{renderHeader?.(ctx) ?? (
|
|
155
|
+
<div data-slot="smart-card-header" className={cn("flex items-start justify-between gap-3", classNames?.header)}>
|
|
156
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
157
|
+
{icon && (
|
|
158
|
+
<div
|
|
159
|
+
data-slot="smart-card-icon"
|
|
160
|
+
className={cn(
|
|
161
|
+
"flex size-9 shrink-0 items-center justify-center rounded-[min(var(--radius-xl),16px)] border border-border/70 bg-muted/45 text-muted-foreground shadow-[0_1px_0_rgba(255,255,255,0.05)]",
|
|
162
|
+
classNames?.icon
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
165
|
+
{icon}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
<div className="grid min-w-0 gap-1">
|
|
169
|
+
{eyebrow && <div data-slot="smart-card-eyebrow" className={cn("text-[11px] font-semibold uppercase tracking-[0.22em] text-muted-foreground", classNames?.eyebrow)}>{eyebrow}</div>}
|
|
170
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
171
|
+
{title && <div data-slot="smart-card-title" className={cn("truncate font-semibold tracking-tight text-foreground", titleClassName[size], classNames?.title)}>{title}</div>}
|
|
172
|
+
{status && <div data-slot="smart-card-status" className={classNames?.status}>{status}</div>}
|
|
173
|
+
</div>
|
|
174
|
+
{description && <div data-slot="smart-card-description" className={cn("line-clamp-2 text-sm text-muted-foreground", classNames?.description)}>{description}</div>}
|
|
153
175
|
</div>
|
|
154
176
|
</div>
|
|
155
177
|
{actions && <div data-slot="smart-card-actions" className={cn("shrink-0", classNames?.actions)} onClick={(event) => event.stopPropagation()}>{actions}</div>}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
export type StatusDotTone = "neutral" | "info" | "success" | "warning" | "danger" | "muted"
|
|
6
|
+
|
|
7
|
+
export type StatusDotProps = React.ComponentProps<"span"> & {
|
|
8
|
+
tone?: StatusDotTone
|
|
9
|
+
pulse?: boolean
|
|
10
|
+
label?: React.ReactNode
|
|
11
|
+
size?: "sm" | "default" | "lg"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const toneClassName: Record<StatusDotTone, string> = {
|
|
15
|
+
neutral: "bg-foreground",
|
|
16
|
+
info: "bg-blue-500",
|
|
17
|
+
success: "bg-emerald-500",
|
|
18
|
+
warning: "bg-amber-500",
|
|
19
|
+
danger: "bg-destructive",
|
|
20
|
+
muted: "bg-muted-foreground",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sizeClassName = {
|
|
24
|
+
sm: "size-1.5",
|
|
25
|
+
default: "size-2",
|
|
26
|
+
lg: "size-2.5",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function StatusDot({ className, tone = "neutral", pulse = false, label, size = "default", ...props }: StatusDotProps) {
|
|
30
|
+
const dot = (
|
|
31
|
+
<span className="relative inline-flex shrink-0 items-center justify-center">
|
|
32
|
+
{pulse ? <span className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-35", toneClassName[tone])} /> : null}
|
|
33
|
+
<span data-slot="status-dot-indicator" className={cn("relative rounded-full", sizeClassName[size], toneClassName[tone])} />
|
|
34
|
+
</span>
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<span data-slot="status-dot" className={cn("inline-flex items-center gap-2 text-sm text-muted-foreground", className)} {...props}>
|
|
39
|
+
{dot}
|
|
40
|
+
{label ? <span data-slot="status-dot-label">{label}</span> : null}
|
|
41
|
+
</span>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { StatusDot }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Avatar } from "@/components/display/avatar"
|
|
4
|
+
import { Card } from "@/components/ui/card"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
export type UserCardProps = React.ComponentProps<typeof Card> & {
|
|
8
|
+
name: React.ReactNode
|
|
9
|
+
description?: React.ReactNode
|
|
10
|
+
src?: string
|
|
11
|
+
alt?: string
|
|
12
|
+
meta?: React.ReactNode
|
|
13
|
+
actions?: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function UserCard({ name, description, src, alt, meta, actions, className, ...props }: UserCardProps) {
|
|
17
|
+
return (
|
|
18
|
+
<Card className={cn("flex items-center gap-4 p-4", className)} {...props}>
|
|
19
|
+
<Avatar src={src} alt={alt} name={typeof name === "string" ? name : undefined} />
|
|
20
|
+
<div className="min-w-0 flex-1">
|
|
21
|
+
<p className="truncate font-medium text-foreground">{name}</p>
|
|
22
|
+
{description ? <p className="truncate text-sm text-muted-foreground">{description}</p> : null}
|
|
23
|
+
{meta ? <div className="mt-2 text-xs text-muted-foreground">{meta}</div> : null}
|
|
24
|
+
</div>
|
|
25
|
+
{actions ? <div className="shrink-0">{actions}</div> : null}
|
|
26
|
+
</Card>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { UserCard }
|
|
@@ -42,17 +42,27 @@ function defaultIcon(tone: AlertTone) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function Alert({ tone = "info", title, description, icon, action, className, children, ...props }: AlertProps) {
|
|
46
|
-
return (
|
|
47
|
-
<div
|
|
48
|
-
data-slot="alert"
|
|
49
|
-
role={tone === "destructive" || tone === "warning" ? "alert" : "status"}
|
|
50
|
-
className={cn(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
function Alert({ tone = "info", title, description, icon, action, className, children, ...props }: AlertProps) {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
data-slot="alert"
|
|
49
|
+
role={tone === "destructive" || tone === "warning" ? "alert" : "status"}
|
|
50
|
+
className={cn(
|
|
51
|
+
"flex gap-3 rounded-[var(--radius-2xl)] border p-4 text-sm shadow-[0_1px_0_rgba(255,255,255,0.05)]",
|
|
52
|
+
alertToneClassName[tone],
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
data-slot="alert-icon"
|
|
59
|
+
className={cn(
|
|
60
|
+
"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full border border-current/10 bg-background/55",
|
|
61
|
+
alertIconClassName[tone]
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{icon ?? defaultIcon(tone)}
|
|
65
|
+
</div>
|
|
56
66
|
<div className="min-w-0 flex-1 space-y-1">
|
|
57
67
|
{title && <div data-slot="alert-title" className="font-medium leading-none">{title}</div>}
|
|
58
68
|
{(description || children) && (
|
|
@@ -27,12 +27,12 @@ function EmptyState({
|
|
|
27
27
|
<div
|
|
28
28
|
data-slot="empty-state"
|
|
29
29
|
className={cn(
|
|
30
|
-
"flex min-h-52 flex-col items-center justify-center gap-4 rounded-[var(--radius-3xl)] border border-dashed border-border/80 bg-muted/25 p-10 text-center shadow-sm",
|
|
30
|
+
"flex min-h-52 flex-col items-center justify-center gap-4 rounded-[var(--radius-3xl)] border border-dashed border-border/80 bg-muted/25 p-10 text-center shadow-sm ring-1 ring-foreground/4",
|
|
31
31
|
className
|
|
32
32
|
)}
|
|
33
33
|
{...props}
|
|
34
34
|
>
|
|
35
|
-
<div className="flex size-12 items-center justify-center rounded-full border border-border/70 bg-background/
|
|
35
|
+
<div className="flex size-12 items-center justify-center rounded-full border border-border/70 bg-background/92 text-muted-foreground shadow-[0_1px_0_rgba(255,255,255,0.08)]">
|
|
36
36
|
{icon ?? <InboxIcon className="size-5" />}
|
|
37
37
|
</div>
|
|
38
38
|
|
|
@@ -20,12 +20,12 @@ function LoadingState({
|
|
|
20
20
|
<div
|
|
21
21
|
data-slot="loading-state"
|
|
22
22
|
className={cn(
|
|
23
|
-
"flex min-h-52 flex-col items-center justify-center gap-4 rounded-[var(--radius-3xl)] border border-border/
|
|
23
|
+
"flex min-h-52 flex-col items-center justify-center gap-4 rounded-[var(--radius-3xl)] border border-border/75 bg-muted/25 p-10 text-center text-muted-foreground shadow-sm ring-1 ring-foreground/4",
|
|
24
24
|
className
|
|
25
25
|
)}
|
|
26
26
|
{...props}
|
|
27
27
|
>
|
|
28
|
-
<div className="flex size-12 items-center justify-center rounded-full border border-border/70 bg-background/
|
|
28
|
+
<div className="flex size-12 items-center justify-center rounded-full border border-border/70 bg-background/92 shadow-[0_1px_0_rgba(255,255,255,0.08)]">
|
|
29
29
|
{icon ?? <Loader2Icon className="size-5 animate-spin" />}
|
|
30
30
|
</div>
|
|
31
31
|
<div className="grid gap-1.5">
|