azamat-ui-kit-cli 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,334 @@
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 { FormInput, type FormInputProps } from "@/components/form/form-input"
6
+ import { FormTextarea, type FormTextareaProps } from "@/components/form/form-textarea"
7
+ import { FormSelect, type FormSelectProps } from "@/components/form/form-select"
8
+ import { FormAsyncSelect, type FormAsyncSelectProps } from "@/components/form/form-async-select"
9
+ import { FormSwitch, type FormSwitchProps } from "@/components/form/form-switch"
10
+ import { FormNumberInput, type FormNumberInputProps } from "@/components/form/form-number-input"
11
+ import { FormPhoneInput, type FormPhoneInputProps } from "@/components/form/form-phone-input"
12
+ import { FormDateInput, type FormDateInputProps } from "@/components/form/form-date-input"
13
+ import {
14
+ FormDateRangeInput,
15
+ type FormDateRangeInputProps,
16
+ } from "@/components/form/form-date-range-input"
17
+ import { cn } from "@/lib/utils"
18
+
19
+ export type FormBuilderLayout = "grid" | "stack"
20
+ export type FormBuilderDensity = "compact" | "default" | "comfortable"
21
+
22
+ export type FormBuilderFieldRenderContext<TFieldValues extends FieldValues> = {
23
+ control: Control<TFieldValues>
24
+ disabled?: boolean
25
+ readOnly?: boolean
26
+ }
27
+
28
+ type BaseFormBuilderField = {
29
+ id: string
30
+ hidden?: boolean
31
+ className?: string
32
+ colSpan?: 1 | 2 | 3 | 4 | "full"
33
+ }
34
+
35
+ export type FormBuilderCustomField<TFieldValues extends FieldValues> = BaseFormBuilderField & {
36
+ type: "custom"
37
+ render: (context: FormBuilderFieldRenderContext<TFieldValues>) => React.ReactNode
38
+ }
39
+
40
+ export type FormBuilderInputField<
41
+ TFieldValues extends FieldValues,
42
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
43
+ > = BaseFormBuilderField & {
44
+ type: "input"
45
+ props: Omit<FormInputProps<TFieldValues, TName>, "control">
46
+ }
47
+
48
+ export type FormBuilderTextareaField<
49
+ TFieldValues extends FieldValues,
50
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
51
+ > = BaseFormBuilderField & {
52
+ type: "textarea"
53
+ props: Omit<FormTextareaProps<TFieldValues, TName>, "control">
54
+ }
55
+
56
+ export type FormBuilderSelectField<
57
+ TFieldValues extends FieldValues,
58
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
59
+ > = BaseFormBuilderField & {
60
+ type: "select"
61
+ props: Omit<FormSelectProps<TFieldValues, TName>, "control">
62
+ }
63
+
64
+ export type FormBuilderAsyncSelectField<
65
+ TFieldValues extends FieldValues,
66
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
67
+ > = BaseFormBuilderField & {
68
+ type: "async-select"
69
+ props: Omit<FormAsyncSelectProps<TFieldValues, TName>, "control">
70
+ }
71
+
72
+ export type FormBuilderSwitchField<
73
+ TFieldValues extends FieldValues,
74
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
75
+ > = BaseFormBuilderField & {
76
+ type: "switch"
77
+ props: Omit<FormSwitchProps<TFieldValues, TName>, "control">
78
+ }
79
+
80
+ export type FormBuilderNumberField<
81
+ TFieldValues extends FieldValues,
82
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
83
+ > = BaseFormBuilderField & {
84
+ type: "number"
85
+ props: Omit<FormNumberInputProps<TFieldValues, TName>, "control">
86
+ }
87
+
88
+ export type FormBuilderPhoneField<
89
+ TFieldValues extends FieldValues,
90
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
91
+ > = BaseFormBuilderField & {
92
+ type: "phone"
93
+ props: Omit<FormPhoneInputProps<TFieldValues, TName>, "control">
94
+ }
95
+
96
+ export type FormBuilderDateField<
97
+ TFieldValues extends FieldValues,
98
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
99
+ > = BaseFormBuilderField & {
100
+ type: "date"
101
+ props: Omit<FormDateInputProps<TFieldValues, TName>, "control">
102
+ }
103
+
104
+ export type FormBuilderDateRangeField<
105
+ TFieldValues extends FieldValues,
106
+ TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
107
+ TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
108
+ > = BaseFormBuilderField & {
109
+ type: "date-range"
110
+ props: Omit<FormDateRangeInputProps<TFieldValues, TFromName, TToName>, "control">
111
+ }
112
+
113
+ export type FormBuilderField<TFieldValues extends FieldValues> =
114
+ | FormBuilderCustomField<TFieldValues>
115
+ | FormBuilderInputField<TFieldValues>
116
+ | FormBuilderTextareaField<TFieldValues>
117
+ | FormBuilderSelectField<TFieldValues>
118
+ | FormBuilderAsyncSelectField<TFieldValues>
119
+ | FormBuilderSwitchField<TFieldValues>
120
+ | FormBuilderNumberField<TFieldValues>
121
+ | FormBuilderPhoneField<TFieldValues>
122
+ | FormBuilderDateField<TFieldValues>
123
+ | FormBuilderDateRangeField<TFieldValues>
124
+
125
+ export type FormBuilderSection<TFieldValues extends FieldValues> = {
126
+ id: string
127
+ title?: React.ReactNode
128
+ description?: React.ReactNode
129
+ actions?: React.ReactNode
130
+ hidden?: boolean
131
+ className?: string
132
+ fields: FormBuilderField<TFieldValues>[]
133
+ }
134
+
135
+ export type FormBuilderProps<TFieldValues extends FieldValues> = Omit<
136
+ React.ComponentProps<"form">,
137
+ "children"
138
+ > & {
139
+ control: Control<TFieldValues>
140
+ fields?: FormBuilderField<TFieldValues>[]
141
+ sections?: FormBuilderSection<TFieldValues>[]
142
+ layout?: FormBuilderLayout
143
+ density?: FormBuilderDensity
144
+ columns?: 1 | 2 | 3 | 4
145
+ disabled?: boolean
146
+ readOnly?: boolean
147
+ footer?: React.ReactNode
148
+ submitLabel?: React.ReactNode
149
+ resetLabel?: React.ReactNode
150
+ onResetClick?: () => void
151
+ isSubmitting?: boolean
152
+ sectionClassName?: string
153
+ fieldClassName?: string
154
+ footerClassName?: string
155
+ }
156
+
157
+ const densityClassName: Record<FormBuilderDensity, string> = {
158
+ compact: "gap-3",
159
+ default: "gap-4",
160
+ comfortable: "gap-6",
161
+ }
162
+
163
+ const columnsClassName: Record<1 | 2 | 3 | 4, string> = {
164
+ 1: "grid-cols-1",
165
+ 2: "grid-cols-1 md:grid-cols-2",
166
+ 3: "grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
167
+ 4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4",
168
+ }
169
+
170
+ const colSpanClassName: Record<NonNullable<BaseFormBuilderField["colSpan"]>, string> = {
171
+ 1: "col-span-1",
172
+ 2: "md:col-span-2",
173
+ 3: "xl:col-span-3",
174
+ 4: "xl:col-span-4",
175
+ full: "col-span-full",
176
+ }
177
+
178
+ function renderFormBuilderField<TFieldValues extends FieldValues>(
179
+ field: FormBuilderField<TFieldValues>,
180
+ context: FormBuilderFieldRenderContext<TFieldValues>
181
+ ) {
182
+ const FormInputComponent = FormInput as unknown as React.ComponentType<Record<string, unknown>>
183
+ const FormSelectComponent = FormSelect as unknown as React.ComponentType<Record<string, unknown>>
184
+
185
+ switch (field.type) {
186
+ case "custom":
187
+ return field.render(context)
188
+ case "input":
189
+ return React.createElement(FormInputComponent, {
190
+ control: context.control,
191
+ disabled: context.disabled,
192
+ readOnly: context.readOnly,
193
+ ...(field.props as Omit<FormInputProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
194
+ })
195
+ case "textarea":
196
+ return <FormTextarea control={context.control} disabled={context.disabled} readOnly={context.readOnly} {...field.props} />
197
+ case "select":
198
+ return React.createElement(FormSelectComponent, {
199
+ control: context.control,
200
+ disabled: context.disabled,
201
+ ...(field.props as Omit<FormSelectProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
202
+ })
203
+ case "async-select":
204
+ return <FormAsyncSelect control={context.control} disabled={context.disabled} {...field.props} />
205
+ case "switch":
206
+ return <FormSwitch control={context.control} disabled={context.disabled} {...field.props} />
207
+ case "number":
208
+ return <FormNumberInput control={context.control} disabled={context.disabled} readOnly={context.readOnly} {...field.props} />
209
+ case "phone":
210
+ return <FormPhoneInput control={context.control} disabled={context.disabled} readOnly={context.readOnly} {...field.props} />
211
+ case "date":
212
+ return <FormDateInput control={context.control} disabled={context.disabled} readOnly={context.readOnly} {...field.props} />
213
+ case "date-range":
214
+ return (
215
+ <FormDateRangeInput
216
+ control={context.control}
217
+ fromInputProps={{ disabled: context.disabled, readOnly: context.readOnly }}
218
+ toInputProps={{ disabled: context.disabled, readOnly: context.readOnly }}
219
+ {...field.props}
220
+ />
221
+ )
222
+ default:
223
+ return null
224
+ }
225
+ }
226
+
227
+ function normalizeSections<TFieldValues extends FieldValues>({
228
+ fields,
229
+ sections,
230
+ }: Pick<FormBuilderProps<TFieldValues>, "fields" | "sections">): FormBuilderSection<TFieldValues>[] {
231
+ if (sections?.length) return sections
232
+ return [{ id: "default", fields: fields ?? [] }]
233
+ }
234
+
235
+ function FormBuilder<TFieldValues extends FieldValues>({
236
+ className,
237
+ control,
238
+ fields,
239
+ sections,
240
+ layout = "grid",
241
+ density = "default",
242
+ columns = 2,
243
+ disabled,
244
+ readOnly,
245
+ footer,
246
+ submitLabel,
247
+ resetLabel,
248
+ onResetClick,
249
+ isSubmitting,
250
+ sectionClassName,
251
+ fieldClassName,
252
+ footerClassName,
253
+ ...props
254
+ }: FormBuilderProps<TFieldValues>) {
255
+ const normalizedSections = normalizeSections({ fields, sections })
256
+ const context = React.useMemo(
257
+ () => ({ control, disabled, readOnly }),
258
+ [control, disabled, readOnly]
259
+ )
260
+
261
+ return (
262
+ <form data-slot="form-builder" className={cn("grid", densityClassName[density], className)} {...props}>
263
+ {normalizedSections
264
+ .filter((section) => !section.hidden)
265
+ .map((section) => (
266
+ <section
267
+ key={section.id}
268
+ data-slot="form-builder-section"
269
+ className={cn("grid gap-4", section.className, sectionClassName)}
270
+ >
271
+ {(section.title || section.description || section.actions) && (
272
+ <div
273
+ data-slot="form-builder-section-header"
274
+ className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"
275
+ >
276
+ <div className="min-w-0 space-y-1">
277
+ {section.title && <h2 className="text-base font-semibold leading-none tracking-tight">{section.title}</h2>}
278
+ {section.description && <p className="text-sm text-muted-foreground">{section.description}</p>}
279
+ </div>
280
+ {section.actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{section.actions}</div>}
281
+ </div>
282
+ )}
283
+
284
+ <div
285
+ data-slot="form-builder-fields"
286
+ data-layout={layout}
287
+ className={cn(
288
+ "grid",
289
+ densityClassName[density],
290
+ layout === "grid" ? columnsClassName[columns] : "grid-cols-1"
291
+ )}
292
+ >
293
+ {section.fields
294
+ .filter((field) => !field.hidden)
295
+ .map((field) => (
296
+ <div
297
+ key={field.id}
298
+ data-slot="form-builder-field"
299
+ className={cn(
300
+ field.colSpan && colSpanClassName[field.colSpan],
301
+ field.className,
302
+ fieldClassName
303
+ )}
304
+ >
305
+ {renderFormBuilderField(field, context)}
306
+ </div>
307
+ ))}
308
+ </div>
309
+ </section>
310
+ ))}
311
+
312
+ {(footer || submitLabel || resetLabel) && (
313
+ <div data-slot="form-builder-footer" className={cn("flex flex-wrap items-center justify-end gap-2", footerClassName)}>
314
+ {footer ?? (
315
+ <>
316
+ {resetLabel && (
317
+ <Button type="button" variant="outline" disabled={disabled || isSubmitting} onClick={onResetClick}>
318
+ {resetLabel}
319
+ </Button>
320
+ )}
321
+ {submitLabel && (
322
+ <Button type="submit" disabled={disabled || isSubmitting}>
323
+ {submitLabel}
324
+ </Button>
325
+ )}
326
+ </>
327
+ )}
328
+ </div>
329
+ )}
330
+ </form>
331
+ )
332
+ }
333
+
334
+ export { FormBuilder, renderFormBuilderField }
@@ -0,0 +1,12 @@
1
+ export * from './resource-page'
2
+ export * from './resource-detail-page'
3
+ export * from './form-builder'
4
+ export * from './form-builder-presets'
5
+ export * from './settings-section'
6
+ export * from './action-system'
7
+ export * from './status-system'
8
+ export * from './filter-builder'
9
+ export * from './data-view'
10
+ export * from './entity-details'
11
+ export * from './resource-system'
12
+ export * from './crud-system'
@@ -0,0 +1,4 @@
1
+ export * from "./resource-page"
2
+ export * from "./resource-detail-page"
3
+ export * from "./form-builder"
4
+ export * from "./form-builder-presets"
@@ -0,0 +1,160 @@
1
+ import * as React from "react"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+ import { DescriptionList, type DescriptionListItem } from "@/components/display/description-list"
5
+ import { PageHeader, type PageHeaderProps } from "@/components/layout/page-header"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ export type ResourceDetailPageDensity = "default" | "compact" | "comfortable"
9
+
10
+ export type ResourceDetailPageSection = {
11
+ id: string
12
+ title?: React.ReactNode
13
+ description?: React.ReactNode
14
+ actions?: React.ReactNode
15
+ hidden?: boolean
16
+ className?: string
17
+ bodyClassName?: string
18
+ items?: DescriptionListItem[]
19
+ children?: React.ReactNode
20
+ }
21
+
22
+ export type ResourceDetailPageProps = Omit<React.ComponentProps<"div">, "children"> & {
23
+ title?: React.ReactNode
24
+ description?: React.ReactNode
25
+ eyebrow?: React.ReactNode
26
+ actions?: React.ReactNode
27
+ breadcrumbs?: React.ReactNode
28
+ status?: React.ReactNode
29
+ summary?: React.ReactNode
30
+ meta?: React.ReactNode
31
+ sections?: ResourceDetailPageSection[]
32
+ children?: React.ReactNode
33
+ aside?: React.ReactNode
34
+ footer?: React.ReactNode
35
+ backLabel?: React.ReactNode
36
+ onBack?: () => void
37
+ headerProps?: Omit<PageHeaderProps, "title" | "description" | "actions" | "eyebrow">
38
+ density?: ResourceDetailPageDensity
39
+ pageHeaderClassName?: string
40
+ contentClassName?: string
41
+ asideClassName?: string
42
+ sectionClassName?: string
43
+ }
44
+
45
+ const densityClassName: Record<ResourceDetailPageDensity, string> = {
46
+ compact: "gap-3",
47
+ default: "gap-4",
48
+ comfortable: "gap-6",
49
+ }
50
+
51
+ function ResourceDetailPage({
52
+ className,
53
+ title,
54
+ description,
55
+ eyebrow,
56
+ actions,
57
+ breadcrumbs,
58
+ status,
59
+ summary,
60
+ meta,
61
+ sections,
62
+ children,
63
+ aside,
64
+ footer,
65
+ backLabel = "Back",
66
+ onBack,
67
+ headerProps,
68
+ density = "default",
69
+ pageHeaderClassName,
70
+ contentClassName,
71
+ asideClassName,
72
+ sectionClassName,
73
+ ...props
74
+ }: ResourceDetailPageProps) {
75
+ const visibleSections = sections?.filter((section) => !section.hidden) ?? []
76
+ const hasHeader = Boolean(title || description || eyebrow || actions || onBack)
77
+
78
+ return (
79
+ <div
80
+ data-slot="resource-detail-page"
81
+ data-density={density}
82
+ className={cn("grid min-w-0", densityClassName[density], className)}
83
+ {...props}
84
+ >
85
+ {breadcrumbs && <div data-slot="resource-detail-page-breadcrumbs">{breadcrumbs}</div>}
86
+
87
+ {hasHeader && (
88
+ <PageHeader
89
+ data-slot="resource-detail-page-header"
90
+ eyebrow={eyebrow}
91
+ title={title}
92
+ description={description}
93
+ actions={
94
+ <>
95
+ {onBack && (
96
+ <Button type="button" variant="outline" onClick={onBack}>
97
+ {backLabel}
98
+ </Button>
99
+ )}
100
+ {actions}
101
+ </>
102
+ }
103
+ className={pageHeaderClassName}
104
+ {...headerProps}
105
+ />
106
+ )}
107
+
108
+ {(status || summary || meta) && (
109
+ <div data-slot="resource-detail-page-summary" className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto]">
110
+ <div className="grid min-w-0 gap-3">
111
+ {status && <div data-slot="resource-detail-page-status">{status}</div>}
112
+ {summary && <div data-slot="resource-detail-page-summary-content">{summary}</div>}
113
+ </div>
114
+ {meta && <div data-slot="resource-detail-page-meta" className="min-w-0 lg:w-80">{meta}</div>}
115
+ </div>
116
+ )}
117
+
118
+ <div
119
+ data-slot="resource-detail-page-content"
120
+ className={cn("grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
121
+ >
122
+ <div data-slot="resource-detail-page-main" className="grid min-w-0 gap-4">
123
+ {children}
124
+
125
+ {visibleSections.map((section) => (
126
+ <section
127
+ key={section.id}
128
+ data-slot="resource-detail-page-section"
129
+ className={cn("rounded-xl border bg-card text-card-foreground shadow-sm", section.className, sectionClassName)}
130
+ >
131
+ {(section.title || section.description || section.actions) && (
132
+ <div className="flex flex-col gap-3 border-b p-4 sm:flex-row sm:items-start sm:justify-between">
133
+ <div className="min-w-0 space-y-1">
134
+ {section.title && <h2 className="text-base font-semibold leading-none tracking-tight">{section.title}</h2>}
135
+ {section.description && <p className="text-sm text-muted-foreground">{section.description}</p>}
136
+ </div>
137
+ {section.actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{section.actions}</div>}
138
+ </div>
139
+ )}
140
+
141
+ <div className={cn("p-4", section.bodyClassName)}>
142
+ {section.children ?? (section.items ? <DescriptionList items={section.items} /> : null)}
143
+ </div>
144
+ </section>
145
+ ))}
146
+ </div>
147
+
148
+ {aside && (
149
+ <aside data-slot="resource-detail-page-aside" className={cn("min-w-0", asideClassName)}>
150
+ {aside}
151
+ </aside>
152
+ )}
153
+ </div>
154
+
155
+ {footer && <div data-slot="resource-detail-page-footer">{footer}</div>}
156
+ </div>
157
+ )
158
+ }
159
+
160
+ export { ResourceDetailPage }
@@ -0,0 +1,159 @@
1
+ import * as React from "react"
2
+
3
+ import { DataTable, type DataTableProps } from "@/components/data-table/data-table"
4
+ import { PageHeader, type PageHeaderProps } from "@/components/layout/page-header"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type ResourcePageDensity = "default" | "compact" | "comfortable"
8
+
9
+ export type ResourcePageSectionProps = React.ComponentProps<"section"> & {
10
+ title?: React.ReactNode
11
+ description?: React.ReactNode
12
+ actions?: React.ReactNode
13
+ headerClassName?: string
14
+ bodyClassName?: string
15
+ }
16
+
17
+ function ResourcePageSection({
18
+ className,
19
+ title,
20
+ description,
21
+ actions,
22
+ headerClassName,
23
+ bodyClassName,
24
+ children,
25
+ ...props
26
+ }: ResourcePageSectionProps) {
27
+ return (
28
+ <section
29
+ data-slot="resource-page-section"
30
+ className={cn("rounded-xl border bg-card text-card-foreground shadow-sm", className)}
31
+ {...props}
32
+ >
33
+ {(title || description || actions) && (
34
+ <div
35
+ data-slot="resource-page-section-header"
36
+ className={cn("flex flex-col gap-3 border-b p-4 sm:flex-row sm:items-start sm:justify-between", headerClassName)}
37
+ >
38
+ <div className="min-w-0 space-y-1">
39
+ {title && <h2 className="text-base font-semibold leading-none tracking-tight">{title}</h2>}
40
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
41
+ </div>
42
+ {actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>}
43
+ </div>
44
+ )}
45
+
46
+ <div data-slot="resource-page-section-body" className={cn("p-4", bodyClassName)}>
47
+ {children}
48
+ </div>
49
+ </section>
50
+ )
51
+ }
52
+
53
+ export type ResourcePageProps<TData, TValue = unknown> = Omit<React.ComponentProps<"div">, "children"> & {
54
+ title?: React.ReactNode
55
+ description?: React.ReactNode
56
+ eyebrow?: React.ReactNode
57
+ actions?: React.ReactNode
58
+ breadcrumbs?: React.ReactNode
59
+ stats?: React.ReactNode
60
+ filters?: React.ReactNode
61
+ tabs?: React.ReactNode
62
+ table?: DataTableProps<TData, TValue>
63
+ children?: React.ReactNode
64
+ aside?: React.ReactNode
65
+ footer?: React.ReactNode
66
+ headerProps?: Omit<PageHeaderProps, "title" | "description" | "actions" | "eyebrow">
67
+ density?: ResourcePageDensity
68
+ pageHeaderClassName?: string
69
+ toolbarClassName?: string
70
+ contentClassName?: string
71
+ asideClassName?: string
72
+ }
73
+
74
+ const densityClassName: Record<ResourcePageDensity, string> = {
75
+ compact: "gap-3",
76
+ default: "gap-4",
77
+ comfortable: "gap-6",
78
+ }
79
+
80
+ function ResourcePage<TData, TValue = unknown>({
81
+ className,
82
+ title,
83
+ description,
84
+ eyebrow,
85
+ actions,
86
+ breadcrumbs,
87
+ stats,
88
+ filters,
89
+ tabs,
90
+ table,
91
+ children,
92
+ aside,
93
+ footer,
94
+ headerProps,
95
+ density = "default",
96
+ pageHeaderClassName,
97
+ toolbarClassName,
98
+ contentClassName,
99
+ asideClassName,
100
+ ...props
101
+ }: ResourcePageProps<TData, TValue>) {
102
+ const hasHeader = Boolean(title || description || eyebrow || actions)
103
+ const hasToolbar = Boolean(breadcrumbs || filters || tabs)
104
+ const hasMainContent = Boolean(children || table)
105
+
106
+ return (
107
+ <div
108
+ data-slot="resource-page"
109
+ data-density={density}
110
+ className={cn("grid min-w-0", densityClassName[density], className)}
111
+ {...props}
112
+ >
113
+ {breadcrumbs && <div data-slot="resource-page-breadcrumbs">{breadcrumbs}</div>}
114
+
115
+ {hasHeader && (
116
+ <PageHeader
117
+ data-slot="resource-page-header"
118
+ eyebrow={eyebrow}
119
+ title={title}
120
+ description={description}
121
+ actions={actions}
122
+ className={pageHeaderClassName}
123
+ {...headerProps}
124
+ />
125
+ )}
126
+
127
+ {stats && <div data-slot="resource-page-stats">{stats}</div>}
128
+
129
+ {hasToolbar && (
130
+ <div data-slot="resource-page-toolbar" className={cn("grid gap-3", toolbarClassName)}>
131
+ {tabs && <div data-slot="resource-page-tabs">{tabs}</div>}
132
+ {filters && <div data-slot="resource-page-filters">{filters}</div>}
133
+ </div>
134
+ )}
135
+
136
+ {(hasMainContent || aside) && (
137
+ <div
138
+ data-slot="resource-page-content"
139
+ className={cn("grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
140
+ >
141
+ <div data-slot="resource-page-main" className="grid min-w-0 gap-4">
142
+ {children}
143
+ {table && <DataTable {...table} />}
144
+ </div>
145
+
146
+ {aside && (
147
+ <aside data-slot="resource-page-aside" className={cn("min-w-0", asideClassName)}>
148
+ {aside}
149
+ </aside>
150
+ )}
151
+ </div>
152
+ )}
153
+
154
+ {footer && <div data-slot="resource-page-footer">{footer}</div>}
155
+ </div>
156
+ )
157
+ }
158
+
159
+ export { ResourcePage, ResourcePageSection }