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.
Files changed (103) hide show
  1. package/README.md +11 -0
  2. package/dist/index.cjs +452 -0
  3. package/package.json +2 -2
  4. package/vendor/src/components/actions/action-menu.tsx +21 -18
  5. package/vendor/src/components/calendar/calendar.tsx +153 -102
  6. package/vendor/src/components/calendar/date-picker.tsx +24 -14
  7. package/vendor/src/components/calendar/date-range-picker.tsx +137 -58
  8. package/vendor/src/components/charts/charts.tsx +32 -21
  9. package/vendor/src/components/command/command-palette.tsx +68 -57
  10. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +23 -20
  11. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +21 -10
  12. package/vendor/src/components/data-table/data-table-pagination.tsx +6 -6
  13. package/vendor/src/components/data-table/data-table-toolbar.tsx +72 -44
  14. package/vendor/src/components/data-table/data-table.tsx +15 -11
  15. package/vendor/src/components/data-table/table-export-menu.tsx +1 -1
  16. package/vendor/src/components/data-table/table-import-button.tsx +1 -1
  17. package/vendor/src/components/display/data-state.tsx +20 -8
  18. package/vendor/src/components/display/index.ts +19 -15
  19. package/vendor/src/components/display/metric-card.tsx +35 -0
  20. package/vendor/src/components/display/progress-circle.tsx +24 -0
  21. package/vendor/src/components/display/smart-card.tsx +49 -27
  22. package/vendor/src/components/display/status-dot.tsx +45 -0
  23. package/vendor/src/components/display/user-card.tsx +30 -0
  24. package/vendor/src/components/feedback/alert.tsx +21 -11
  25. package/vendor/src/components/feedback/empty-state.tsx +2 -2
  26. package/vendor/src/components/feedback/loading-state.tsx +2 -2
  27. package/vendor/src/components/feedback/page-state.tsx +19 -15
  28. package/vendor/src/components/feedback/status-badge.tsx +43 -43
  29. package/vendor/src/components/form/form-app-input.tsx +147 -0
  30. package/vendor/src/components/form/form-date-input.tsx +16 -19
  31. package/vendor/src/components/form/form-field-shell.tsx +11 -8
  32. package/vendor/src/components/form/form-field-utils.ts +76 -0
  33. package/vendor/src/components/form/form-input.tsx +423 -44
  34. package/vendor/src/components/form/form-number-input.tsx +16 -15
  35. package/vendor/src/components/form/form-phone-input.tsx +15 -9
  36. package/vendor/src/components/form/form-search-input.tsx +16 -19
  37. package/vendor/src/components/form/form-select.tsx +4 -3
  38. package/vendor/src/components/form/public.ts +16 -14
  39. package/vendor/src/components/form/smart-form-shell.tsx +13 -12
  40. package/vendor/src/components/inputs/app-input.tsx +27 -0
  41. package/vendor/src/components/inputs/async-select.tsx +113 -84
  42. package/vendor/src/components/inputs/clearable-input.tsx +81 -61
  43. package/vendor/src/components/inputs/date-input.tsx +21 -17
  44. package/vendor/src/components/inputs/date-range-input.tsx +10 -10
  45. package/vendor/src/components/inputs/index.ts +1 -0
  46. package/vendor/src/components/inputs/input-decorator.tsx +101 -57
  47. package/vendor/src/components/inputs/masked-input.tsx +20 -20
  48. package/vendor/src/components/inputs/money-input.tsx +2 -2
  49. package/vendor/src/components/inputs/number-input.tsx +29 -19
  50. package/vendor/src/components/inputs/password-input.tsx +82 -45
  51. package/vendor/src/components/inputs/phone-input.tsx +24 -2
  52. package/vendor/src/components/inputs/quantity-input.tsx +2 -2
  53. package/vendor/src/components/inputs/search-input.tsx +54 -3
  54. package/vendor/src/components/inputs/simple-select.tsx +110 -22
  55. package/vendor/src/components/layout/app-shell.tsx +2 -2
  56. package/vendor/src/components/layout/index.ts +5 -4
  57. package/vendor/src/components/layout/page-header.tsx +79 -35
  58. package/vendor/src/components/layout/public.ts +12 -10
  59. package/vendor/src/components/layout/section-header.tsx +56 -0
  60. package/vendor/src/components/layout/stack.tsx +106 -0
  61. package/vendor/src/components/layout/stat-card.tsx +66 -29
  62. package/vendor/src/components/navigation/index.ts +1 -0
  63. package/vendor/src/components/navigation/nav-tabs.tsx +60 -0
  64. package/vendor/src/components/navigation/page-tabs.tsx +41 -26
  65. package/vendor/src/components/navigation/pagination.tsx +14 -10
  66. package/vendor/src/components/overlay/alert-dialog.tsx +65 -0
  67. package/vendor/src/components/overlay/drawer.tsx +71 -0
  68. package/vendor/src/components/overlay/index.ts +4 -2
  69. package/vendor/src/components/patterns/data-view.tsx +13 -8
  70. package/vendor/src/components/ui/badge.tsx +96 -52
  71. package/vendor/src/components/ui/button.tsx +99 -61
  72. package/vendor/src/components/ui/card.tsx +84 -25
  73. package/vendor/src/components/ui/checkbox.tsx +68 -68
  74. package/vendor/src/components/ui/command.tsx +32 -32
  75. package/vendor/src/components/ui/dialog.tsx +135 -138
  76. package/vendor/src/components/ui/dropdown-menu.tsx +21 -21
  77. package/vendor/src/components/ui/hover-card.tsx +49 -0
  78. package/vendor/src/components/ui/input-primitive.tsx +24 -0
  79. package/vendor/src/components/ui/input.tsx +191 -20
  80. package/vendor/src/components/ui/kbd.tsx +33 -0
  81. package/vendor/src/components/ui/popover.tsx +11 -11
  82. package/vendor/src/components/ui/radio-group.tsx +102 -0
  83. package/vendor/src/components/ui/right-click-menu.tsx +60 -0
  84. package/vendor/src/components/ui/scroll-box.tsx +27 -0
  85. package/vendor/src/components/ui/segmented-control.tsx +21 -17
  86. package/vendor/src/components/ui/select.tsx +187 -189
  87. package/vendor/src/components/ui/skeleton.tsx +2 -2
  88. package/vendor/src/components/ui/switch.tsx +60 -60
  89. package/vendor/src/components/ui/table.tsx +114 -114
  90. package/vendor/src/components/ui/tabs.tsx +2 -2
  91. package/vendor/src/components/ui/textarea.tsx +1 -1
  92. package/vendor/src/components/upload/file-dropzone.tsx +38 -0
  93. package/vendor/src/components/upload/file-upload.tsx +4 -4
  94. package/vendor/src/components/upload/image-upload.tsx +22 -19
  95. package/vendor/src/components/upload/index.ts +2 -0
  96. package/vendor/src/families/catalog.ts +1 -0
  97. package/vendor/src/families/docs-groups.ts +10 -1
  98. package/vendor/src/families/member-metadata.ts +24 -0
  99. package/vendor/src/families/member-snippets.ts +41 -2
  100. package/vendor/src/families/migration-map.ts +3 -0
  101. package/vendor/src/index.ts +23 -18
  102. package/vendor/templates/styles/globals.css +253 -0
  103. 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
