@questpie/admin 0.0.1 → 1.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 (250) hide show
  1. package/README.md +439 -424
  2. package/dist/auth-layout-M8K8_q5R.mjs +181 -0
  3. package/dist/auth-layout-M8K8_q5R.mjs.map +1 -0
  4. package/dist/bulk-upload-dialog-h7zXD78Y.mjs +274 -0
  5. package/dist/bulk-upload-dialog-h7zXD78Y.mjs.map +1 -0
  6. package/dist/{components/ui/card.mjs → card-BKHjBQfw.mjs} +8 -8
  7. package/dist/card-BKHjBQfw.mjs.map +1 -0
  8. package/dist/client/styles/index.css +434 -0
  9. package/dist/client-BCGpkAz6.mjs +22635 -0
  10. package/dist/client-BCGpkAz6.mjs.map +1 -0
  11. package/dist/client-CcWZbkBP.d.mts +13585 -0
  12. package/dist/client-CcWZbkBP.d.mts.map +1 -0
  13. package/dist/client.d.mts +3 -0
  14. package/dist/client.mjs +14 -0
  15. package/dist/content-locales-provider-BXvuIgfg.mjs +1650 -0
  16. package/dist/content-locales-provider-BXvuIgfg.mjs.map +1 -0
  17. package/dist/dashboard-page-B4PGEdc2.mjs +2500 -0
  18. package/dist/dashboard-page-B4PGEdc2.mjs.map +1 -0
  19. package/dist/dashboard-page-CVlyR40m.mjs +6 -0
  20. package/dist/dropzone-Do3awXKd.mjs +634 -0
  21. package/dist/dropzone-Do3awXKd.mjs.map +1 -0
  22. package/dist/{views/auth/forgot-password-form.mjs → forgot-password-page-Bcp-An4Y.mjs} +87 -14
  23. package/dist/forgot-password-page-Bcp-An4Y.mjs.map +1 -0
  24. package/dist/forgot-password-page-CIILVhfo.mjs +7 -0
  25. package/dist/index-B9Xwk4hi.d.mts +2753 -0
  26. package/dist/index-B9Xwk4hi.d.mts.map +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +14 -0
  29. package/dist/login-page-8K7fo0qK.mjs +7 -0
  30. package/dist/login-page-CP4gA-dl.mjs +298 -0
  31. package/dist/login-page-CP4gA-dl.mjs.map +1 -0
  32. package/dist/preview-utils-BKQ9-TMa.mjs +65 -0
  33. package/dist/preview-utils-BKQ9-TMa.mjs.map +1 -0
  34. package/dist/{views/auth/reset-password-form.mjs → reset-password-page-BqfDmLxA.mjs} +111 -14
  35. package/dist/reset-password-page-BqfDmLxA.mjs.map +1 -0
  36. package/dist/reset-password-page-DLATv0xQ.mjs +7 -0
  37. package/dist/runtime-6VZM878K.mjs +69 -0
  38. package/dist/runtime-6VZM878K.mjs.map +1 -0
  39. package/dist/saved-views.types-BMsz5mCy.d.mts +42 -0
  40. package/dist/saved-views.types-BMsz5mCy.d.mts.map +1 -0
  41. package/dist/server.d.mts +250 -0
  42. package/dist/server.d.mts.map +1 -0
  43. package/dist/server.mjs +832 -0
  44. package/dist/server.mjs.map +1 -0
  45. package/dist/setup-page-CMZ5P_OE.mjs +6 -0
  46. package/dist/setup-page-YAP_fzqh.mjs +264 -0
  47. package/dist/setup-page-YAP_fzqh.mjs.map +1 -0
  48. package/dist/shared.d.mts +57 -0
  49. package/dist/shared.d.mts.map +1 -0
  50. package/dist/shared.mjs +3 -0
  51. package/dist/{hooks/use-auth.mjs → use-auth-BoLmWtmU.mjs} +42 -30
  52. package/dist/use-auth-BoLmWtmU.mjs.map +1 -0
  53. package/package.json +48 -197
  54. package/.turbo/turbo-build.log +0 -108
  55. package/CHANGELOG.md +0 -10
  56. package/STATUS.md +0 -917
  57. package/VALIDATION.md +0 -602
  58. package/components.json +0 -24
  59. package/dist/__tests__/setup.mjs +0 -38
  60. package/dist/__tests__/test-utils.mjs +0 -45
  61. package/dist/__tests__/vitest.d.mjs +0 -3
  62. package/dist/components/admin-app.mjs +0 -69
  63. package/dist/components/fields/array-field.mjs +0 -190
  64. package/dist/components/fields/checkbox-field.mjs +0 -34
  65. package/dist/components/fields/custom-field.mjs +0 -32
  66. package/dist/components/fields/date-field.mjs +0 -41
  67. package/dist/components/fields/datetime-field.mjs +0 -42
  68. package/dist/components/fields/email-field.mjs +0 -37
  69. package/dist/components/fields/embedded-collection.mjs +0 -253
  70. package/dist/components/fields/field-types.mjs +0 -1
  71. package/dist/components/fields/field-utils.mjs +0 -10
  72. package/dist/components/fields/field-wrapper.mjs +0 -34
  73. package/dist/components/fields/index.mjs +0 -23
  74. package/dist/components/fields/json-field.mjs +0 -243
  75. package/dist/components/fields/locale-badge.mjs +0 -16
  76. package/dist/components/fields/number-field.mjs +0 -39
  77. package/dist/components/fields/password-field.mjs +0 -37
  78. package/dist/components/fields/relation-field.mjs +0 -104
  79. package/dist/components/fields/relation-picker.mjs +0 -229
  80. package/dist/components/fields/relation-select.mjs +0 -188
  81. package/dist/components/fields/rich-text-editor/index.mjs +0 -897
  82. package/dist/components/fields/select-field.mjs +0 -41
  83. package/dist/components/fields/switch-field.mjs +0 -34
  84. package/dist/components/fields/text-field.mjs +0 -38
  85. package/dist/components/fields/textarea-field.mjs +0 -38
  86. package/dist/components/index.mjs +0 -59
  87. package/dist/components/primitives/checkbox-input.mjs +0 -127
  88. package/dist/components/primitives/date-input.mjs +0 -303
  89. package/dist/components/primitives/index.mjs +0 -12
  90. package/dist/components/primitives/number-input.mjs +0 -104
  91. package/dist/components/primitives/select-input.mjs +0 -177
  92. package/dist/components/primitives/tag-input.mjs +0 -135
  93. package/dist/components/primitives/text-input.mjs +0 -39
  94. package/dist/components/primitives/textarea-input.mjs +0 -37
  95. package/dist/components/primitives/toggle-input.mjs +0 -31
  96. package/dist/components/primitives/types.mjs +0 -12
  97. package/dist/components/ui/accordion.mjs +0 -55
  98. package/dist/components/ui/avatar.mjs +0 -54
  99. package/dist/components/ui/badge.mjs +0 -34
  100. package/dist/components/ui/button.mjs +0 -48
  101. package/dist/components/ui/checkbox.mjs +0 -21
  102. package/dist/components/ui/combobox.mjs +0 -163
  103. package/dist/components/ui/dialog.mjs +0 -95
  104. package/dist/components/ui/dropdown-menu.mjs +0 -138
  105. package/dist/components/ui/field.mjs +0 -113
  106. package/dist/components/ui/input-group.mjs +0 -82
  107. package/dist/components/ui/input.mjs +0 -17
  108. package/dist/components/ui/label.mjs +0 -15
  109. package/dist/components/ui/popover.mjs +0 -56
  110. package/dist/components/ui/scroll-area.mjs +0 -38
  111. package/dist/components/ui/select.mjs +0 -100
  112. package/dist/components/ui/separator.mjs +0 -16
  113. package/dist/components/ui/sheet.mjs +0 -90
  114. package/dist/components/ui/sidebar.mjs +0 -387
  115. package/dist/components/ui/skeleton.mjs +0 -14
  116. package/dist/components/ui/spinner.mjs +0 -16
  117. package/dist/components/ui/switch.mjs +0 -22
  118. package/dist/components/ui/table.mjs +0 -68
  119. package/dist/components/ui/tabs.mjs +0 -48
  120. package/dist/components/ui/textarea.mjs +0 -15
  121. package/dist/components/ui/tooltip.mjs +0 -44
  122. package/dist/config/component-registry.mjs +0 -38
  123. package/dist/config/index.mjs +0 -129
  124. package/dist/hooks/admin-provider.mjs +0 -70
  125. package/dist/hooks/index.mjs +0 -7
  126. package/dist/hooks/store.mjs +0 -178
  127. package/dist/hooks/use-collection-db.mjs +0 -146
  128. package/dist/hooks/use-collection.mjs +0 -112
  129. package/dist/hooks/use-global.mjs +0 -46
  130. package/dist/hooks/use-mobile.mjs +0 -20
  131. package/dist/lib/utils.mjs +0 -10
  132. package/dist/styles/index.css +0 -336
  133. package/dist/styles/index.mjs +0 -1
  134. package/dist/utils/index.mjs +0 -9
  135. package/dist/views/auth/auth-layout.mjs +0 -52
  136. package/dist/views/auth/index.mjs +0 -6
  137. package/dist/views/auth/login-form.mjs +0 -156
  138. package/dist/views/collection/auto-form-fields.mjs +0 -525
  139. package/dist/views/collection/collection-form.mjs +0 -91
  140. package/dist/views/collection/collection-list.mjs +0 -76
  141. package/dist/views/collection/form-field.mjs +0 -42
  142. package/dist/views/collection/index.mjs +0 -6
  143. package/dist/views/common/index.mjs +0 -4
  144. package/dist/views/common/locale-switcher.mjs +0 -39
  145. package/dist/views/common/version-history.mjs +0 -272
  146. package/dist/views/index.mjs +0 -9
  147. package/dist/views/layout/admin-layout.mjs +0 -40
  148. package/dist/views/layout/admin-router.mjs +0 -95
  149. package/dist/views/layout/admin-sidebar.mjs +0 -63
  150. package/dist/views/layout/index.mjs +0 -5
  151. package/src/__tests__/setup.ts +0 -44
  152. package/src/__tests__/test-utils.tsx +0 -49
  153. package/src/__tests__/vitest.d.ts +0 -9
  154. package/src/components/admin-app.tsx +0 -221
  155. package/src/components/fields/array-field.tsx +0 -237
  156. package/src/components/fields/checkbox-field.tsx +0 -47
  157. package/src/components/fields/custom-field.tsx +0 -50
  158. package/src/components/fields/date-field.tsx +0 -65
  159. package/src/components/fields/datetime-field.tsx +0 -67
  160. package/src/components/fields/email-field.tsx +0 -51
  161. package/src/components/fields/embedded-collection.tsx +0 -315
  162. package/src/components/fields/field-types.ts +0 -162
  163. package/src/components/fields/field-utils.ts +0 -6
  164. package/src/components/fields/field-wrapper.tsx +0 -52
  165. package/src/components/fields/index.ts +0 -66
  166. package/src/components/fields/json-field.tsx +0 -440
  167. package/src/components/fields/locale-badge.tsx +0 -15
  168. package/src/components/fields/number-field.tsx +0 -57
  169. package/src/components/fields/password-field.tsx +0 -51
  170. package/src/components/fields/relation-field.tsx +0 -243
  171. package/src/components/fields/relation-picker.tsx +0 -402
  172. package/src/components/fields/relation-select.tsx +0 -327
  173. package/src/components/fields/rich-text-editor/index.tsx +0 -1337
  174. package/src/components/fields/select-field.tsx +0 -61
  175. package/src/components/fields/switch-field.tsx +0 -47
  176. package/src/components/fields/text-field.tsx +0 -55
  177. package/src/components/fields/textarea-field.tsx +0 -55
  178. package/src/components/index.ts +0 -40
  179. package/src/components/primitives/checkbox-input.tsx +0 -193
  180. package/src/components/primitives/date-input.tsx +0 -401
  181. package/src/components/primitives/index.ts +0 -24
  182. package/src/components/primitives/number-input.tsx +0 -132
  183. package/src/components/primitives/select-input.tsx +0 -296
  184. package/src/components/primitives/tag-input.tsx +0 -200
  185. package/src/components/primitives/text-input.tsx +0 -49
  186. package/src/components/primitives/textarea-input.tsx +0 -46
  187. package/src/components/primitives/toggle-input.tsx +0 -36
  188. package/src/components/primitives/types.ts +0 -235
  189. package/src/components/ui/accordion.tsx +0 -72
  190. package/src/components/ui/avatar.tsx +0 -106
  191. package/src/components/ui/badge.tsx +0 -48
  192. package/src/components/ui/button.tsx +0 -53
  193. package/src/components/ui/card.tsx +0 -94
  194. package/src/components/ui/checkbox.tsx +0 -27
  195. package/src/components/ui/combobox.tsx +0 -290
  196. package/src/components/ui/dialog.tsx +0 -151
  197. package/src/components/ui/dropdown-menu.tsx +0 -254
  198. package/src/components/ui/field.tsx +0 -227
  199. package/src/components/ui/input-group.tsx +0 -149
  200. package/src/components/ui/input.tsx +0 -20
  201. package/src/components/ui/label.tsx +0 -18
  202. package/src/components/ui/popover.tsx +0 -88
  203. package/src/components/ui/scroll-area.tsx +0 -53
  204. package/src/components/ui/select.tsx +0 -192
  205. package/src/components/ui/separator.tsx +0 -23
  206. package/src/components/ui/sheet.tsx +0 -127
  207. package/src/components/ui/sidebar.tsx +0 -723
  208. package/src/components/ui/skeleton.tsx +0 -13
  209. package/src/components/ui/spinner.tsx +0 -10
  210. package/src/components/ui/switch.tsx +0 -32
  211. package/src/components/ui/table.tsx +0 -99
  212. package/src/components/ui/tabs.tsx +0 -82
  213. package/src/components/ui/textarea.tsx +0 -18
  214. package/src/components/ui/tooltip.tsx +0 -70
  215. package/src/config/component-registry.ts +0 -190
  216. package/src/config/index.ts +0 -1099
  217. package/src/hooks/README.md +0 -269
  218. package/src/hooks/admin-provider.tsx +0 -110
  219. package/src/hooks/index.ts +0 -41
  220. package/src/hooks/store.ts +0 -248
  221. package/src/hooks/use-auth.ts +0 -168
  222. package/src/hooks/use-collection-db.ts +0 -209
  223. package/src/hooks/use-collection.ts +0 -156
  224. package/src/hooks/use-global.ts +0 -69
  225. package/src/hooks/use-mobile.ts +0 -21
  226. package/src/lib/utils.ts +0 -6
  227. package/src/styles/index.css +0 -340
  228. package/src/utils/index.ts +0 -6
  229. package/src/views/auth/auth-layout.tsx +0 -77
  230. package/src/views/auth/forgot-password-form.tsx +0 -192
  231. package/src/views/auth/index.ts +0 -21
  232. package/src/views/auth/login-form.tsx +0 -229
  233. package/src/views/auth/reset-password-form.tsx +0 -232
  234. package/src/views/collection/auto-form-fields.tsx +0 -982
  235. package/src/views/collection/collection-form.tsx +0 -186
  236. package/src/views/collection/collection-list.tsx +0 -223
  237. package/src/views/collection/form-field.tsx +0 -52
  238. package/src/views/collection/index.ts +0 -15
  239. package/src/views/common/index.ts +0 -8
  240. package/src/views/common/locale-switcher.tsx +0 -45
  241. package/src/views/common/version-history.tsx +0 -406
  242. package/src/views/index.ts +0 -25
  243. package/src/views/layout/admin-layout.tsx +0 -117
  244. package/src/views/layout/admin-router.tsx +0 -206
  245. package/src/views/layout/admin-sidebar.tsx +0 -185
  246. package/src/views/layout/index.ts +0 -12
  247. package/tsconfig.json +0 -13
  248. package/tsconfig.tsbuildinfo +0 -1
  249. package/tsdown.config.ts +0 -13
  250. package/vitest.config.ts +0 -29
