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.
- package/README.md +8 -0
- package/dist/index.js +432 -0
- package/package.json +34 -0
- package/vendor/package.json +4 -0
- package/vendor/src/components/actions/action-bar.tsx +35 -0
- package/vendor/src/components/actions/action-menu.tsx +120 -0
- package/vendor/src/components/actions/button-group.tsx +47 -0
- package/vendor/src/components/actions/copy-button.tsx +91 -0
- package/vendor/src/components/actions/copy-field.tsx +31 -0
- package/vendor/src/components/actions/floating-action-button.tsx +33 -0
- package/vendor/src/components/actions/index.ts +7 -0
- package/vendor/src/components/actions/public.ts +5 -0
- package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
- package/vendor/src/components/calendar/calendar.tsx +328 -0
- package/vendor/src/components/calendar/date-picker.tsx +78 -0
- package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
- package/vendor/src/components/calendar/date-utils.ts +89 -0
- package/vendor/src/components/calendar/index.ts +4 -0
- package/vendor/src/components/charts/charts.tsx +275 -0
- package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
- package/vendor/src/components/charts/index.ts +4 -0
- package/vendor/src/components/charts/kpi.tsx +68 -0
- package/vendor/src/components/charts/progress-ring.tsx +45 -0
- package/vendor/src/components/charts/public.ts +1 -0
- package/vendor/src/components/command/command-palette.tsx +375 -0
- package/vendor/src/components/command/index.ts +1 -0
- package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
- package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
- package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
- package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
- package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
- package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
- package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
- package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
- package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
- package/vendor/src/components/data-table/data-table.tsx +507 -0
- package/vendor/src/components/data-table/index.ts +12 -0
- package/vendor/src/components/data-table/public.ts +10 -0
- package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
- package/vendor/src/components/data-table/table-import-button.tsx +43 -0
- package/vendor/src/components/display/activity-feed.tsx +97 -0
- package/vendor/src/components/display/avatar.tsx +131 -0
- package/vendor/src/components/display/code-block.tsx +33 -0
- package/vendor/src/components/display/data-state.tsx +63 -0
- package/vendor/src/components/display/description-list.tsx +119 -0
- package/vendor/src/components/display/descriptions.tsx +83 -0
- package/vendor/src/components/display/entity-card.tsx +53 -0
- package/vendor/src/components/display/file-card.tsx +54 -0
- package/vendor/src/components/display/index.ts +30 -0
- package/vendor/src/components/display/kanban.tsx +104 -0
- package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
- package/vendor/src/components/display/list.tsx +100 -0
- package/vendor/src/components/display/metric-grid.tsx +86 -0
- package/vendor/src/components/display/progress.tsx +162 -0
- package/vendor/src/components/display/property-grid.tsx +54 -0
- package/vendor/src/components/display/result.tsx +90 -0
- package/vendor/src/components/display/smart-card.tsx +168 -0
- package/vendor/src/components/display/statistic.tsx +107 -0
- package/vendor/src/components/display/status-legend.tsx +108 -0
- package/vendor/src/components/display/tag-list.tsx +52 -0
- package/vendor/src/components/display/timeline.tsx +132 -0
- package/vendor/src/components/display/tree-view.tsx +116 -0
- package/vendor/src/components/feedback/alert.tsx +69 -0
- package/vendor/src/components/feedback/empty-state.tsx +56 -0
- package/vendor/src/components/feedback/index.ts +5 -0
- package/vendor/src/components/feedback/loading-state.tsx +39 -0
- package/vendor/src/components/feedback/page-state.tsx +69 -0
- package/vendor/src/components/feedback/status-badge.tsx +62 -0
- package/vendor/src/components/filters/filter-bar.tsx +89 -0
- package/vendor/src/components/filters/filter-chips.tsx +69 -0
- package/vendor/src/components/filters/index.ts +2 -0
- package/vendor/src/components/form/form-actions.tsx +53 -0
- package/vendor/src/components/form/form-async-select.tsx +26 -0
- package/vendor/src/components/form/form-date-input.tsx +19 -0
- package/vendor/src/components/form/form-date-picker.tsx +54 -0
- package/vendor/src/components/form/form-date-range-input.tsx +79 -0
- package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
- package/vendor/src/components/form/form-field-shell.tsx +191 -0
- package/vendor/src/components/form/form-input.tsx +480 -0
- package/vendor/src/components/form/form-number-input.tsx +19 -0
- package/vendor/src/components/form/form-password-input.tsx +19 -0
- package/vendor/src/components/form/form-phone-input.tsx +22 -0
- package/vendor/src/components/form/form-search-input.tsx +19 -0
- package/vendor/src/components/form/form-section.tsx +29 -0
- package/vendor/src/components/form/form-select.tsx +194 -0
- package/vendor/src/components/form/form-switch.tsx +145 -0
- package/vendor/src/components/form/form-textarea.tsx +103 -0
- package/vendor/src/components/form/index.ts +17 -0
- package/vendor/src/components/form/public.ts +14 -0
- package/vendor/src/components/form/smart-form-shell.tsx +59 -0
- package/vendor/src/components/inputs/async-select.tsx +1143 -0
- package/vendor/src/components/inputs/clearable-input.tsx +78 -0
- package/vendor/src/components/inputs/color-input.tsx +47 -0
- package/vendor/src/components/inputs/combobox.tsx +89 -0
- package/vendor/src/components/inputs/date-input.tsx +32 -0
- package/vendor/src/components/inputs/date-range-input.tsx +67 -0
- package/vendor/src/components/inputs/index.ts +19 -0
- package/vendor/src/components/inputs/input-chrome.tsx +37 -0
- package/vendor/src/components/inputs/input-decorator.tsx +64 -0
- package/vendor/src/components/inputs/input-value.ts +42 -0
- package/vendor/src/components/inputs/masked-input.tsx +51 -0
- package/vendor/src/components/inputs/money-input.tsx +73 -0
- package/vendor/src/components/inputs/number-input.tsx +87 -0
- package/vendor/src/components/inputs/numeric-value.ts +39 -0
- package/vendor/src/components/inputs/otp-input.tsx +102 -0
- package/vendor/src/components/inputs/password-input.tsx +85 -0
- package/vendor/src/components/inputs/phone-input.tsx +46 -0
- package/vendor/src/components/inputs/quantity-input.tsx +116 -0
- package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
- package/vendor/src/components/inputs/rating.tsx +98 -0
- package/vendor/src/components/inputs/search-input.tsx +26 -0
- package/vendor/src/components/inputs/simple-select.tsx +72 -0
- package/vendor/src/components/inputs/slider.tsx +149 -0
- package/vendor/src/components/inputs/tag-input.tsx +104 -0
- package/vendor/src/components/layout/app-header.tsx +46 -0
- package/vendor/src/components/layout/app-shell.tsx +243 -0
- package/vendor/src/components/layout/app-sidebar.tsx +179 -0
- package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
- package/vendor/src/components/layout/index.ts +11 -0
- package/vendor/src/components/layout/page-container.tsx +30 -0
- package/vendor/src/components/layout/page-header.tsx +60 -0
- package/vendor/src/components/layout/public.ts +10 -0
- package/vendor/src/components/layout/section.tsx +76 -0
- package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
- package/vendor/src/components/layout/stat-card.tsx +88 -0
- package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
- package/vendor/src/components/layout/workspace-shell.tsx +50 -0
- package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
- package/vendor/src/components/navigation/index.ts +4 -0
- package/vendor/src/components/navigation/page-tabs.tsx +67 -0
- package/vendor/src/components/navigation/pagination.tsx +179 -0
- package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
- package/vendor/src/components/notifications/index.ts +1 -0
- package/vendor/src/components/notifications/toast.tsx +259 -0
- package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
- package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
- package/vendor/src/components/overlay/index.ts +4 -0
- package/vendor/src/components/overlay/modal-shell.tsx +93 -0
- package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
- package/vendor/src/components/patterns/action-system.tsx +116 -0
- package/vendor/src/components/patterns/crud-system.tsx +53 -0
- package/vendor/src/components/patterns/data-view.tsx +84 -0
- package/vendor/src/components/patterns/entity-details.tsx +66 -0
- package/vendor/src/components/patterns/filter-builder.tsx +113 -0
- package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
- package/vendor/src/components/patterns/form-builder.tsx +334 -0
- package/vendor/src/components/patterns/index.ts +12 -0
- package/vendor/src/components/patterns/public.ts +4 -0
- package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
- package/vendor/src/components/patterns/resource-page.tsx +159 -0
- package/vendor/src/components/patterns/resource-system.tsx +61 -0
- package/vendor/src/components/patterns/settings-section.tsx +46 -0
- package/vendor/src/components/patterns/status-system.tsx +89 -0
- package/vendor/src/components/theme-provider.tsx +51 -0
- package/vendor/src/components/ui/badge.tsx +52 -0
- package/vendor/src/components/ui/button.tsx +61 -0
- package/vendor/src/components/ui/card.tsx +103 -0
- package/vendor/src/components/ui/checkbox.tsx +82 -0
- package/vendor/src/components/ui/collapse.tsx +126 -0
- package/vendor/src/components/ui/command.tsx +194 -0
- package/vendor/src/components/ui/dialog.tsx +160 -0
- package/vendor/src/components/ui/divider.tsx +46 -0
- package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
- package/vendor/src/components/ui/input-group.tsx +158 -0
- package/vendor/src/components/ui/input.tsx +20 -0
- package/vendor/src/components/ui/popover.tsx +90 -0
- package/vendor/src/components/ui/segmented-control.tsx +78 -0
- package/vendor/src/components/ui/select.tsx +201 -0
- package/vendor/src/components/ui/skeleton.tsx +75 -0
- package/vendor/src/components/ui/spinner.tsx +50 -0
- package/vendor/src/components/ui/switch.tsx +71 -0
- package/vendor/src/components/ui/table.tsx +114 -0
- package/vendor/src/components/ui/tabs.tsx +55 -0
- package/vendor/src/components/ui/textarea.tsx +18 -0
- package/vendor/src/components/ui/tooltip.tsx +38 -0
- package/vendor/src/components/upload/file-upload.tsx +483 -0
- package/vendor/src/components/upload/image-upload.tsx +118 -0
- package/vendor/src/components/upload/index.ts +2 -0
- package/vendor/src/components/wizard/index.ts +2 -0
- package/vendor/src/components/wizard/stepper.tsx +53 -0
- package/vendor/src/components/wizard/wizard.tsx +60 -0
- package/vendor/src/families/card-family.ts +28 -0
- package/vendor/src/families/catalog.ts +96 -0
- package/vendor/src/families/data-table-family.ts +31 -0
- package/vendor/src/families/docs-adoption.ts +103 -0
- package/vendor/src/families/docs-groups.ts +209 -0
- package/vendor/src/families/docs-queries.ts +84 -0
- package/vendor/src/families/docs-routing.ts +89 -0
- package/vendor/src/families/form-family.ts +45 -0
- package/vendor/src/families/index.ts +17 -0
- package/vendor/src/families/input-family.ts +61 -0
- package/vendor/src/families/member-metadata.ts +466 -0
- package/vendor/src/families/member-queries.ts +28 -0
- package/vendor/src/families/member-snippet-queries.ts +54 -0
- package/vendor/src/families/member-snippets.ts +673 -0
- package/vendor/src/families/migration-map.ts +79 -0
- package/vendor/src/families/queries.ts +63 -0
- package/vendor/src/families/select-family.ts +33 -0
- package/vendor/src/families/views.ts +81 -0
- package/vendor/src/hooks/index.ts +6 -0
- package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
- package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
- package/vendor/src/hooks/use-debounce.ts +52 -0
- package/vendor/src/hooks/use-disclosure.ts +38 -0
- package/vendor/src/hooks/use-is-mobile.ts +28 -0
- package/vendor/src/hooks/use-session-storage-state.ts +85 -0
- package/vendor/src/index.ts +38 -0
- package/vendor/src/lib/utils.ts +6 -0
- package/vendor/templates/components/button.tsx +0 -0
- package/vendor/templates/components/data-table.tsx +0 -0
- package/vendor/templates/components/input.tsx +0 -0
- package/vendor/templates/lib/utils.ts +0 -0
- 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,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,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 }
|