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,116 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { MoreHorizontalIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuLabel,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from "@/components/ui/dropdown-menu"
|
|
12
|
+
import { cn } from "@/lib/utils"
|
|
13
|
+
|
|
14
|
+
type MaybeFn<TItem, TResult> = TResult | ((item: TItem) => TResult)
|
|
15
|
+
|
|
16
|
+
function resolveMaybeFn<TItem, TResult>(value: MaybeFn<TItem, TResult> | undefined, item: TItem): TResult | undefined {
|
|
17
|
+
return typeof value === "function" ? (value as (item: TItem) => TResult)(item) : value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ActionSystemAction<TItem = unknown> = {
|
|
21
|
+
key: string
|
|
22
|
+
label: React.ReactNode
|
|
23
|
+
icon?: React.ReactNode
|
|
24
|
+
description?: React.ReactNode
|
|
25
|
+
variant?: React.ComponentProps<typeof Button>["variant"]
|
|
26
|
+
disabled?: MaybeFn<TItem, boolean>
|
|
27
|
+
hidden?: MaybeFn<TItem, boolean>
|
|
28
|
+
confirm?: MaybeFn<TItem, string | { title?: string; description?: string } | undefined>
|
|
29
|
+
onClick?: (item: TItem, action: ActionSystemAction<TItem>) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ActionSystemLabels = {
|
|
33
|
+
more?: React.ReactNode
|
|
34
|
+
menu?: React.ReactNode
|
|
35
|
+
confirmFallback?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ActionSystemProps<TItem = unknown> = React.ComponentProps<"div"> & {
|
|
39
|
+
item: TItem
|
|
40
|
+
actions: ActionSystemAction<TItem>[]
|
|
41
|
+
mode?: "inline" | "menu" | "hybrid"
|
|
42
|
+
maxInline?: number
|
|
43
|
+
size?: React.ComponentProps<typeof Button>["size"]
|
|
44
|
+
labels?: ActionSystemLabels
|
|
45
|
+
onAction?: (item: TItem, action: ActionSystemAction<TItem>) => void
|
|
46
|
+
renderAction?: (action: ActionSystemAction<TItem>, item: TItem, state: { disabled: boolean }) => React.ReactNode
|
|
47
|
+
renderMenuItem?: (action: ActionSystemAction<TItem>, item: TItem, state: { disabled: boolean }) => React.ReactNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ActionSystem<TItem = unknown>({
|
|
51
|
+
item,
|
|
52
|
+
actions,
|
|
53
|
+
mode = "hybrid",
|
|
54
|
+
maxInline = 2,
|
|
55
|
+
size = "sm",
|
|
56
|
+
labels,
|
|
57
|
+
onAction,
|
|
58
|
+
renderAction,
|
|
59
|
+
renderMenuItem,
|
|
60
|
+
className,
|
|
61
|
+
...props
|
|
62
|
+
}: ActionSystemProps<TItem>) {
|
|
63
|
+
const visibleActions = actions.filter((action) => !resolveMaybeFn(action.hidden, item))
|
|
64
|
+
const inlineActions = mode === "menu" ? [] : visibleActions.slice(0, mode === "inline" ? visibleActions.length : maxInline)
|
|
65
|
+
const menuActions = mode === "inline" ? [] : visibleActions.slice(mode === "menu" ? 0 : maxInline)
|
|
66
|
+
|
|
67
|
+
const runAction = (action: ActionSystemAction<TItem>) => {
|
|
68
|
+
const confirm = resolveMaybeFn(action.confirm, item)
|
|
69
|
+
if (confirm) {
|
|
70
|
+
const message = typeof confirm === "string" ? confirm : confirm.description ?? confirm.title ?? labels?.confirmFallback ?? "Are you sure?"
|
|
71
|
+
if (typeof window !== "undefined" && !window.confirm(message)) return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
action.onClick?.(item, action)
|
|
75
|
+
onAction?.(item, action)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div data-slot="action-system" className={cn("inline-flex items-center gap-2", className)} {...props}>
|
|
80
|
+
{inlineActions.map((action) => {
|
|
81
|
+
const disabled = Boolean(resolveMaybeFn(action.disabled, item))
|
|
82
|
+
return renderAction?.(action, item, { disabled }) ?? (
|
|
83
|
+
<Button key={action.key} type="button" size={size} variant={action.variant ?? "outline"} disabled={disabled} onClick={() => runAction(action)}>
|
|
84
|
+
{action.icon}
|
|
85
|
+
{action.label}
|
|
86
|
+
</Button>
|
|
87
|
+
)
|
|
88
|
+
})}
|
|
89
|
+
{menuActions.length > 0 && (
|
|
90
|
+
<DropdownMenu>
|
|
91
|
+
<DropdownMenuTrigger render={<Button type="button" size={size} variant="outline" />}>
|
|
92
|
+
<MoreHorizontalIcon />
|
|
93
|
+
<span className="sr-only">{labels?.more ?? "More actions"}</span>
|
|
94
|
+
</DropdownMenuTrigger>
|
|
95
|
+
<DropdownMenuContent align="end" className="min-w-48">
|
|
96
|
+
{labels?.menu && <DropdownMenuLabel>{labels.menu}</DropdownMenuLabel>}
|
|
97
|
+
{menuActions.map((action) => {
|
|
98
|
+
const disabled = Boolean(resolveMaybeFn(action.disabled, item))
|
|
99
|
+
return renderMenuItem?.(action, item, { disabled }) ?? (
|
|
100
|
+
<DropdownMenuItem key={action.key} disabled={disabled} variant={action.variant === "destructive" ? "destructive" : "default"} onClick={() => runAction(action)}>
|
|
101
|
+
{action.icon}
|
|
102
|
+
<span className="grid gap-0.5">
|
|
103
|
+
<span>{action.label}</span>
|
|
104
|
+
{action.description && <span className="text-xs text-muted-foreground">{action.description}</span>}
|
|
105
|
+
</span>
|
|
106
|
+
</DropdownMenuItem>
|
|
107
|
+
)
|
|
108
|
+
})}
|
|
109
|
+
</DropdownMenuContent>
|
|
110
|
+
</DropdownMenu>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { ActionSystem }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { ActionSystem, type ActionSystemAction } from "@/components/patterns/action-system"
|
|
4
|
+
import { ResourceSystem, type ResourceSystemProps } from "@/components/patterns/resource-system"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
export type CrudSystemLabels = {
|
|
9
|
+
create?: React.ReactNode
|
|
10
|
+
edit?: React.ReactNode
|
|
11
|
+
delete?: React.ReactNode
|
|
12
|
+
view?: React.ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CrudSystemProps<TItem = unknown> = Omit<ResourceSystemProps<TItem>, "actions"> & {
|
|
16
|
+
labels?: CrudSystemLabels
|
|
17
|
+
canCreate?: boolean
|
|
18
|
+
createAction?: React.ReactNode
|
|
19
|
+
extraActions?: React.ReactNode
|
|
20
|
+
itemActions?: ActionSystemAction<TItem>[]
|
|
21
|
+
onCreate?: () => void
|
|
22
|
+
onView?: (item: TItem) => void
|
|
23
|
+
onEdit?: (item: TItem) => void
|
|
24
|
+
onDelete?: (item: TItem) => void
|
|
25
|
+
renderActions?: (args: { createAction?: React.ReactNode; extraActions?: React.ReactNode }) => React.ReactNode
|
|
26
|
+
actionsClassName?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function CrudSystem<TItem = unknown>({ labels, canCreate = true, createAction, extraActions, itemActions, onCreate, onView, onEdit, onDelete, renderActions, actionsClassName, list, ...props }: CrudSystemProps<TItem>) {
|
|
30
|
+
const resolvedItemActions = itemActions ?? [
|
|
31
|
+
onView && { key: "view", label: labels?.view ?? "View", onClick: onView },
|
|
32
|
+
onEdit && { key: "edit", label: labels?.edit ?? "Edit", onClick: onEdit },
|
|
33
|
+
onDelete && { key: "delete", label: labels?.delete ?? "Delete", variant: "destructive" as const, confirm: "Are you sure?", onClick: onDelete },
|
|
34
|
+
].filter(Boolean) as ActionSystemAction<TItem>[]
|
|
35
|
+
|
|
36
|
+
const actions = renderActions?.({ createAction, extraActions }) ?? (
|
|
37
|
+
<div className={cn("flex items-center gap-2", actionsClassName)}>
|
|
38
|
+
{extraActions}
|
|
39
|
+
{canCreate && (createAction ?? <Button type="button" onClick={onCreate}>{labels?.create ?? "Create"}</Button>)}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const renderItem = list.renderItem ?? (resolvedItemActions.length ? (item: TItem) => (
|
|
44
|
+
<div className="flex items-center justify-between gap-3 rounded-lg border bg-card p-3">
|
|
45
|
+
<pre className="min-w-0 flex-1 overflow-hidden text-xs">{JSON.stringify(item, null, 2)}</pre>
|
|
46
|
+
<ActionSystem item={item} actions={resolvedItemActions} />
|
|
47
|
+
</div>
|
|
48
|
+
) : undefined)
|
|
49
|
+
|
|
50
|
+
return <ResourceSystem {...props} actions={actions} list={{ ...list, renderItem }} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { CrudSystem }
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { InlineState } from "@/components/feedback/page-state"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
export type DataViewState = "idle" | "loading" | "error" | "empty"
|
|
7
|
+
|
|
8
|
+
export type DataViewLabels = {
|
|
9
|
+
loadingTitle?: React.ReactNode
|
|
10
|
+
loadingDescription?: React.ReactNode
|
|
11
|
+
errorTitle?: React.ReactNode
|
|
12
|
+
emptyTitle?: React.ReactNode
|
|
13
|
+
emptyDescription?: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DataViewProps<TItem = unknown> = React.ComponentProps<"div"> & {
|
|
17
|
+
data: TItem[]
|
|
18
|
+
loading?: boolean
|
|
19
|
+
error?: unknown
|
|
20
|
+
view?: string
|
|
21
|
+
labels?: DataViewLabels
|
|
22
|
+
toolbar?: React.ReactNode
|
|
23
|
+
filters?: React.ReactNode
|
|
24
|
+
selectionBar?: React.ReactNode
|
|
25
|
+
pagination?: React.ReactNode
|
|
26
|
+
actions?: React.ReactNode
|
|
27
|
+
empty?: React.ReactNode
|
|
28
|
+
loadingState?: React.ReactNode
|
|
29
|
+
errorState?: React.ReactNode
|
|
30
|
+
getState?: (args: { data: TItem[]; loading: boolean; error: unknown }) => DataViewState
|
|
31
|
+
renderContent?: (data: TItem[], meta: { view?: string; state: DataViewState }) => React.ReactNode
|
|
32
|
+
renderItem?: (item: TItem, index: number) => React.ReactNode
|
|
33
|
+
contentClassName?: string
|
|
34
|
+
toolbarClassName?: string
|
|
35
|
+
filtersClassName?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function DataView<TItem = unknown>({
|
|
39
|
+
data,
|
|
40
|
+
loading = false,
|
|
41
|
+
error,
|
|
42
|
+
view,
|
|
43
|
+
labels,
|
|
44
|
+
toolbar,
|
|
45
|
+
filters,
|
|
46
|
+
selectionBar,
|
|
47
|
+
pagination,
|
|
48
|
+
actions,
|
|
49
|
+
empty,
|
|
50
|
+
loadingState,
|
|
51
|
+
errorState,
|
|
52
|
+
getState,
|
|
53
|
+
renderContent,
|
|
54
|
+
renderItem,
|
|
55
|
+
contentClassName,
|
|
56
|
+
toolbarClassName,
|
|
57
|
+
filtersClassName,
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: DataViewProps<TItem>) {
|
|
61
|
+
const state = getState?.({ data, loading, error }) ?? (loading ? "loading" : error ? "error" : data.length === 0 ? "empty" : "idle")
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div data-slot="data-view" data-state={state} className={cn("grid gap-4", className)} {...props}>
|
|
65
|
+
{(toolbar || actions) && (
|
|
66
|
+
<div data-slot="data-view-toolbar" className={cn("flex flex-wrap items-center justify-between gap-3", toolbarClassName)}>
|
|
67
|
+
<div className="min-w-0 flex-1">{toolbar}</div>
|
|
68
|
+
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
{filters && <div data-slot="data-view-filters" className={filtersClassName}>{filters}</div>}
|
|
72
|
+
{selectionBar}
|
|
73
|
+
<div data-slot="data-view-content" className={contentClassName}>
|
|
74
|
+
{state === "loading" ? loadingState ?? <InlineState tone="loading" title={labels?.loadingTitle ?? "Loading"} description={labels?.loadingDescription} /> : null}
|
|
75
|
+
{state === "error" ? errorState ?? <InlineState tone="error" title={labels?.errorTitle ?? "Something went wrong"} /> : null}
|
|
76
|
+
{state === "empty" ? empty ?? <InlineState tone="empty" title={labels?.emptyTitle ?? "No data"} description={labels?.emptyDescription} /> : null}
|
|
77
|
+
{state === "idle" ? renderContent?.(data, { view, state }) ?? <div className="grid gap-3">{data.map((item, index) => renderItem?.(item, index) ?? <pre key={index} className="rounded-lg border bg-card p-3 text-xs">{JSON.stringify(item, null, 2)}</pre>)}</div> : null}
|
|
78
|
+
</div>
|
|
79
|
+
{pagination && <div data-slot="data-view-pagination">{pagination}</div>}
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { DataView }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { PropertyGrid, type PropertyGridItem } from "@/components/display/property-grid"
|
|
4
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
export type EntityDetailsTab = {
|
|
8
|
+
key: string
|
|
9
|
+
label: React.ReactNode
|
|
10
|
+
content: React.ReactNode
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type EntityDetailsSection = {
|
|
15
|
+
key: string
|
|
16
|
+
title?: React.ReactNode
|
|
17
|
+
content: React.ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type EntityDetailsProps = React.ComponentProps<"div"> & {
|
|
21
|
+
title: React.ReactNode
|
|
22
|
+
subtitle?: React.ReactNode
|
|
23
|
+
avatar?: React.ReactNode
|
|
24
|
+
status?: React.ReactNode
|
|
25
|
+
actions?: React.ReactNode
|
|
26
|
+
metadata?: PropertyGridItem[]
|
|
27
|
+
sections?: EntityDetailsSection[]
|
|
28
|
+
tabs?: EntityDetailsTab[]
|
|
29
|
+
tab?: string
|
|
30
|
+
defaultTab?: string
|
|
31
|
+
onTabChange?: (key: string) => void
|
|
32
|
+
renderHeader?: () => React.ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function EntityDetails({ title, subtitle, avatar, status, actions, metadata, sections, tabs, tab, defaultTab, onTabChange, renderHeader, className, children, ...props }: EntityDetailsProps) {
|
|
36
|
+
return (
|
|
37
|
+
<div data-slot="entity-details" className={cn("grid gap-6", className)} {...props}>
|
|
38
|
+
{renderHeader?.() ?? (
|
|
39
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
40
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
41
|
+
{avatar}
|
|
42
|
+
<div className="grid min-w-0 gap-1">
|
|
43
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
44
|
+
<h1 className="truncate text-2xl font-semibold tracking-tight text-foreground">{title}</h1>
|
|
45
|
+
{status}
|
|
46
|
+
</div>
|
|
47
|
+
{subtitle && <div className="text-sm text-muted-foreground">{subtitle}</div>}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
{metadata?.length ? <PropertyGrid items={metadata} columns={3} /> : null}
|
|
54
|
+
{sections?.length ? <div className="grid gap-4">{sections.map((section) => <section key={section.key} className="grid gap-3 rounded-lg border bg-card p-4">{section.title && <h2 className="text-base font-semibold text-foreground">{section.title}</h2>}{section.content}</section>)}</div> : null}
|
|
55
|
+
{tabs?.length ? (
|
|
56
|
+
<Tabs value={tab} defaultValue={defaultTab ?? tabs[0]?.key} onValueChange={onTabChange}>
|
|
57
|
+
<TabsList>{tabs.map((item) => <TabsTrigger key={item.key} value={item.key} disabled={item.disabled}>{item.label}</TabsTrigger>)}</TabsList>
|
|
58
|
+
{tabs.map((item) => <TabsContent key={item.key} value={item.key}>{item.content}</TabsContent>)}
|
|
59
|
+
</Tabs>
|
|
60
|
+
) : null}
|
|
61
|
+
{children}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { EntityDetails }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { RotateCcwIcon, SearchIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { Input } from "@/components/ui/input"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
export type FilterValue = string | number | boolean | null | undefined | string[] | [string | undefined, string | undefined]
|
|
9
|
+
|
|
10
|
+
export type FilterOption = {
|
|
11
|
+
value: string
|
|
12
|
+
label: React.ReactNode
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type FilterField = {
|
|
17
|
+
key: string
|
|
18
|
+
label?: React.ReactNode
|
|
19
|
+
type?: "text" | "search" | "select" | "date" | "date-range" | "custom"
|
|
20
|
+
placeholder?: string
|
|
21
|
+
options?: FilterOption[]
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
hidden?: boolean
|
|
24
|
+
className?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type FilterBuilderLabels = {
|
|
28
|
+
reset?: React.ReactNode
|
|
29
|
+
apply?: React.ReactNode
|
|
30
|
+
search?: string
|
|
31
|
+
from?: string
|
|
32
|
+
to?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type FilterBuilderProps<TFilters extends Record<string, FilterValue> = Record<string, FilterValue>> = React.ComponentProps<"div"> & {
|
|
36
|
+
value: TFilters
|
|
37
|
+
fields: FilterField[]
|
|
38
|
+
labels?: FilterBuilderLabels
|
|
39
|
+
layout?: "inline" | "grid"
|
|
40
|
+
columns?: 1 | 2 | 3 | 4
|
|
41
|
+
showApply?: boolean
|
|
42
|
+
showReset?: boolean
|
|
43
|
+
onChange: (value: TFilters) => void
|
|
44
|
+
onApply?: (value: TFilters) => void
|
|
45
|
+
onReset?: () => void
|
|
46
|
+
renderField?: (field: FilterField, value: FilterValue, helpers: { setValue: (value: FilterValue) => void; filters: TFilters }) => React.ReactNode
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function FilterBuilder<TFilters extends Record<string, FilterValue> = Record<string, FilterValue>>({
|
|
50
|
+
value,
|
|
51
|
+
fields,
|
|
52
|
+
labels,
|
|
53
|
+
layout = "inline",
|
|
54
|
+
columns = 3,
|
|
55
|
+
showApply = false,
|
|
56
|
+
showReset = true,
|
|
57
|
+
onChange,
|
|
58
|
+
onApply,
|
|
59
|
+
onReset,
|
|
60
|
+
renderField,
|
|
61
|
+
className,
|
|
62
|
+
...props
|
|
63
|
+
}: FilterBuilderProps<TFilters>) {
|
|
64
|
+
const setFieldValue = (key: string, nextValue: FilterValue) => onChange({ ...value, [key]: nextValue } as TFilters)
|
|
65
|
+
const visibleFields = fields.filter((field) => !field.hidden)
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div data-slot="filter-builder" className={cn("flex flex-wrap items-end gap-3", layout === "grid" && "grid w-full", columns === 2 && "sm:grid-cols-2", columns === 3 && "sm:grid-cols-2 lg:grid-cols-3", columns === 4 && "sm:grid-cols-2 lg:grid-cols-4", className)} {...props}>
|
|
69
|
+
{visibleFields.map((field) => (
|
|
70
|
+
<div key={field.key} data-slot="filter-builder-field" className={cn("grid min-w-48 gap-1.5", field.className)}>
|
|
71
|
+
{field.label && <label className="text-xs font-medium text-muted-foreground">{field.label}</label>}
|
|
72
|
+
{renderField?.(field, value[field.key], { setValue: (nextValue) => setFieldValue(field.key, nextValue), filters: value }) ?? <DefaultFilterField field={field} value={value[field.key]} labels={labels} onChange={(nextValue) => setFieldValue(field.key, nextValue)} />}
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
{(showReset || showApply) && (
|
|
76
|
+
<div data-slot="filter-builder-actions" className="flex items-center gap-2">
|
|
77
|
+
{showReset && <Button type="button" variant="outline" size="sm" onClick={onReset}><RotateCcwIcon data-icon="inline-start" />{labels?.reset ?? "Reset"}</Button>}
|
|
78
|
+
{showApply && <Button type="button" size="sm" onClick={() => onApply?.(value)}>{labels?.apply ?? "Apply"}</Button>}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function DefaultFilterField({ field, value, labels, onChange }: { field: FilterField; value: FilterValue; labels?: FilterBuilderLabels; onChange: (value: FilterValue) => void }) {
|
|
86
|
+
if (field.type === "select") {
|
|
87
|
+
return (
|
|
88
|
+
<select disabled={field.disabled} value={String(value ?? "")} className="h-8 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring" onChange={(event) => onChange(event.currentTarget.value || undefined)}>
|
|
89
|
+
<option value="">{field.placeholder ?? "All"}</option>
|
|
90
|
+
{field.options?.map((option) => <option key={option.value} value={option.value} disabled={option.disabled}>{typeof option.label === "string" ? option.label : option.value}</option>)}
|
|
91
|
+
</select>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (field.type === "date-range") {
|
|
96
|
+
const range = Array.isArray(value) ? value as [string | undefined, string | undefined] : [undefined, undefined]
|
|
97
|
+
return (
|
|
98
|
+
<div className="grid grid-cols-2 gap-2">
|
|
99
|
+
<Input type="date" disabled={field.disabled} value={range[0] ?? ""} aria-label={labels?.from ?? "From"} onChange={(event) => onChange([event.currentTarget.value || undefined, range[1]])} />
|
|
100
|
+
<Input type="date" disabled={field.disabled} value={range[1] ?? ""} aria-label={labels?.to ?? "To"} onChange={(event) => onChange([range[0], event.currentTarget.value || undefined])} />
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="relative">
|
|
107
|
+
{field.type === "search" && <SearchIcon className="pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />}
|
|
108
|
+
<Input type={field.type === "date" ? "date" : "text"} disabled={field.disabled} value={String(value ?? "")} placeholder={field.placeholder ?? labels?.search} className={cn(field.type === "search" && "pl-8")} onChange={(event) => onChange(event.currentTarget.value || undefined)} />
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { FilterBuilder }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { FieldPath, FieldValues } from "react-hook-form"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
FormBuilderAsyncSelectField,
|
|
5
|
+
FormBuilderCustomField,
|
|
6
|
+
FormBuilderDateField,
|
|
7
|
+
FormBuilderDateRangeField,
|
|
8
|
+
FormBuilderInputField,
|
|
9
|
+
FormBuilderNumberField,
|
|
10
|
+
FormBuilderPhoneField,
|
|
11
|
+
FormBuilderSection,
|
|
12
|
+
FormBuilderSelectField,
|
|
13
|
+
FormBuilderSwitchField,
|
|
14
|
+
FormBuilderTextareaField,
|
|
15
|
+
} from "./form-builder"
|
|
16
|
+
|
|
17
|
+
type FieldBase = {
|
|
18
|
+
id: string
|
|
19
|
+
hidden?: boolean
|
|
20
|
+
className?: string
|
|
21
|
+
colSpan?: 1 | 2 | 3 | 4 | "full"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type FieldPresetOptions<TProps> = FieldBase & {
|
|
25
|
+
props: TProps
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function splitFieldOptions<TProps>(options: FieldPresetOptions<TProps>) {
|
|
29
|
+
const { id, hidden, className, colSpan, props } = options
|
|
30
|
+
return { base: { id, hidden, className, colSpan }, props }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function inputField<
|
|
34
|
+
TFieldValues extends FieldValues,
|
|
35
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
36
|
+
>(options: FieldPresetOptions<FormBuilderInputField<TFieldValues, TName>["props"]>): FormBuilderInputField<TFieldValues, TName> {
|
|
37
|
+
const { base, props } = splitFieldOptions(options)
|
|
38
|
+
return { ...base, type: "input", props }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function textareaField<
|
|
42
|
+
TFieldValues extends FieldValues,
|
|
43
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
44
|
+
>(options: FieldPresetOptions<FormBuilderTextareaField<TFieldValues, TName>["props"]>): FormBuilderTextareaField<TFieldValues, TName> {
|
|
45
|
+
const { base, props } = splitFieldOptions(options)
|
|
46
|
+
return { ...base, type: "textarea", props }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function selectField<
|
|
50
|
+
TFieldValues extends FieldValues,
|
|
51
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
52
|
+
>(options: FieldPresetOptions<FormBuilderSelectField<TFieldValues, TName>["props"]>): FormBuilderSelectField<TFieldValues, TName> {
|
|
53
|
+
const { base, props } = splitFieldOptions(options)
|
|
54
|
+
return { ...base, type: "select", props }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asyncSelectField<
|
|
58
|
+
TFieldValues extends FieldValues,
|
|
59
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
60
|
+
>(options: FieldPresetOptions<FormBuilderAsyncSelectField<TFieldValues, TName>["props"]>): FormBuilderAsyncSelectField<TFieldValues, TName> {
|
|
61
|
+
const { base, props } = splitFieldOptions(options)
|
|
62
|
+
return { ...base, type: "async-select", props }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function switchField<
|
|
66
|
+
TFieldValues extends FieldValues,
|
|
67
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
68
|
+
>(options: FieldPresetOptions<FormBuilderSwitchField<TFieldValues, TName>["props"]>): FormBuilderSwitchField<TFieldValues, TName> {
|
|
69
|
+
const { base, props } = splitFieldOptions(options)
|
|
70
|
+
return { ...base, type: "switch", props }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function numberField<
|
|
74
|
+
TFieldValues extends FieldValues,
|
|
75
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
76
|
+
>(options: FieldPresetOptions<FormBuilderNumberField<TFieldValues, TName>["props"]>): FormBuilderNumberField<TFieldValues, TName> {
|
|
77
|
+
const { base, props } = splitFieldOptions(options)
|
|
78
|
+
return { ...base, type: "number", props }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function phoneField<
|
|
82
|
+
TFieldValues extends FieldValues,
|
|
83
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
84
|
+
>(options: FieldPresetOptions<FormBuilderPhoneField<TFieldValues, TName>["props"]>): FormBuilderPhoneField<TFieldValues, TName> {
|
|
85
|
+
const { base, props } = splitFieldOptions(options)
|
|
86
|
+
return { ...base, type: "phone", props }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function dateField<
|
|
90
|
+
TFieldValues extends FieldValues,
|
|
91
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
92
|
+
>(options: FieldPresetOptions<FormBuilderDateField<TFieldValues, TName>["props"]>): FormBuilderDateField<TFieldValues, TName> {
|
|
93
|
+
const { base, props } = splitFieldOptions(options)
|
|
94
|
+
return { ...base, type: "date", props }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function dateRangeField<
|
|
98
|
+
TFieldValues extends FieldValues,
|
|
99
|
+
TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
100
|
+
TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
101
|
+
>(options: FieldPresetOptions<FormBuilderDateRangeField<TFieldValues, TFromName, TToName>["props"]>): FormBuilderDateRangeField<TFieldValues, TFromName, TToName> {
|
|
102
|
+
const { base, props } = splitFieldOptions(options)
|
|
103
|
+
return { ...base, type: "date-range", props }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function customField<TFieldValues extends FieldValues>(
|
|
107
|
+
options: FieldBase & Pick<FormBuilderCustomField<TFieldValues>, "render">
|
|
108
|
+
): FormBuilderCustomField<TFieldValues> {
|
|
109
|
+
const { id, hidden, className, colSpan, render } = options
|
|
110
|
+
return { id, hidden, className, colSpan, type: "custom", render }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function formSection<TFieldValues extends FieldValues>(
|
|
114
|
+
section: FormBuilderSection<TFieldValues>
|
|
115
|
+
): FormBuilderSection<TFieldValues> {
|
|
116
|
+
return section
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
asyncSelectField,
|
|
121
|
+
customField,
|
|
122
|
+
dateField,
|
|
123
|
+
dateRangeField,
|
|
124
|
+
formSection,
|
|
125
|
+
inputField,
|
|
126
|
+
numberField,
|
|
127
|
+
phoneField,
|
|
128
|
+
selectField,
|
|
129
|
+
switchField,
|
|
130
|
+
textareaField,
|
|
131
|
+
}
|