@@ -1,9 +0,0 @@
1
- import "vitest";
2
- import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
3
-
4
- declare module "vitest" {
5
- interface Assertion<T = any>
6
- extends TestingLibraryMatchers<typeof expect.stringContaining, T> {}
7
- interface AsymmetricMatchersContaining
8
- extends TestingLibraryMatchers<unknown, unknown> {}
9
- }
@@ -1,221 +0,0 @@
1
- /**
2
- * AdminApp Component
3
- *
4
- * MAIN ENTRY POINT - Mount this and you have a complete admin!
5
- *
6
- * Minimal usage:
7
- * <AdminApp client={cmsClient} />
8
- *
9
- * Everything else is OPTIONAL customization.
10
- */
11
-
12
- import * as React from "react";
13
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
14
- import type { Questpie } from "questpie";
15
- import type { QuestpieClient } from "questpie/client";
16
- import { AdminProvider } from "../hooks/admin-provider";
17
- import { AdminLayout } from "../views/layout/admin-layout";
18
- import { AdminRouter } from "../views/layout/admin-router";
19
- import type { ComponentRegistry } from "../config/component-registry";
20
- import { mergeComponentRegistry } from "../config/component-registry";
21
-
22
- export interface AdminAppProps<T extends Questpie<any>> {
23
- /**
24
- * CMS client - THE ONLY REQUIRED PROP!
25
- */
26
- client: QuestpieClient<T>;
27
-
28
- /**
29
- * Admin config (OPTIONAL)
30
- * If not provided, auto-generates from CMS schema
31
- */
32
- config?: {
33
- app?: {
34
- brand?: {
35
- name?: string;
36
- logo?: React.ComponentType;
37
- homeRoute?: string;
38
- };
39
- locales?: {
40
- default: string;
41
- available: string[];
42
- };
43
- header?: {
44
- show?: boolean;
45
- component?: React.ComponentType;
46
- };
47
- footer?: {
48
- show?: boolean;
49
- component?: React.ComponentType;
50
- };
51
- debug?: {
52
- showQueryDevtools?: boolean;
53
- showRouterDevtools?: boolean;
54
- };
55
- };
56
- collections?: Record<string, any>;
57
- globals?: Record<string, any>;
58
- pages?: Record<string, any>;
59
- };
60
-
61
- /**
62
- * Component registry (OPTIONAL)
63
- * Custom field components, layouts, etc.
64
- */
65
- registry?: Partial<ComponentRegistry>;
66
-
67
- /**
68
- * QueryClient instance (OPTIONAL)
69
- * If not provided, creates default
70
- */
71
- queryClient?: QueryClient;
72
-
73
- /**
74
- * Router integration (framework-specific)
75
- */
76
- router: {
77
- /**
78
- * Link component from your router
79
- * Example: from TanStack Router, Next.js, React Router
80
- */
81
- LinkComponent: React.ComponentType<{
82
- to: string;
83
- className?: string;
84
- children: React.ReactNode;
85
- activeProps?: { className?: string };
86
- }>;
87
-
88
- /**
89
- * Current route path
90
- */
91
- currentPath: string;
92
-
93
- /**
94
- * Route segments after /admin
95
- * Example: for /admin/barbers/123 -> ["barbers", "123"]
96
- */
97
- segments: string[];
98
-
99
- /**
100
- * Navigate function
101
- */
102
- navigate: (path: string) => void;
103
- };
104
-
105
- /**
106
- * Base path for admin routes (OPTIONAL)
107
- * Default: "/admin"
108
- */
109
- basePath?: string;
110
-
111
- /**
112
- * Custom dashboard component (OPTIONAL)
113
- */
114
- DashboardComponent?: React.ComponentType;
115
-
116
- /**
117
- * Custom collection components (OPTIONAL)
118
- */
119
- collectionComponents?: Record<
120
- string,
121
- {
122
- List?: React.ComponentType;
123
- Form?: React.ComponentType;
124
- }
125
- >;
126
-
127
- /**
128
- * Render custom form fields (OPTIONAL - will be replaced by AutoFormFields)
129
- */
130
- renderFormFields?: (collection: string) => React.ReactNode;
131
- }
132
-
133
- /**
134
- * Default QueryClient configuration
135
- */
136
- function createDefaultQueryClient() {
137
- return new QueryClient({
138
- defaultOptions: {
139
- queries: {
140
- staleTime: 60 * 1000, // 1 minute
141
- refetchOnWindowFocus: false,
142
- },
143
- },
144
- });
145
- }
146
-
147
- /**
148
- * AdminApp Component
149
- *
150
- * ONE COMPONENT TO RULE THEM ALL!
151
- */
152
- export function AdminApp<T extends Questpie<any>>({
153
- client,
154
- config,
155
- registry,
156
- queryClient,
157
- router,
158
- basePath = "/admin",
159
- DashboardComponent,
160
- collectionComponents,
161
- renderFormFields,
162
- }: AdminAppProps<T>): React.ReactElement {
163
- // Create or use provided QueryClient
164
- const [defaultQueryClient] = React.useState(
165
- () => queryClient ?? createDefaultQueryClient(),
166
- );
167
-
168
- // Merge component registry
169
- const mergedRegistry = React.useMemo(
170
- () => mergeComponentRegistry(registry),
171
- [registry],
172
- );
173
-
174
- // Auto-generate config from CMS if not provided
175
- const finalConfig = React.useMemo(() => {
176
- if (config) return config;
177
-
178
- // TODO: Auto-generate from CMS schema
179
- // For now, return minimal config
180
- return {
181
- app: {
182
- brand: {
183
- name: "Admin",
184
- },
185
- },
186
- collections: {},
187
- };
188
- }, [config]);
189
-
190
- return (
191
- <QueryClientProvider client={defaultQueryClient}>
192
- <AdminProvider
193
- client={client}
194
- queryClient={defaultQueryClient}
195
- locales={finalConfig.app?.locales}
196
- >
197
- <AdminLayout
198
- config={finalConfig}
199
- activeRoute={router.currentPath}
200
- LinkComponent={router.LinkComponent}
201
- >
202
- <AdminRouter
203
- config={finalConfig}
204
- segments={router.segments}
205
- navigate={router.navigate}
206
- DashboardComponent={DashboardComponent}
207
- collectionComponents={collectionComponents}
208
- renderFormFields={renderFormFields}
209
- registry={mergedRegistry}
210
- />
211
- </AdminLayout>
212
- </AdminProvider>
213
-
214
- {/* Optional: Query Devtools */}
215
- {config?.app?.debug?.showQueryDevtools && (
216
- // TODO: Add ReactQueryDevtools
217
- <div />
218
- )}
219
- </QueryClientProvider>
220
- );
221
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * ArrayField Component
3
- *
4
- * Handles arrays of primitive values with optional ordering.
5
- */
6
-
7
- import * as React from "react";
8
- import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
9
- import { CaretDown, CaretUp, Plus, Trash } from "@phosphor-icons/react";
10
- import { Button } from "../ui/button";
11
- import { Input } from "../ui/input";
12
- import { Textarea } from "../ui/textarea";
13
- import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
14
- import { LocaleBadge } from "./locale-badge";
15
- import type { FieldComponentProps } from "../../config/component-registry";
16
-
17
- export type ArrayFieldItemType =
18
- | "text"
19
- | "number"
20
- | "email"
21
- | "textarea"
22
- | "select";
23
-
24
- export interface ArrayFieldProps extends FieldComponentProps<any[]> {
25
- itemType?: ArrayFieldItemType;
26
- options?: Array<{ label: string; value: any }>;
27
- orderable?: boolean;
28
- minItems?: number;
29
- maxItems?: number;
30
- }
31
-
32
- export function ArrayField({
33
- name,
34
- value,
35
- label,
36
- description,
37
- placeholder,
38
- required,
39
- disabled,
40
- readOnly,
41
- error,
42
- localized,
43
- locale,
44
- itemType = "text",
45
- options,
46
- orderable = false,
47
- minItems,
48
- maxItems,
49
- }: ArrayFieldProps) {
50
- const form = useFormContext();
51
- const { control } = form;
52
- const { fields, append, remove, move } = useFieldArray({ control, name });
53
- const values = (useWatch({ control, name }) as any[] | undefined) ?? value;
54
-
55
- const canAddMore = !maxItems || fields.length < maxItems;
56
- const canRemove = !readOnly && (!minItems || fields.length > minItems);
57
-
58
- const createEmptyItem = React.useCallback(() => {
59
- if (itemType === "number") return undefined;
60
- if (itemType === "select") {
61
- return options?.[0]?.value ?? "";
62
- }
63
- return "";
64
- }, [itemType, options]);
65
-
66
- const handleAdd = () => {
67
- if (disabled || readOnly || !canAddMore) return;
68
- append(createEmptyItem());
69
- };
70
-
71
- const handleRemove = (index: number) => {
72
- if (!canRemove) return;
73
- remove(index);
74
- };
75
-
76
- const handleMove = (from: number, to: number) => {
77
- if (to < 0 || to >= fields.length) return;
78
- move(from, to);
79
- };
80
-
81
- const renderItemInput = (index: number) => {
82
- const itemName = `${name}.${index}`;
83
- const itemValue = values?.[index];
84
- const itemPlaceholder = placeholder || `Item ${index + 1}`;
85
-
86
- if (itemType === "textarea") {
87
- return (
88
- <Textarea
89
- id={itemName}
90
- placeholder={itemPlaceholder}
91
- disabled={disabled || readOnly}
92
- defaultValue={itemValue ?? ""}
93
- {...form.register(itemName)}
94
- />
95
- );
96
- }
97
-
98
- if (itemType === "select") {
99
- return (
100
- <Select
101
- value={itemValue ?? ""}
102
- onValueChange={(value) =>
103
- form.setValue(itemName, value, {
104
- shouldDirty: true,
105
- shouldTouch: true,
106
- })
107
- }
108
- disabled={disabled || readOnly}
109
- >
110
- <SelectTrigger id={itemName}>
111
- <span className="truncate">
112
- {options?.find((o) => o.value === itemValue)?.label ||
113
- itemPlaceholder}
114
- </span>
115
- </SelectTrigger>
116
- <SelectContent>
117
- {options?.map((option) => (
118
- <SelectItem key={option.value} value={option.value}>
119
- {option.label}
120
- </SelectItem>
121
- ))}
122
- </SelectContent>
123
- </Select>
124
- );
125
- }
126
-
127
- return (
128
- <Input
129
- id={itemName}
130
- type={itemType === "email" ? "email" : itemType}
131
- placeholder={itemPlaceholder}
132
- disabled={disabled || readOnly}
133
- defaultValue={itemValue ?? ""}
134
- {...form.register(
135
- itemName,
136
- itemType === "number" ? { valueAsNumber: true } : undefined,
137
- )}
138
- />
139
- );
140
- };
141
-
142
- return (
143
- <div className="space-y-2">
144
- {label && (
145
- <div className="flex items-center gap-2">
146
- <label htmlFor={name} className="text-sm font-medium">
147
- {label}
148
- {required && <span className="text-destructive">*</span>}
149
- {maxItems && (
150
- <span className="ml-2 text-xs text-muted-foreground">
151
- ({fields.length}/{maxItems})
152
- </span>
153
- )}
154
- </label>
155
- {localized && <LocaleBadge locale={locale || "i18n"} />}
156
- </div>
157
- )}
158
- {description && (
159
- <p className="text-sm text-muted-foreground">{description}</p>
160
- )}
161
-
162
- <div className="space-y-2">
163
- {fields.length === 0 ? (
164
- <div className="rounded-lg border border-dashed p-4 text-center">
165
- <p className="text-sm text-muted-foreground">
166
- {placeholder || `No ${label || "items"} added yet`}
167
- </p>
168
- </div>
169
- ) : (
170
- fields.map((field, index) => {
171
- const canMoveUp = orderable && index > 0;
172
- const canMoveDown = orderable && index < fields.length - 1;
173
-
174
- return (
175
- <div key={field.id} className="flex items-start gap-2">
176
- <div className="flex-1">{renderItemInput(index)}</div>
177
- {orderable && !readOnly && (
178
- <div className="flex flex-col gap-1">
179
- <Button
180
- type="button"
181
- variant="ghost"
182
- size="icon"
183
- className="h-6 w-6"
184
- onClick={() => handleMove(index, index - 1)}
185
- disabled={!canMoveUp || disabled}
186
- title="Move up"
187
- >
188
- <CaretUp className="h-3 w-3" />
189
- </Button>
190
- <Button
191
- type="button"
192
- variant="ghost"
193
- size="icon"
194
- className="h-6 w-6"
195
- onClick={() => handleMove(index, index + 1)}
196
- disabled={!canMoveDown || disabled}
197
- title="Move down"
198
- >
199
- <CaretDown className="h-3 w-3" />
200
- </Button>
201
- </div>
202
- )}
203
- {!readOnly && canRemove && (
204
- <Button
205
- type="button"
206
- variant="ghost"
207
- size="icon"
208
- className="h-6 w-6"
209
- onClick={() => handleRemove(index)}
210
- disabled={disabled}
211
- title="Remove"
212
- >
213
- <Trash className="h-3 w-3" />
214
- </Button>
215
- )}
216
- </div>
217
- );
218
- })
219
- )}
220
- </div>
221
-
222
- {!readOnly && canAddMore && (
223
- <Button
224
- type="button"
225
- variant="outline"
226
- onClick={handleAdd}
227
- disabled={disabled}
228
- >
229
- <Plus className="h-4 w-4" />
230
- Add {label || "item"}
231
- </Button>
232
- )}
233
-
234
- {error && <p className="text-sm text-destructive">{error}</p>}
235
- </div>
236
- );
237
- }
@@ -1,47 +0,0 @@
1
- import { Controller } from "react-hook-form";
2
- import { CheckboxInput } from "../primitives/checkbox-input";
3
- import { FieldWrapper } from "./field-wrapper";
4
- import { useResolvedControl } from "./field-utils";
5
- import type { BooleanFieldProps } from "./field-types";
6
-
7
- export function CheckboxField({
8
- name,
9
- label,
10
- description,
11
- required,
12
- disabled,
13
- localized,
14
- locale,
15
- control,
16
- className,
17
- }: BooleanFieldProps) {
18
- const resolvedControl = useResolvedControl(control);
19
-
20
- return (
21
- <Controller
22
- name={name}
23
- control={resolvedControl}
24
- render={({ field, fieldState }) => (
25
- <FieldWrapper
26
- name={name}
27
- label={label}
28
- description={description}
29
- required={required}
30
- disabled={disabled}
31
- localized={localized}
32
- locale={locale}
33
- error={fieldState.error?.message}
34
- >
35
- <CheckboxInput
36
- id={name}
37
- value={!!field.value}
38
- onChange={field.onChange}
39
- disabled={disabled}
40
- aria-invalid={!!fieldState.error}
41
- className={className}
42
- />
43
- </FieldWrapper>
44
- )}
45
- />
46
- );
47
- }
@@ -1,50 +0,0 @@
1
- import * as React from "react";
2
- import { Controller } from "react-hook-form";
3
- import { FieldWrapper } from "./field-wrapper";
4
- import { useResolvedControl } from "./field-utils";
5
- import type { BaseFieldProps } from "./field-types";
6
-
7
- type CustomFieldProps = BaseFieldProps & {
8
- component: React.ComponentType<any>;
9
- };
10
-
11
- export function CustomField({
12
- name,
13
- label,
14
- description,
15
- placeholder,
16
- required,
17
- disabled,
18
- localized,
19
- locale,
20
- control,
21
- component: Component,
22
- }: CustomFieldProps) {
23
- const resolvedControl = useResolvedControl(control);
24
-
25
- return (
26
- <Controller
27
- name={name}
28
- control={resolvedControl}
29
- render={({ field, fieldState }) => (
30
- <FieldWrapper
31
- name={name}
32
- label={label}
33
- description={description}
34
- required={required}
35
- disabled={disabled}
36
- localized={localized}
37
- locale={locale}
38
- error={fieldState.error?.message}
39
- >
40
- <Component
41
- {...field}
42
- id={name}
43
- disabled={disabled}
44
- placeholder={placeholder}
45
- />
46
- </FieldWrapper>
47
- )}
48
- />
49
- );
50
- }
@@ -1,65 +0,0 @@
1
- import { Controller } from "react-hook-form";
2
- import { DateInput } from "../primitives/date-input";
3
- import { FieldWrapper } from "./field-wrapper";
4
- import { useResolvedControl } from "./field-utils";
5
- import type { DateFieldProps } from "./field-types";
6
-
7
- export function DateField({
8
- name,
9
- label,
10
- description,
11
- placeholder,
12
- required,
13
- disabled,
14
- localized,
15
- locale,
16
- control,
17
- className,
18
- minDate,
19
- maxDate,
20
- format,
21
- }: DateFieldProps) {
22
- const resolvedControl = useResolvedControl(control);
23
-
24
- return (
25
- <Controller
26
- name={name}
27
- control={resolvedControl}
28
- render={({ field, fieldState }) => {
29
- // Handle string dates from form (convert to Date object)
30
- const dateValue =
31
- field.value instanceof Date
32
- ? field.value
33
- : field.value
34
- ? new Date(field.value)
35
- : null;
36
-
37
- return (
38
- <FieldWrapper
39
- name={name}
40
- label={label}
41
- description={description}
42
- required={required}
43
- disabled={disabled}
44
- localized={localized}
45
- locale={locale}
46
- error={fieldState.error?.message}
47
- >
48
- <DateInput
49
- id={name}
50
- value={dateValue}
51
- onChange={field.onChange}
52
- minDate={minDate}
53
- maxDate={maxDate}
54
- format={format}
55
- placeholder={placeholder}
56
- disabled={disabled}
57
- aria-invalid={!!fieldState.error}
58
- className={className}
59
- />
60
- </FieldWrapper>
61
- );
62
- }}
63
- />
64
- );
65
- }
@@ -1,67 +0,0 @@
1
- import { Controller } from "react-hook-form";
2
- import { DateTimeInput } from "../primitives/date-input";
3
- import { FieldWrapper } from "./field-wrapper";
4
- import { useResolvedControl } from "./field-utils";
5
- import type { DateTimeFieldProps } from "./field-types";
6
-
7
- export function DatetimeField({
8
- name,
9
- label,
10
- description,
11
- placeholder,
12
- required,
13
- disabled,
14
- localized,
15
- locale,
16
- control,
17
- className,
18
- minDate,
19
- maxDate,
20
- format,
21
- precision,
22
- }: DateTimeFieldProps) {
23
- const resolvedControl = useResolvedControl(control);
24
-
25
- return (
26
- <Controller
27
- name={name}
28
- control={resolvedControl}
29
- render={({ field, fieldState }) => {
30
- // Handle string dates from form (convert to Date object)
31
- const dateValue =
32
- field.value instanceof Date
33
- ? field.value
34
- : field.value
35
- ? new Date(field.value)
36
- : null;
37
-
38
- return (
39
- <FieldWrapper
40
- name={name}
41
- label={label}
42
- description={description}
43
- required={required}
44
- disabled={disabled}
45
- localized={localized}
46
- locale={locale}
47
- error={fieldState.error?.message}
48
- >
49
- <DateTimeInput
50
- id={name}
51
- value={dateValue}
52
- onChange={field.onChange}
53
- minDate={minDate}
54
- maxDate={maxDate}
55
- format={format}
56
- precision={precision}
57
- placeholder={placeholder}
58
- disabled={disabled}
59
- aria-invalid={!!fieldState.error}
60
- className={className}
61
- />
62
- </FieldWrapper>
63
- );
64
- }}
65
- />
66
- );
67
- }