- export type DataTableToolbarProps = React.ComponentProps<"div"> & {
6
- title?: React.ReactNode
7
- description?: React.ReactNode
8
- search?: React.ReactNode
9
- filters?: React.ReactNode
10
- actions?: React.ReactNode
11
- selectionActions?: React.ReactNode
12
- selectedCount?: number
13
- totalCount?: number
14
- selectedLabel?: (selectedCount: number, totalCount?: number) => React.ReactNode
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
- className={cn("flex flex-col gap-4 rounded-[var(--radius-2xl)] p-4 md:p-5", className)}
39
- {...props}
40
- >
41
- {(hasHeading || actions) && (
42
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
43
- {hasHeading && (
44
- <div className="grid gap-1">
45
- {title && <h2 className="text-lg font-semibold tracking-tight text-foreground">{title}</h2>}
46
- {description && <p className="text-sm leading-6 text-muted-foreground">{description}</p>}
47
- </div>
48
- )}
49
-
50
- {actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>}
51
- </div>
52
- )}
53
-
54
- {(search || filters || hasSelection || children) && (
55
- <div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
56
- <div className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
57
- {search}
58
- {filters}
59
- {children}
60
- </div>
61
-
62
- {hasSelection && (
63
- <div className="flex shrink-0 items-center gap-2 rounded-full border border-border/70 bg-background/88 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur">
64
- <span className="text-muted-foreground">
65
- {selectedLabel(selectedCount, totalCount)}
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/96 shadow-sm backdrop-blur",
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 className={cn(stickyHeader && "sticky top-0 z-10 bg-background shadow-sm")}>
418
- {table.getHeaderGroups().map((headerGroup) => (
419
- <TableRow key={headerGroup.id}>
420
- {headerGroup.headers.map((header) => (
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
- stickyHeader && "bg-background",
427
- bordered && "border-r last:border-r-0",
428
- getHeaderCellClassName(header, headerCellClassName)
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 data-slot="data-state" data-status={status} className={cn("min-w-0", className)} {...props}>
47
- <CardContent className={cn("flex flex-col items-center justify-center text-center", compact ? "p-4" : "min-h-52 p-8")}>
48
- <div className={cn("mb-3 flex items-center justify-center rounded-full bg-muted text-muted-foreground [&_svg]:size-5", compact ? "size-9" : "size-12")}>{icon ?? content.icon}</div>
49
- <div className={cn("font-semibold", compact ? "text-sm" : "text-base")}>{title ?? content.title}</div>
50
- <p className="mt-1 max-w-md text-sm text-muted-foreground">{description ?? content.description}</p>
51
- {children && <div className="mt-4 w-full">{children}</div>}
52
- {(actions || onRetry) && (
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 "./activity-feed"
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-md",
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 && (renderMedia?.(ctx) ?? <div data-slot="smart-card-media" className={cn("bg-muted", orientation === "horizontal" ? "w-40 shrink-0" : "aspect-video", classNames?.media)}>{media}</div>)}
141
- <div data-slot="smart-card-body" className={cn("grid min-w-0 flex-1 gap-3", densityClassName[density], classNames?.body)}>
142
- {renderHeader?.(ctx) ?? (
143
- <div data-slot="smart-card-header" className={cn("flex items-start justify-between gap-3", classNames?.header)}>
144
- <div className="flex min-w-0 items-start gap-3">
145
- {icon && <div data-slot="smart-card-icon" className={cn("flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground", classNames?.icon)}>{icon}</div>}
146
- <div className="grid min-w-0 gap-1">
147
- {eyebrow && <div data-slot="smart-card-eyebrow" className={cn("text-xs font-medium uppercase tracking-wide text-muted-foreground", classNames?.eyebrow)}>{eyebrow}</div>}
148
- <div className="flex flex-wrap items-center gap-2">
149
- {title && <div data-slot="smart-card-title" className={cn("truncate font-semibold text-foreground", titleClassName[size], classNames?.title)}>{title}</div>}
150
- {status && <div data-slot="smart-card-status" className={classNames?.status}>{status}</div>}
151
- </div>
152
- {description && <div data-slot="smart-card-description" className={cn("line-clamp-2 text-sm text-muted-foreground", classNames?.description)}>{description}</div>}
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("flex gap-3 rounded-lg border p-4 text-sm", alertToneClassName[tone], className)}
51
- {...props}
52
- >
53
- <div data-slot="alert-icon" className={cn("mt-0.5 shrink-0", alertIconClassName[tone])}>
54
- {icon ?? defaultIcon(tone)}
55
- </div>
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/90 text-muted-foreground shadow-sm">
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/70 bg-muted/25 p-10 text-center text-muted-foreground shadow-sm",
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/90 shadow-sm">
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">