azamat-ui-kit-cli 0.2.2

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 (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,69 @@
1
+ import * as React from "react"
2
+ import { AlertCircleIcon, CheckCircle2Icon, InfoIcon, TriangleAlertIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type AlertTone = "info" | "success" | "warning" | "destructive" | "muted"
7
+
8
+ export type AlertProps = React.ComponentProps<"div"> & {
9
+ tone?: AlertTone
10
+ title?: React.ReactNode
11
+ description?: React.ReactNode
12
+ icon?: React.ReactNode
13
+ action?: React.ReactNode
14
+ }
15
+
16
+ const alertToneClassName: Record<AlertTone, string> = {
17
+ info: "border-primary/25 bg-primary/5 text-foreground",
18
+ success: "border-emerald-500/25 bg-emerald-500/10 text-foreground",
19
+ warning: "border-amber-500/30 bg-amber-500/10 text-foreground",
20
+ destructive: "border-destructive/30 bg-destructive/10 text-foreground",
21
+ muted: "border-border bg-muted/50 text-foreground",
22
+ }
23
+
24
+ const alertIconClassName: Record<AlertTone, string> = {
25
+ info: "text-primary",
26
+ success: "text-emerald-600 dark:text-emerald-400",
27
+ warning: "text-amber-600 dark:text-amber-400",
28
+ destructive: "text-destructive",
29
+ muted: "text-muted-foreground",
30
+ }
31
+
32
+ function defaultIcon(tone: AlertTone) {
33
+ switch (tone) {
34
+ case "success":
35
+ return <CheckCircle2Icon className="size-4" />
36
+ case "warning":
37
+ return <TriangleAlertIcon className="size-4" />
38
+ case "destructive":
39
+ return <AlertCircleIcon className="size-4" />
40
+ default:
41
+ return <InfoIcon className="size-4" />
42
+ }
43
+ }
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>
56
+ <div className="min-w-0 flex-1 space-y-1">
57
+ {title && <div data-slot="alert-title" className="font-medium leading-none">{title}</div>}
58
+ {(description || children) && (
59
+ <div data-slot="alert-description" className="text-muted-foreground">
60
+ {description ?? children}
61
+ </div>
62
+ )}
63
+ </div>
64
+ {action && <div data-slot="alert-action" className="shrink-0">{action}</div>}
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export { Alert }
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+ import { InboxIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type EmptyStateProps = React.ComponentProps<"div"> & {
8
+ icon?: React.ReactNode
9
+ title?: React.ReactNode
10
+ description?: React.ReactNode
11
+ action?: React.ReactNode
12
+ actionLabel?: React.ReactNode
13
+ onAction?: () => void
14
+ }
15
+
16
+ function EmptyState({
17
+ className,
18
+ icon,
19
+ title = "No data",
20
+ description,
21
+ action,
22
+ actionLabel,
23
+ onAction,
24
+ ...props
25
+ }: EmptyStateProps) {
26
+ return (
27
+ <div
28
+ data-slot="empty-state"
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",
31
+ className
32
+ )}
33
+ {...props}
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">
36
+ {icon ?? <InboxIcon className="size-5" />}
37
+ </div>
38
+
39
+ <div className="grid gap-1.5">
40
+ {title && <h3 className="text-base font-semibold tracking-tight text-foreground">{title}</h3>}
41
+ {description && (
42
+ <p className="max-w-sm text-sm leading-6 text-muted-foreground">{description}</p>
43
+ )}
44
+ </div>
45
+
46
+ {action ??
47
+ (actionLabel && onAction ? (
48
+ <Button type="button" variant="outline" size="sm" onClick={onAction}>
49
+ {actionLabel}
50
+ </Button>
51
+ ) : null)}
52
+ </div>
53
+ )
54
+ }
55
+
56
+ export { EmptyState }
@@ -0,0 +1,5 @@
1
+ export * from "./empty-state"
2
+ export * from "./loading-state"
3
+ export * from "./status-badge"
4
+ export * from "./alert"
5
+ export * from "./page-state"
@@ -0,0 +1,39 @@
1
+ import * as React from "react"
2
+ import { Loader2Icon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type LoadingStateProps = React.ComponentProps<"div"> & {
7
+ label?: React.ReactNode
8
+ description?: React.ReactNode
9
+ icon?: React.ReactNode
10
+ }
11
+
12
+ function LoadingState({
13
+ className,
14
+ label = "Loading...",
15
+ description,
16
+ icon,
17
+ ...props
18
+ }: LoadingStateProps) {
19
+ return (
20
+ <div
21
+ data-slot="loading-state"
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",
24
+ className
25
+ )}
26
+ {...props}
27
+ >
28
+ <div className="flex size-12 items-center justify-center rounded-full border border-border/70 bg-background/90 shadow-sm">
29
+ {icon ?? <Loader2Icon className="size-5 animate-spin" />}
30
+ </div>
31
+ <div className="grid gap-1.5">
32
+ {label && <div className="text-base font-semibold tracking-tight text-foreground">{label}</div>}
33
+ {description && <p className="max-w-sm text-sm leading-6">{description}</p>}
34
+ </div>
35
+ </div>
36
+ )
37
+ }
38
+
39
+ export { LoadingState }
@@ -0,0 +1,69 @@
1
+ import * as React from "react"
2
+ import { AlertCircleIcon, CheckCircle2Icon, InfoIcon, Loader2Icon, SearchXIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type PageStateTone = "empty" | "loading" | "error" | "success" | "info"
8
+
9
+ export type PageStateProps = Omit<React.ComponentProps<"div">, "title"> & {
10
+ tone?: PageStateTone
11
+ title?: React.ReactNode
12
+ description?: React.ReactNode
13
+ icon?: React.ReactNode
14
+ action?: React.ReactNode
15
+ compact?: boolean
16
+ }
17
+
18
+ function defaultPageStateIcon(tone: PageStateTone) {
19
+ switch (tone) {
20
+ case "loading":
21
+ return <Loader2Icon className="size-8 animate-spin" />
22
+ case "error":
23
+ return <AlertCircleIcon className="size-8" />
24
+ case "success":
25
+ return <CheckCircle2Icon className="size-8" />
26
+ case "info":
27
+ return <InfoIcon className="size-8" />
28
+ default:
29
+ return <SearchXIcon className="size-8" />
30
+ }
31
+ }
32
+
33
+ function PageState({ tone = "empty", title, description, icon, action, compact = false, className, ...props }: PageStateProps) {
34
+ return (
35
+ <div
36
+ data-slot="page-state"
37
+ role={tone === "error" ? "alert" : "status"}
38
+ className={cn("flex flex-col items-center justify-center rounded-lg border bg-card text-center", compact ? "gap-2 p-6" : "min-h-72 gap-4 p-10", className)}
39
+ {...props}
40
+ >
41
+ <div className="flex size-14 items-center justify-center rounded-full bg-muted text-muted-foreground">
42
+ {icon ?? defaultPageStateIcon(tone)}
43
+ </div>
44
+ <div className="grid gap-1">
45
+ {title && <div className="text-base font-semibold text-foreground">{title}</div>}
46
+ {description && <div className="max-w-md text-sm text-muted-foreground">{description}</div>}
47
+ </div>
48
+ {action && <div>{action}</div>}
49
+ </div>
50
+ )
51
+ }
52
+
53
+ export type InlineStateProps = Omit<PageStateProps, "compact"> & {
54
+ retryLabel?: React.ReactNode
55
+ onRetry?: () => void
56
+ }
57
+
58
+ function InlineState({ retryLabel = "Retry", onRetry, action, className, ...props }: InlineStateProps) {
59
+ return (
60
+ <PageState
61
+ compact
62
+ className={cn("min-h-0", className)}
63
+ action={action ?? (onRetry ? <Button type="button" variant="outline" size="sm" onClick={onRetry}>{retryLabel}</Button> : undefined)}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+
69
+ export { InlineState, PageState }
@@ -0,0 +1,62 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge, badgeVariants } from "@/components/ui/badge"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ type BadgeVariant = NonNullable<Parameters<typeof badgeVariants>[0]>["variant"]
7
+
8
+ export type StatusBadgeTone =
9
+ | "default"
10
+ | "success"
11
+ | "warning"
12
+ | "danger"
13
+ | "info"
14
+ | "muted"
15
+ | "outline"
16
+
17
+ export type StatusBadgeProps = React.ComponentProps<typeof Badge> & {
18
+ tone?: StatusBadgeTone
19
+ dot?: boolean
20
+ }
21
+
22
+ const toneClassName: Record<StatusBadgeTone, string> = {
23
+ default: "border-primary/10 bg-primary/8 text-primary shadow-sm",
24
+ success: "border-emerald-500/20 bg-emerald-500/12 text-emerald-700 shadow-sm dark:text-emerald-300",
25
+ warning: "border-amber-500/22 bg-amber-500/12 text-amber-700 shadow-sm dark:text-amber-300",
26
+ danger: "border-destructive/18 bg-destructive/12 text-destructive shadow-sm",
27
+ info: "border-blue-500/20 bg-blue-500/12 text-blue-700 shadow-sm dark:text-blue-300",
28
+ muted: "border-border/70 bg-muted/80 text-muted-foreground",
29
+ outline: "border-border/80 bg-background/90 text-foreground shadow-sm",
30
+ }
31
+
32
+ const toneVariant: Record<StatusBadgeTone, BadgeVariant> = {
33
+ default: "default",
34
+ success: "secondary",
35
+ warning: "secondary",
36
+ danger: "destructive",
37
+ info: "secondary",
38
+ muted: "secondary",
39
+ outline: "outline",
40
+ }
41
+
42
+ function StatusBadge({
43
+ className,
44
+ tone = "default",
45
+ dot = false,
46
+ children,
47
+ ...props
48
+ }: StatusBadgeProps) {
49
+ return (
50
+ <Badge
51
+ data-slot="status-badge"
52
+ variant={toneVariant[tone]}
53
+ className={cn("rounded-full px-2.5 py-1 text-[11px] font-semibold tracking-[0.01em]", toneClassName[tone], className)}
54
+ {...props}
55
+ >
56
+ {dot && <span className="size-1.5 rounded-full bg-current shadow-[0_0_0_3px_color-mix(in_oklch,currentColor,transparent_82%)]" />}
57
+ {children}
58
+ </Badge>
59
+ )
60
+ }
61
+
62
+ export { StatusBadge }
@@ -0,0 +1,89 @@
1
+ import * as React from "react"
2
+ import { SlidersHorizontalIcon, XIcon } from "lucide-react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type FilterBarProps = React.ComponentProps<"div"> & {
8
+ search?: React.ReactNode
9
+ filters?: React.ReactNode
10
+ actions?: React.ReactNode
11
+ activeCount?: number
12
+ activeLabel?: (count: number) => React.ReactNode
13
+ resetLabel?: React.ReactNode
14
+ onReset?: () => void
15
+ collapsible?: boolean
16
+ defaultExpanded?: boolean
17
+ }
18
+
19
+ function FilterBar({
20
+ className,
21
+ search,
22
+ filters,
23
+ actions,
24
+ activeCount = 0,
25
+ activeLabel = (count) => `${count} active`,
26
+ resetLabel = "Reset",
27
+ onReset,
28
+ collapsible = false,
29
+ defaultExpanded = false,
30
+ children,
31
+ ...props
32
+ }: FilterBarProps) {
33
+ const [expanded, setExpanded] = React.useState(defaultExpanded)
34
+ const hasFilters = Boolean(filters || children)
35
+ const shouldShowFilters = !collapsible || expanded
36
+
37
+ return (
38
+ <div
39
+ data-slot="filter-bar"
40
+ className={cn("flex flex-col gap-3 rounded-lg border bg-card p-3", className)}
41
+ {...props}
42
+ >
43
+ <div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
44
+ <div className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
45
+ {search && <div className="min-w-0 flex-1">{search}</div>}
46
+
47
+ {collapsible && hasFilters && (
48
+ <Button
49
+ type="button"
50
+ variant={expanded ? "secondary" : "outline"}
51
+ size="sm"
52
+ onClick={() => setExpanded((value) => !value)}
53
+ >
54
+ <SlidersHorizontalIcon data-icon="inline-start" />
55
+ Filters
56
+ {activeCount > 0 && (
57
+ <span className="ml-1 rounded-full bg-background px-1.5 text-xs">
58
+ {activeCount}
59
+ </span>
60
+ )}
61
+ </Button>
62
+ )}
63
+ </div>
64
+
65
+ <div className="flex shrink-0 flex-wrap items-center gap-2">
66
+ {activeCount > 0 && (
67
+ <span className="text-sm text-muted-foreground">{activeLabel(activeCount)}</span>
68
+ )}
69
+ {activeCount > 0 && onReset && (
70
+ <Button type="button" variant="ghost" size="sm" onClick={onReset}>
71
+ <XIcon data-icon="inline-start" />
72
+ {resetLabel}
73
+ </Button>
74
+ )}
75
+ {actions}
76
+ </div>
77
+ </div>
78
+
79
+ {hasFilters && shouldShowFilters && (
80
+ <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center">
81
+ {filters}
82
+ {children}
83
+ </div>
84
+ )}
85
+ </div>
86
+ )
87
+ }
88
+
89
+ export { FilterBar }
@@ -0,0 +1,69 @@
1
+ import * as React from "react"
2
+ import { XIcon } from "lucide-react"
3
+
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { Button } from "@/components/ui/button"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ export type FilterChip = {
9
+ key: string
10
+ label: React.ReactNode
11
+ value?: React.ReactNode
12
+ tone?: "default" | "success" | "warning" | "danger" | "info" | "muted"
13
+ disabled?: boolean
14
+ hidden?: boolean
15
+ }
16
+
17
+ export type FilterChipsProps = React.ComponentProps<"div"> & {
18
+ chips: FilterChip[]
19
+ clearLabel?: React.ReactNode
20
+ empty?: React.ReactNode
21
+ onRemove?: (key: string) => void
22
+ onClear?: () => void
23
+ }
24
+
25
+ const chipVariant: Record<NonNullable<FilterChip["tone"]>, React.ComponentProps<typeof Badge>["variant"]> = {
26
+ default: "secondary",
27
+ success: "secondary",
28
+ warning: "outline",
29
+ danger: "destructive",
30
+ info: "outline",
31
+ muted: "outline",
32
+ }
33
+
34
+ function FilterChips({
35
+ chips,
36
+ clearLabel = "Clear all",
37
+ empty = "No active filters",
38
+ onRemove,
39
+ onClear,
40
+ className,
41
+ ...props
42
+ }: FilterChipsProps) {
43
+ const visibleChips = chips.filter((chip) => !chip.hidden)
44
+
45
+ if (!visibleChips.length) {
46
+ return <div data-slot="filter-chips-empty" className={cn("text-sm text-muted-foreground", className)} {...props}>{empty}</div>
47
+ }
48
+
49
+ return (
50
+ <div data-slot="filter-chips" className={cn("flex flex-wrap items-center gap-2", className)} {...props}>
51
+ {visibleChips.map((chip) => (
52
+ <Badge key={chip.key} variant={chipVariant[chip.tone ?? "default"]} className={cn("gap-1.5", chip.disabled && "opacity-60")}>
53
+ <span>{chip.label}</span>
54
+ {chip.value !== undefined && <span className="text-muted-foreground">{chip.value}</span>}
55
+ {onRemove && !chip.disabled && (
56
+ <button type="button" className="rounded-full p-0.5 hover:bg-muted" onClick={() => onRemove(chip.key)}>
57
+ <XIcon className="size-3" />
58
+ </button>
59
+ )}
60
+ </Badge>
61
+ ))}
62
+ {onClear && (
63
+ <Button type="button" variant="ghost" size="xs" onClick={onClear}>{clearLabel}</Button>
64
+ )}
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export { FilterChips }
@@ -0,0 +1,2 @@
1
+ export * from "./filter-bar"
2
+ export * from "./filter-chips"
@@ -0,0 +1,53 @@
1
+ import * as React from "react"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type FormActionsProps = React.ComponentProps<"div"> & {
7
+ submitLabel?: React.ReactNode
8
+ cancelLabel?: React.ReactNode
9
+ resetLabel?: React.ReactNode
10
+ loading?: boolean
11
+ disabled?: boolean
12
+ onCancel?: () => void
13
+ onReset?: () => void
14
+ align?: "start" | "end" | "between"
15
+ }
16
+
17
+ function FormActions({
18
+ submitLabel = "Save",
19
+ cancelLabel = "Cancel",
20
+ resetLabel,
21
+ loading = false,
22
+ disabled = false,
23
+ onCancel,
24
+ onReset,
25
+ align = "end",
26
+ className,
27
+ children,
28
+ ...props
29
+ }: FormActionsProps) {
30
+ return (
31
+ <div
32
+ data-slot="form-actions"
33
+ className={cn(
34
+ "flex flex-wrap items-center gap-2 border-t pt-4",
35
+ align === "end" && "justify-end",
36
+ align === "between" && "justify-between",
37
+ align === "start" && "justify-start",
38
+ className
39
+ )}
40
+ {...props}
41
+ >
42
+ {children ?? (
43
+ <>
44
+ {resetLabel && <Button type="button" variant="ghost" disabled={disabled || loading} onClick={onReset}>{resetLabel}</Button>}
45
+ {onCancel && <Button type="button" variant="outline" disabled={disabled || loading} onClick={onCancel}>{cancelLabel}</Button>}
46
+ <Button type="submit" disabled={disabled || loading}>{loading ? "Saving..." : submitLabel}</Button>
47
+ </>
48
+ )}
49
+ </div>
50
+ )
51
+ }
52
+
53
+ export { FormActions }
@@ -0,0 +1,26 @@
1
+ import {
2
+ FormSelect,
3
+ type FormSelectAsyncVariantProps as BaseFormAsyncSelectProps,
4
+ } from "@/components/form/form-select"
5
+ import type { AsyncSelectOption } from "@/components/inputs/async-select"
6
+ import type { FieldPath, FieldValues } from "react-hook-form"
7
+
8
+ export type FormAsyncSelectProps<
9
+ TFieldValues extends FieldValues = FieldValues,
10
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
11
+ TValue extends string = string,
12
+ TData = unknown,
13
+ TOption extends AsyncSelectOption<TValue, TData> = AsyncSelectOption<TValue, TData>,
14
+ > = Omit<BaseFormAsyncSelectProps<TFieldValues, TName, TValue, TData, TOption>, "kind">
15
+
16
+ function FormAsyncSelect<
17
+ TFieldValues extends FieldValues = FieldValues,
18
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
19
+ TValue extends string = string,
20
+ TData = unknown,
21
+ TOption extends AsyncSelectOption<TValue, TData> = AsyncSelectOption<TValue, TData>,
22
+ >(props: FormAsyncSelectProps<TFieldValues, TName, TValue, TData, TOption>) {
23
+ return <FormSelect {...props} kind="async" />
24
+ }
25
+
26
+ export { FormAsyncSelect }
@@ -0,0 +1,19 @@
1
+ import {
2
+ FormInput,
3
+ type FormInputDateVariantProps as BaseFormDateInputProps,
4
+ } from "@/components/form/form-input"
5
+ import type { FieldPath, FieldValues } from "react-hook-form"
6
+
7
+ export type FormDateInputProps<
8
+ TFieldValues extends FieldValues = FieldValues,
9
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
10
+ > = Omit<BaseFormDateInputProps<TFieldValues, TName>, "kind">
11
+
12
+ function FormDateInput<
13
+ TFieldValues extends FieldValues = FieldValues,
14
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
15
+ >(props: FormDateInputProps<TFieldValues, TName>) {
16
+ return <FormInput {...props} kind="date" />
17
+ }
18
+
19
+ export { FormDateInput }
@@ -0,0 +1,54 @@
1
+ import { Controller, type Control, type FieldPath, type FieldValues } from "react-hook-form"
2
+
3
+ import { DatePicker, type DatePickerProps } from "@/components/calendar/date-picker"
4
+ import { FormFieldShell, type FormFieldShellProps } from "@/components/form/form-field-shell"
5
+
6
+ export type FormDatePickerProps<
7
+ TFieldValues extends FieldValues = FieldValues,
8
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
9
+ > = Omit<DatePickerProps, "value" | "onValueChange"> &
10
+ Pick<FormFieldShellProps, "label" | "description" | "required" | "className"> & {
11
+ control: Control<TFieldValues>
12
+ name: TName
13
+ fieldClassName?: string
14
+ emptyValue?: unknown
15
+ onValueChange?: (value: string) => void
16
+ }
17
+
18
+ function FormDatePicker<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
21
+ >({
22
+ control,
23
+ name,
24
+ label,
25
+ description,
26
+ required,
27
+ className,
28
+ fieldClassName,
29
+ emptyValue = "",
30
+ onValueChange,
31
+ ...props
32
+ }: FormDatePickerProps<TFieldValues, TName>) {
33
+ return (
34
+ <Controller
35
+ control={control}
36
+ name={name}
37
+ render={({ field, fieldState }) => (
38
+ <FormFieldShell label={label} description={description} required={required} error={fieldState.error?.message} className={className}>
39
+ <DatePicker
40
+ value={field.value ?? ""}
41
+ triggerClassName={fieldClassName}
42
+ onValueChange={(nextValue) => {
43
+ field.onChange(nextValue || emptyValue)
44
+ onValueChange?.(nextValue)
45
+ }}
46
+ {...props}
47
+ />
48
+ </FormFieldShell>
49
+ )}
50
+ />
51
+ )
52
+ }
53
+
54
+ export { FormDatePicker }