tembro 4.2.0 → 5.0.0

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 (91) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/components/patterns/index.d.ts +0 -8
  3. package/dist/components/patterns/public.d.ts +0 -8
  4. package/dist/components/ui/hover-card/index.cjs +1 -0
  5. package/dist/components/ui/hover-card/index.d.ts +11 -0
  6. package/dist/components/ui/hover-card/index.js +1 -0
  7. package/dist/components/ui/menubar/index.cjs +1 -0
  8. package/dist/components/ui/menubar/index.d.ts +42 -0
  9. package/dist/components/ui/menubar/index.js +1 -0
  10. package/dist/components/ui/navigation-menu/index.cjs +1 -0
  11. package/dist/components/ui/navigation-menu/index.d.ts +20 -0
  12. package/dist/components/ui/navigation-menu/index.js +1 -0
  13. package/dist/index.d.ts +4 -0
  14. package/dist/src/components/patterns/index.cjs +1 -1
  15. package/dist/src/components/patterns/index.js +2 -10
  16. package/dist/src/components/patterns/public.cjs +1 -1
  17. package/dist/src/components/patterns/public.js +2 -10
  18. package/dist/src/components/ui/hover-card/index.cjs +1 -0
  19. package/dist/src/components/ui/hover-card/index.js +50 -0
  20. package/dist/src/components/ui/menubar/index.cjs +1 -0
  21. package/dist/src/components/ui/menubar/index.js +132 -0
  22. package/dist/src/components/ui/navigation-menu/index.cjs +1 -0
  23. package/dist/src/components/ui/navigation-menu/index.js +73 -0
  24. package/dist/src/components/ui/popover/index.cjs +1 -1
  25. package/dist/src/components/ui/popover/index.js +1 -1
  26. package/dist/src/index.cjs +1 -1
  27. package/dist/src/index.js +25 -29
  28. package/dist/src/public-component-surface.cjs +1 -1
  29. package/dist/src/public-component-surface.js +16 -42
  30. package/package.json +1 -1
  31. package/packages/cli/dist/index.cjs +100 -100
  32. package/packages/cli/vendor/src/components/patterns/index.ts +0 -8
  33. package/packages/cli/vendor/src/components/patterns/public.ts +0 -8
  34. package/packages/cli/vendor/src/components/ui/hover-card/index.tsx +72 -0
  35. package/packages/cli/vendor/src/components/ui/menubar/index.tsx +140 -0
  36. package/packages/cli/vendor/src/components/ui/navigation-menu/index.tsx +100 -0
  37. package/packages/cli/vendor/src/components/ui/popover/index.tsx +1 -1
  38. package/packages/cli/vendor/src/index.ts +6 -2
  39. package/packages/cli/vendor/src/public-component-surface.ts +4 -10
  40. package/packages/cli/vendor/templates/showcase/src/showcase/data/registry.ts +10 -10
  41. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
  42. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  43. package/registry.json +2124 -258
  44. package/dist/components/patterns/bulk-action-bar.cjs +0 -1
  45. package/dist/components/patterns/bulk-action-bar.d.ts +0 -10
  46. package/dist/components/patterns/bulk-action-bar.js +0 -1
  47. package/dist/components/patterns/data-view.cjs +0 -1
  48. package/dist/components/patterns/data-view.d.ts +0 -17
  49. package/dist/components/patterns/data-view.js +0 -1
  50. package/dist/components/patterns/detail-layout.cjs +0 -1
  51. package/dist/components/patterns/detail-layout.d.ts +0 -16
  52. package/dist/components/patterns/detail-layout.js +0 -1
  53. package/dist/components/patterns/form-builder.cjs +0 -1
  54. package/dist/components/patterns/form-builder.d.ts +0 -110
  55. package/dist/components/patterns/form-builder.js +0 -1
  56. package/dist/components/patterns/page-toolbar.cjs +0 -1
  57. package/dist/components/patterns/page-toolbar.d.ts +0 -11
  58. package/dist/components/patterns/page-toolbar.js +0 -1
  59. package/dist/components/patterns/resource-detail-page.cjs +0 -1
  60. package/dist/components/patterns/resource-detail-page.d.ts +0 -39
  61. package/dist/components/patterns/resource-detail-page.js +0 -1
  62. package/dist/components/patterns/resource-page.cjs +0 -1
  63. package/dist/components/patterns/resource-page.d.ts +0 -30
  64. package/dist/components/patterns/resource-page.js +0 -1
  65. package/dist/components/patterns/settings-page.cjs +0 -1
  66. package/dist/components/patterns/settings-page.d.ts +0 -21
  67. package/dist/components/patterns/settings-page.js +0 -1
  68. package/dist/src/components/patterns/bulk-action-bar.cjs +0 -1
  69. package/dist/src/components/patterns/bulk-action-bar.js +0 -34
  70. package/dist/src/components/patterns/data-view.cjs +0 -1
  71. package/dist/src/components/patterns/data-view.js +0 -47
  72. package/dist/src/components/patterns/detail-layout.cjs +0 -1
  73. package/dist/src/components/patterns/detail-layout.js +0 -65
  74. package/dist/src/components/patterns/form-builder.cjs +0 -1
  75. package/dist/src/components/patterns/form-builder.js +0 -263
  76. package/dist/src/components/patterns/page-toolbar.cjs +0 -1
  77. package/dist/src/components/patterns/page-toolbar.js +0 -41
  78. package/dist/src/components/patterns/resource-detail-page.cjs +0 -1
  79. package/dist/src/components/patterns/resource-detail-page.js +0 -100
  80. package/dist/src/components/patterns/resource-page.cjs +0 -1
  81. package/dist/src/components/patterns/resource-page.js +0 -88
  82. package/dist/src/components/patterns/settings-page.cjs +0 -1
  83. package/dist/src/components/patterns/settings-page.js +0 -64
  84. package/packages/cli/vendor/src/components/patterns/bulk-action-bar.tsx +0 -53
  85. package/packages/cli/vendor/src/components/patterns/data-view.tsx +0 -64
  86. package/packages/cli/vendor/src/components/patterns/detail-layout.tsx +0 -75
  87. package/packages/cli/vendor/src/components/patterns/form-builder.tsx +0 -472
  88. package/packages/cli/vendor/src/components/patterns/page-toolbar.tsx +0 -61
  89. package/packages/cli/vendor/src/components/patterns/resource-detail-page.tsx +0 -164
  90. package/packages/cli/vendor/src/components/patterns/resource-page.tsx +0 -149
  91. package/packages/cli/vendor/src/components/patterns/settings-page.tsx +0 -84
@@ -1,64 +0,0 @@
1
- import * as React from "react"
2
-
3
- import { DataTable, type DataTableProps } from "@/components/data-table/data-table"
4
- import { EmptyState } from "@/components/patterns/empty-state"
5
- import { PageToolbar } from "@/components/patterns/page-toolbar"
6
- import { cn } from "@/lib/utils"
7
-
8
- export type DataViewProps<TData, TValue = unknown> = Omit<React.ComponentProps<"div">, "children"> & {
9
- title?: React.ReactNode
10
- description?: React.ReactNode
11
- toolbar?: React.ComponentProps<typeof PageToolbar>
12
- table?: DataTableProps<TData, TValue>
13
- count?: number
14
- loading?: boolean
15
- error?: React.ReactNode
16
- emptyTitle?: React.ReactNode
17
- emptyDescription?: React.ReactNode
18
- children?: React.ReactNode
19
- }
20
-
21
- function DataView<TData, TValue = unknown>({
22
- className,
23
- title,
24
- description,
25
- toolbar,
26
- table,
27
- count,
28
- loading = false,
29
- error,
30
- emptyTitle = "No results",
31
- emptyDescription = "Try adjusting filters or create a new item.",
32
- children,
33
- ...props
34
- }: DataViewProps<TData, TValue>) {
35
- const hasHeader = Boolean(title || description)
36
- const hasToolbar = Boolean(toolbar)
37
- const hasData = count === undefined ? Boolean(children || table) : count > 0
38
-
39
- return (
40
- <div data-slot="data-view" className={cn("grid min-w-0 gap-4", className)} {...props}>
41
- {hasHeader && (
42
- <div data-slot="data-view-header" className="grid gap-1">
43
- {title && <h2 className="text-xl font-semibold tracking-tight">{title}</h2>}
44
- {description && <p className="text-sm leading-6 text-muted-foreground">{description}</p>}
45
- </div>
46
- )}
47
- {hasToolbar && <PageToolbar {...toolbar} />}
48
- {error ? (
49
- <EmptyState tone="error" title="Something went wrong" description={error} />
50
- ) : loading ? (
51
- <EmptyState tone="info" title="Loading data" description="Please wait while the latest data is prepared." />
52
- ) : hasData ? (
53
- <div data-slot="data-view-content" className="min-w-0">
54
- {children}
55
- {table && <DataTable {...table} />}
56
- </div>
57
- ) : (
58
- <EmptyState title={emptyTitle} description={emptyDescription} />
59
- )}
60
- </div>
61
- )
62
- }
63
-
64
- export { DataView }
@@ -1,75 +0,0 @@
1
- import * as React from "react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- export type DetailLayoutProps = React.ComponentProps<"div"> & {
6
- title?: React.ReactNode
7
- description?: React.ReactNode
8
- eyebrow?: React.ReactNode
9
- actions?: React.ReactNode
10
- breadcrumbs?: React.ReactNode
11
- summary?: React.ReactNode
12
- aside?: React.ReactNode
13
- footer?: React.ReactNode
14
- headerClassName?: string
15
- contentClassName?: string
16
- asideClassName?: string
17
- }
18
-
19
- function DetailLayout({
20
- className,
21
- title,
22
- description,
23
- eyebrow,
24
- actions,
25
- breadcrumbs,
26
- summary,
27
- aside,
28
- footer,
29
- headerClassName,
30
- contentClassName,
31
- asideClassName,
32
- children,
33
- ...props
34
- }: DetailLayoutProps) {
35
- const hasHeader = Boolean(title || description || eyebrow || actions)
36
- const hasContent = Boolean(children || aside)
37
-
38
- return (
39
- <div data-slot="detail-layout" className={cn("grid min-w-0 gap-4", className)} {...props}>
40
- {breadcrumbs && <div data-slot="detail-layout-breadcrumbs">{breadcrumbs}</div>}
41
- {hasHeader && (
42
- <div
43
- data-slot="detail-layout-header"
44
- className={cn("flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", headerClassName)}
45
- >
46
- <div className="grid min-w-0 gap-1">
47
- {eyebrow && <div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">{eyebrow}</div>}
48
- {title && <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>}
49
- {description && <p className="max-w-3xl text-sm leading-6 text-muted-foreground">{description}</p>}
50
- </div>
51
- {actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>}
52
- </div>
53
- )}
54
- {summary && <div data-slot="detail-layout-summary">{summary}</div>}
55
- {hasContent && (
56
- <div
57
- data-slot="detail-layout-content"
58
- className={cn("grid min-w-0 gap-4", aside && "xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
59
- >
60
- <main data-slot="detail-layout-main" className="min-w-0">
61
- {children}
62
- </main>
63
- {aside && (
64
- <aside data-slot="detail-layout-aside" className={cn("min-w-0", asideClassName)}>
65
- {aside}
66
- </aside>
67
- )}
68
- </div>
69
- )}
70
- {footer && <div data-slot="detail-layout-footer">{footer}</div>}
71
- </div>
72
- )
73
- }
74
-
75
- export { DetailLayout }
@@ -1,472 +0,0 @@
1
- import * as React from "react"
2
- import type { Control, FieldPath, FieldValues } from "react-hook-form"
3
-
4
- import { Button } from "@/components/ui/button"
5
- import {
6
- FormInput,
7
- type FormInputDateVariantProps,
8
- type FormInputNumberVariantProps,
9
- type FormInputPhoneVariantProps,
10
- type FormInputProps,
11
- } from "@/components/form/form-input"
12
- import { FormTextarea, type FormTextareaProps } from "@/components/form/form-textarea"
13
- import {
14
- FormSelect,
15
- type FormSelectAsyncVariantProps,
16
- type FormSelectProps,
17
- } from "@/components/form/form-select"
18
- import { FormSwitch, type FormSwitchProps } from "@/components/form/form-switch"
19
- import {
20
- FormDateRangeInput,
21
- type FormDateRangeInputProps,
22
- } from "@/components/form/form-date-range-input"
23
- import { cn } from "@/lib/utils"
24
-
25
- export type FormBuilderLayout = "grid" | "stack"
26
- export type FormBuilderDensity = "compact" | "default" | "comfortable"
27
-
28
- type DistributiveOmit<T, TKey extends PropertyKey> = T extends unknown ? Omit<T, TKey> : never
29
-
30
- export type FormBuilderFieldRenderContext<TFieldValues extends FieldValues> = {
31
- control: Control<TFieldValues>
32
- disabled?: boolean
33
- readOnly?: boolean
34
- }
35
-
36
- type BaseFormBuilderField = {
37
- id: string
38
- hidden?: boolean
39
- className?: string
40
- colSpan?: 1 | 2 | 3 | 4 | "full"
41
- }
42
-
43
- export type FormBuilderCustomField<TFieldValues extends FieldValues> = BaseFormBuilderField & {
44
- type: "custom"
45
- render: (context: FormBuilderFieldRenderContext<TFieldValues>) => React.ReactNode
46
- }
47
-
48
- export type FormBuilderInputField<
49
- TFieldValues extends FieldValues,
50
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
51
- > = BaseFormBuilderField & {
52
- type: "input"
53
- props: DistributiveOmit<FormInputProps<TFieldValues, TName>, "control">
54
- }
55
-
56
- export type FormBuilderTextareaField<
57
- TFieldValues extends FieldValues,
58
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
59
- > = BaseFormBuilderField & {
60
- type: "textarea"
61
- props: Omit<FormTextareaProps<TFieldValues, TName>, "control">
62
- }
63
-
64
- export type FormBuilderSelectField<
65
- TFieldValues extends FieldValues,
66
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
67
- > = BaseFormBuilderField & {
68
- type: "select"
69
- props: DistributiveOmit<FormSelectProps<TFieldValues, TName>, "control">
70
- }
71
-
72
- export type FormBuilderAsyncSelectField<
73
- TFieldValues extends FieldValues,
74
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
75
- > = BaseFormBuilderField & {
76
- type: "async-select"
77
- props: Omit<FormSelectAsyncVariantProps<TFieldValues, TName>, "control" | "kind">
78
- }
79
-
80
- export type FormBuilderSwitchField<
81
- TFieldValues extends FieldValues,
82
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
83
- > = BaseFormBuilderField & {
84
- type: "switch"
85
- props: Omit<FormSwitchProps<TFieldValues, TName>, "control">
86
- }
87
-
88
- export type FormBuilderNumberField<
89
- TFieldValues extends FieldValues,
90
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
91
- > = BaseFormBuilderField & {
92
- type: "number"
93
- props: Omit<FormInputNumberVariantProps<TFieldValues, TName>, "control" | "kind">
94
- }
95
-
96
- export type FormBuilderPhoneField<
97
- TFieldValues extends FieldValues,
98
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
99
- > = BaseFormBuilderField & {
100
- type: "phone"
101
- props: Omit<FormInputPhoneVariantProps<TFieldValues, TName>, "control" | "kind">
102
- }
103
-
104
- export type FormBuilderDateField<
105
- TFieldValues extends FieldValues,
106
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
107
- > = BaseFormBuilderField & {
108
- type: "date"
109
- props: Omit<FormInputDateVariantProps<TFieldValues, TName>, "control" | "kind">
110
- }
111
-
112
- export type FormBuilderDateRangeField<
113
- TFieldValues extends FieldValues,
114
- TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
115
- TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
116
- > = BaseFormBuilderField & {
117
- type: "date-range"
118
- props: Omit<FormDateRangeInputProps<TFieldValues, TFromName, TToName>, "control">
119
- }
120
-
121
- type FormBuilderStoredField = BaseFormBuilderField & {
122
- type: "input" | "textarea" | "select" | "async-select" | "switch" | "number" | "phone" | "date" | "date-range"
123
- props: object
124
- }
125
-
126
- export type FormBuilderField<TFieldValues extends FieldValues> =
127
- | FormBuilderCustomField<TFieldValues>
128
- | FormBuilderStoredField
129
-
130
- export type FormBuilderSection<TFieldValues extends FieldValues> = {
131
- id: string
132
- title?: React.ReactNode
133
- description?: React.ReactNode
134
- actions?: React.ReactNode
135
- hidden?: boolean
136
- className?: string
137
- fields: FormBuilderField<TFieldValues>[]
138
- }
139
-
140
- export type FormBuilderProps<TFieldValues extends FieldValues> = Omit<
141
- React.ComponentProps<"form">,
142
- "children"
143
- > & {
144
- control: Control<TFieldValues>
145
- fields?: FormBuilderField<TFieldValues>[]
146
- sections?: FormBuilderSection<TFieldValues>[]
147
- layout?: FormBuilderLayout
148
- density?: FormBuilderDensity
149
- columns?: 1 | 2 | 3 | 4
150
- disabled?: boolean
151
- readOnly?: boolean
152
- footer?: React.ReactNode
153
- submitLabel?: React.ReactNode
154
- resetLabel?: React.ReactNode
155
- onResetClick?: () => void
156
- isSubmitting?: boolean
157
- sectionClassName?: string
158
- fieldClassName?: string
159
- footerClassName?: string
160
- }
161
-
162
- const densityClassName: Record<FormBuilderDensity, string> = {
163
- compact: "gap-3",
164
- default: "gap-4",
165
- comfortable: "gap-6",
166
- }
167
-
168
- const columnsClassName: Record<1 | 2 | 3 | 4, string> = {
169
- 1: "grid-cols-1",
170
- 2: "grid-cols-1 md:grid-cols-2",
171
- 3: "grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
172
- 4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4",
173
- }
174
-
175
- const colSpanClassName: Record<NonNullable<BaseFormBuilderField["colSpan"]>, string> = {
176
- 1: "col-span-1",
177
- 2: "md:col-span-2",
178
- 3: "xl:col-span-3",
179
- 4: "xl:col-span-4",
180
- full: "col-span-full",
181
- }
182
-
183
- type FieldPresetOptions<TProps> = BaseFormBuilderField & {
184
- props: TProps
185
- }
186
-
187
- function splitFieldOptions<TProps>(options: FieldPresetOptions<TProps>) {
188
- const { id, hidden, className, colSpan, props } = options
189
- return { base: { id, hidden, className, colSpan }, props }
190
- }
191
-
192
- function inputField<
193
- TFieldValues extends FieldValues,
194
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
195
- >(options: FieldPresetOptions<FormBuilderInputField<TFieldValues, TName>["props"]>): FormBuilderInputField<TFieldValues, TName> {
196
- const { base, props } = splitFieldOptions(options)
197
- return { ...base, type: "input", props }
198
- }
199
-
200
- function textareaField<
201
- TFieldValues extends FieldValues,
202
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
203
- >(options: FieldPresetOptions<FormBuilderTextareaField<TFieldValues, TName>["props"]>): FormBuilderTextareaField<TFieldValues, TName> {
204
- const { base, props } = splitFieldOptions(options)
205
- return { ...base, type: "textarea", props }
206
- }
207
-
208
- function selectField<
209
- TFieldValues extends FieldValues,
210
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
211
- >(options: FieldPresetOptions<FormBuilderSelectField<TFieldValues, TName>["props"]>): FormBuilderSelectField<TFieldValues, TName> {
212
- const { base, props } = splitFieldOptions(options)
213
- return { ...base, type: "select", props }
214
- }
215
-
216
- function asyncSelectField<
217
- TFieldValues extends FieldValues,
218
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
219
- >(options: FieldPresetOptions<FormBuilderAsyncSelectField<TFieldValues, TName>["props"]>): FormBuilderAsyncSelectField<TFieldValues, TName> {
220
- const { base, props } = splitFieldOptions(options)
221
- return { ...base, type: "async-select", props }
222
- }
223
-
224
- function switchField<
225
- TFieldValues extends FieldValues,
226
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
227
- >(options: FieldPresetOptions<FormBuilderSwitchField<TFieldValues, TName>["props"]>): FormBuilderSwitchField<TFieldValues, TName> {
228
- const { base, props } = splitFieldOptions(options)
229
- return { ...base, type: "switch", props }
230
- }
231
-
232
- function numberField<
233
- TFieldValues extends FieldValues,
234
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
235
- >(options: FieldPresetOptions<FormBuilderNumberField<TFieldValues, TName>["props"]>): FormBuilderNumberField<TFieldValues, TName> {
236
- const { base, props } = splitFieldOptions(options)
237
- return { ...base, type: "number", props }
238
- }
239
-
240
- function phoneField<
241
- TFieldValues extends FieldValues,
242
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
243
- >(options: FieldPresetOptions<FormBuilderPhoneField<TFieldValues, TName>["props"]>): FormBuilderPhoneField<TFieldValues, TName> {
244
- const { base, props } = splitFieldOptions(options)
245
- return { ...base, type: "phone", props }
246
- }
247
-
248
- function dateField<
249
- TFieldValues extends FieldValues,
250
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
251
- >(options: FieldPresetOptions<FormBuilderDateField<TFieldValues, TName>["props"]>): FormBuilderDateField<TFieldValues, TName> {
252
- const { base, props } = splitFieldOptions(options)
253
- return { ...base, type: "date", props }
254
- }
255
-
256
- function dateRangeField<
257
- TFieldValues extends FieldValues,
258
- TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
259
- TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
260
- >(options: FieldPresetOptions<FormBuilderDateRangeField<TFieldValues, TFromName, TToName>["props"]>): FormBuilderDateRangeField<TFieldValues, TFromName, TToName> {
261
- const { base, props } = splitFieldOptions(options)
262
- return { ...base, type: "date-range", props }
263
- }
264
-
265
- function customField<TFieldValues extends FieldValues>(
266
- options: BaseFormBuilderField & Pick<FormBuilderCustomField<TFieldValues>, "render">
267
- ): FormBuilderCustomField<TFieldValues> {
268
- const { id, hidden, className, colSpan, render } = options
269
- return { id, hidden, className, colSpan, type: "custom", render }
270
- }
271
-
272
- function formSection<TFieldValues extends FieldValues>(
273
- section: FormBuilderSection<TFieldValues>
274
- ): FormBuilderSection<TFieldValues> {
275
- return section
276
- }
277
-
278
- function renderFormBuilderField<TFieldValues extends FieldValues>(
279
- field: FormBuilderField<TFieldValues>,
280
- context: FormBuilderFieldRenderContext<TFieldValues>
281
- ) {
282
- const FormInputComponent = FormInput as unknown as React.ComponentType<Record<string, unknown>>
283
- const FormSelectComponent = FormSelect as unknown as React.ComponentType<Record<string, unknown>>
284
- const FormTextareaComponent = FormTextarea as unknown as React.ComponentType<Record<string, unknown>>
285
- const FormSwitchComponent = FormSwitch as unknown as React.ComponentType<Record<string, unknown>>
286
- const FormDateRangeInputComponent = FormDateRangeInput as unknown as React.ComponentType<Record<string, unknown>>
287
-
288
- switch (field.type) {
289
- case "custom":
290
- return field.render(context)
291
- case "input":
292
- return React.createElement(FormInputComponent, {
293
- control: context.control,
294
- disabled: context.disabled,
295
- readOnly: context.readOnly,
296
- ...(field.props as Omit<FormInputProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
297
- })
298
- case "textarea":
299
- return React.createElement(FormTextareaComponent, { control: context.control, disabled: context.disabled, readOnly: context.readOnly, ...field.props })
300
- case "select":
301
- return React.createElement(FormSelectComponent, {
302
- control: context.control,
303
- disabled: context.disabled,
304
- ...(field.props as Omit<FormSelectProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
305
- })
306
- case "async-select":
307
- return React.createElement(FormSelectComponent, {
308
- control: context.control,
309
- disabled: context.disabled,
310
- kind: "async",
311
- ...field.props,
312
- })
313
- case "switch":
314
- return React.createElement(FormSwitchComponent, { control: context.control, disabled: context.disabled, ...field.props })
315
- case "number":
316
- return React.createElement(FormInputComponent, {
317
- control: context.control,
318
- disabled: context.disabled,
319
- readOnly: context.readOnly,
320
- kind: "number",
321
- ...field.props,
322
- })
323
- case "phone":
324
- return React.createElement(FormInputComponent, {
325
- control: context.control,
326
- disabled: context.disabled,
327
- readOnly: context.readOnly,
328
- kind: "phone",
329
- ...field.props,
330
- })
331
- case "date":
332
- return React.createElement(FormInputComponent, {
333
- control: context.control,
334
- disabled: context.disabled,
335
- readOnly: context.readOnly,
336
- kind: "date",
337
- ...field.props,
338
- })
339
- case "date-range":
340
- return React.createElement(FormDateRangeInputComponent, {
341
- control: context.control,
342
- fromInputProps: { disabled: context.disabled, readOnly: context.readOnly },
343
- toInputProps: { disabled: context.disabled, readOnly: context.readOnly },
344
- ...field.props,
345
- })
346
- default:
347
- return null
348
- }
349
- }
350
-
351
- function normalizeSections<TFieldValues extends FieldValues>({
352
- fields,
353
- sections,
354
- }: Pick<FormBuilderProps<TFieldValues>, "fields" | "sections">): FormBuilderSection<TFieldValues>[] {
355
- if (sections?.length) return sections
356
- return [{ id: "default", fields: fields ?? [] }]
357
- }
358
-
359
- function FormBuilder<TFieldValues extends FieldValues>({
360
- className,
361
- control,
362
- fields,
363
- sections,
364
- layout = "grid",
365
- density = "default",
366
- columns = 2,
367
- disabled,
368
- readOnly,
369
- footer,
370
- submitLabel,
371
- resetLabel,
372
- onResetClick,
373
- isSubmitting,
374
- sectionClassName,
375
- fieldClassName,
376
- footerClassName,
377
- ...props
378
- }: FormBuilderProps<TFieldValues>) {
379
- const normalizedSections = normalizeSections({ fields, sections })
380
- const context = React.useMemo(
381
- () => ({ control, disabled, readOnly }),
382
- [control, disabled, readOnly]
383
- )
384
-
385
- return (
386
- <form data-slot="form-builder" className={cn("grid", densityClassName[density], className)} {...props}>
387
- {normalizedSections
388
- .filter((section) => !section.hidden)
389
- .map((section) => (
390
- <section
391
- key={section.id}
392
- data-slot="form-builder-section"
393
- className={cn("grid gap-4", section.className, sectionClassName)}
394
- >
395
- {(section.title || section.description || section.actions) && (
396
- <div
397
- data-slot="form-builder-section-header"
398
- className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"
399
- >
400
- <div className="min-w-0 space-y-1">
401
- {section.title && <h2 className="text-base font-semibold leading-none tracking-tight">{section.title}</h2>}
402
- {section.description && <p className="text-sm text-muted-foreground">{section.description}</p>}
403
- </div>
404
- {section.actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{section.actions}</div>}
405
- </div>
406
- )}
407
-
408
- <div
409
- data-slot="form-builder-fields"
410
- data-layout={layout}
411
- className={cn(
412
- "grid",
413
- densityClassName[density],
414
- layout === "grid" ? columnsClassName[columns] : "grid-cols-1"
415
- )}
416
- >
417
- {section.fields
418
- .filter((field) => !field.hidden)
419
- .map((field) => (
420
- <div
421
- key={field.id}
422
- data-slot="form-builder-field"
423
- className={cn(
424
- field.colSpan && colSpanClassName[field.colSpan],
425
- field.className,
426
- fieldClassName
427
- )}
428
- >
429
- {renderFormBuilderField(field, context)}
430
- </div>
431
- ))}
432
- </div>
433
- </section>
434
- ))}
435
-
436
- {(footer || submitLabel || resetLabel) && (
437
- <div data-slot="form-builder-footer" className={cn("flex flex-wrap items-center justify-end gap-2", footerClassName)}>
438
- {footer ?? (
439
- <>
440
- {resetLabel && (
441
- <Button type="button" variant="outline" disabled={disabled || isSubmitting} onClick={onResetClick}>
442
- {resetLabel}
443
- </Button>
444
- )}
445
- {submitLabel && (
446
- <Button type="submit" disabled={disabled || isSubmitting}>
447
- {submitLabel}
448
- </Button>
449
- )}
450
- </>
451
- )}
452
- </div>
453
- )}
454
- </form>
455
- )
456
- }
457
-
458
- export {
459
- FormBuilder,
460
- asyncSelectField,
461
- customField,
462
- dateField,
463
- dateRangeField,
464
- formSection,
465
- inputField,
466
- numberField,
467
- phoneField,
468
- renderFormBuilderField,
469
- selectField,
470
- switchField,
471
- textareaField,
472
- }
@@ -1,61 +0,0 @@
1
- import * as React from "react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- export type PageToolbarProps = React.ComponentProps<"div"> & {
6
- search?: React.ReactNode
7
- filters?: React.ReactNode
8
- viewOptions?: React.ReactNode
9
- actions?: React.ReactNode
10
- summary?: React.ReactNode
11
- sticky?: boolean
12
- }
13
-
14
- function PageToolbar({
15
- className,
16
- search,
17
- filters,
18
- viewOptions,
19
- actions,
20
- summary,
21
- sticky = false,
22
- children,
23
- ...props
24
- }: PageToolbarProps) {
25
- const hasPrimary = Boolean(search || filters || children)
26
- const hasSecondary = Boolean(summary || viewOptions || actions)
27
-
28
- if (!hasPrimary && !hasSecondary) return null
29
-
30
- return (
31
- <div
32
- data-slot="page-toolbar"
33
- data-sticky={sticky || undefined}
34
- className={cn(
35
- "flex flex-col gap-3 rounded-xl border bg-card p-3 shadow-sm",
36
- sticky && "sticky top-0 z-10",
37
- className
38
- )}
39
- {...props}
40
- >
41
- <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
42
- {hasPrimary && (
43
- <div data-slot="page-toolbar-primary" className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
44
- {search && <div className="min-w-0 flex-1">{search}</div>}
45
- {filters}
46
- {children}
47
- </div>
48
- )}
49
- {hasSecondary && (
50
- <div data-slot="page-toolbar-secondary" className="flex shrink-0 flex-wrap items-center gap-2">
51
- {summary && <div className="text-sm text-muted-foreground">{summary}</div>}
52
- {viewOptions}
53
- {actions}
54
- </div>
55
- )}
56
- </div>
57
- </div>
58
- )
59
- }
60
-
61
- export { PageToolbar }