@questpie/admin 0.0.1

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 (203) hide show
  1. package/.turbo/turbo-build.log +108 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +556 -0
  4. package/STATUS.md +917 -0
  5. package/VALIDATION.md +602 -0
  6. package/components.json +24 -0
  7. package/dist/__tests__/setup.mjs +38 -0
  8. package/dist/__tests__/test-utils.mjs +45 -0
  9. package/dist/__tests__/vitest.d.mjs +3 -0
  10. package/dist/components/admin-app.mjs +69 -0
  11. package/dist/components/fields/array-field.mjs +190 -0
  12. package/dist/components/fields/checkbox-field.mjs +34 -0
  13. package/dist/components/fields/custom-field.mjs +32 -0
  14. package/dist/components/fields/date-field.mjs +41 -0
  15. package/dist/components/fields/datetime-field.mjs +42 -0
  16. package/dist/components/fields/email-field.mjs +37 -0
  17. package/dist/components/fields/embedded-collection.mjs +253 -0
  18. package/dist/components/fields/field-types.mjs +1 -0
  19. package/dist/components/fields/field-utils.mjs +10 -0
  20. package/dist/components/fields/field-wrapper.mjs +34 -0
  21. package/dist/components/fields/index.mjs +23 -0
  22. package/dist/components/fields/json-field.mjs +243 -0
  23. package/dist/components/fields/locale-badge.mjs +16 -0
  24. package/dist/components/fields/number-field.mjs +39 -0
  25. package/dist/components/fields/password-field.mjs +37 -0
  26. package/dist/components/fields/relation-field.mjs +104 -0
  27. package/dist/components/fields/relation-picker.mjs +229 -0
  28. package/dist/components/fields/relation-select.mjs +188 -0
  29. package/dist/components/fields/rich-text-editor/index.mjs +897 -0
  30. package/dist/components/fields/select-field.mjs +41 -0
  31. package/dist/components/fields/switch-field.mjs +34 -0
  32. package/dist/components/fields/text-field.mjs +38 -0
  33. package/dist/components/fields/textarea-field.mjs +38 -0
  34. package/dist/components/index.mjs +59 -0
  35. package/dist/components/primitives/checkbox-input.mjs +127 -0
  36. package/dist/components/primitives/date-input.mjs +303 -0
  37. package/dist/components/primitives/index.mjs +12 -0
  38. package/dist/components/primitives/number-input.mjs +104 -0
  39. package/dist/components/primitives/select-input.mjs +177 -0
  40. package/dist/components/primitives/tag-input.mjs +135 -0
  41. package/dist/components/primitives/text-input.mjs +39 -0
  42. package/dist/components/primitives/textarea-input.mjs +37 -0
  43. package/dist/components/primitives/toggle-input.mjs +31 -0
  44. package/dist/components/primitives/types.mjs +12 -0
  45. package/dist/components/ui/accordion.mjs +55 -0
  46. package/dist/components/ui/avatar.mjs +54 -0
  47. package/dist/components/ui/badge.mjs +34 -0
  48. package/dist/components/ui/button.mjs +48 -0
  49. package/dist/components/ui/card.mjs +58 -0
  50. package/dist/components/ui/checkbox.mjs +21 -0
  51. package/dist/components/ui/combobox.mjs +163 -0
  52. package/dist/components/ui/dialog.mjs +95 -0
  53. package/dist/components/ui/dropdown-menu.mjs +138 -0
  54. package/dist/components/ui/field.mjs +113 -0
  55. package/dist/components/ui/input-group.mjs +82 -0
  56. package/dist/components/ui/input.mjs +17 -0
  57. package/dist/components/ui/label.mjs +15 -0
  58. package/dist/components/ui/popover.mjs +56 -0
  59. package/dist/components/ui/scroll-area.mjs +38 -0
  60. package/dist/components/ui/select.mjs +100 -0
  61. package/dist/components/ui/separator.mjs +16 -0
  62. package/dist/components/ui/sheet.mjs +90 -0
  63. package/dist/components/ui/sidebar.mjs +387 -0
  64. package/dist/components/ui/skeleton.mjs +14 -0
  65. package/dist/components/ui/spinner.mjs +16 -0
  66. package/dist/components/ui/switch.mjs +22 -0
  67. package/dist/components/ui/table.mjs +68 -0
  68. package/dist/components/ui/tabs.mjs +48 -0
  69. package/dist/components/ui/textarea.mjs +15 -0
  70. package/dist/components/ui/tooltip.mjs +44 -0
  71. package/dist/config/component-registry.mjs +38 -0
  72. package/dist/config/index.mjs +129 -0
  73. package/dist/hooks/admin-provider.mjs +70 -0
  74. package/dist/hooks/index.mjs +7 -0
  75. package/dist/hooks/store.mjs +178 -0
  76. package/dist/hooks/use-auth.mjs +76 -0
  77. package/dist/hooks/use-collection-db.mjs +146 -0
  78. package/dist/hooks/use-collection.mjs +112 -0
  79. package/dist/hooks/use-global.mjs +46 -0
  80. package/dist/hooks/use-mobile.mjs +20 -0
  81. package/dist/lib/utils.mjs +10 -0
  82. package/dist/styles/index.css +336 -0
  83. package/dist/styles/index.mjs +1 -0
  84. package/dist/utils/index.mjs +9 -0
  85. package/dist/views/auth/auth-layout.mjs +52 -0
  86. package/dist/views/auth/forgot-password-form.mjs +148 -0
  87. package/dist/views/auth/index.mjs +6 -0
  88. package/dist/views/auth/login-form.mjs +156 -0
  89. package/dist/views/auth/reset-password-form.mjs +184 -0
  90. package/dist/views/collection/auto-form-fields.mjs +525 -0
  91. package/dist/views/collection/collection-form.mjs +91 -0
  92. package/dist/views/collection/collection-list.mjs +76 -0
  93. package/dist/views/collection/form-field.mjs +42 -0
  94. package/dist/views/collection/index.mjs +6 -0
  95. package/dist/views/common/index.mjs +4 -0
  96. package/dist/views/common/locale-switcher.mjs +39 -0
  97. package/dist/views/common/version-history.mjs +272 -0
  98. package/dist/views/index.mjs +9 -0
  99. package/dist/views/layout/admin-layout.mjs +40 -0
  100. package/dist/views/layout/admin-router.mjs +95 -0
  101. package/dist/views/layout/admin-sidebar.mjs +63 -0
  102. package/dist/views/layout/index.mjs +5 -0
  103. package/package.json +276 -0
  104. package/src/__tests__/setup.ts +44 -0
  105. package/src/__tests__/test-utils.tsx +49 -0
  106. package/src/__tests__/vitest.d.ts +9 -0
  107. package/src/components/admin-app.tsx +221 -0
  108. package/src/components/fields/array-field.tsx +237 -0
  109. package/src/components/fields/checkbox-field.tsx +47 -0
  110. package/src/components/fields/custom-field.tsx +50 -0
  111. package/src/components/fields/date-field.tsx +65 -0
  112. package/src/components/fields/datetime-field.tsx +67 -0
  113. package/src/components/fields/email-field.tsx +51 -0
  114. package/src/components/fields/embedded-collection.tsx +315 -0
  115. package/src/components/fields/field-types.ts +162 -0
  116. package/src/components/fields/field-utils.ts +6 -0
  117. package/src/components/fields/field-wrapper.tsx +52 -0
  118. package/src/components/fields/index.ts +66 -0
  119. package/src/components/fields/json-field.tsx +440 -0
  120. package/src/components/fields/locale-badge.tsx +15 -0
  121. package/src/components/fields/number-field.tsx +57 -0
  122. package/src/components/fields/password-field.tsx +51 -0
  123. package/src/components/fields/relation-field.tsx +243 -0
  124. package/src/components/fields/relation-picker.tsx +402 -0
  125. package/src/components/fields/relation-select.tsx +327 -0
  126. package/src/components/fields/rich-text-editor/index.tsx +1337 -0
  127. package/src/components/fields/select-field.tsx +61 -0
  128. package/src/components/fields/switch-field.tsx +47 -0
  129. package/src/components/fields/text-field.tsx +55 -0
  130. package/src/components/fields/textarea-field.tsx +55 -0
  131. package/src/components/index.ts +40 -0
  132. package/src/components/primitives/checkbox-input.tsx +193 -0
  133. package/src/components/primitives/date-input.tsx +401 -0
  134. package/src/components/primitives/index.ts +24 -0
  135. package/src/components/primitives/number-input.tsx +132 -0
  136. package/src/components/primitives/select-input.tsx +296 -0
  137. package/src/components/primitives/tag-input.tsx +200 -0
  138. package/src/components/primitives/text-input.tsx +49 -0
  139. package/src/components/primitives/textarea-input.tsx +46 -0
  140. package/src/components/primitives/toggle-input.tsx +36 -0
  141. package/src/components/primitives/types.ts +235 -0
  142. package/src/components/ui/accordion.tsx +72 -0
  143. package/src/components/ui/avatar.tsx +106 -0
  144. package/src/components/ui/badge.tsx +48 -0
  145. package/src/components/ui/button.tsx +53 -0
  146. package/src/components/ui/card.tsx +94 -0
  147. package/src/components/ui/checkbox.tsx +27 -0
  148. package/src/components/ui/combobox.tsx +290 -0
  149. package/src/components/ui/dialog.tsx +151 -0
  150. package/src/components/ui/dropdown-menu.tsx +254 -0
  151. package/src/components/ui/field.tsx +227 -0
  152. package/src/components/ui/input-group.tsx +149 -0
  153. package/src/components/ui/input.tsx +20 -0
  154. package/src/components/ui/label.tsx +18 -0
  155. package/src/components/ui/popover.tsx +88 -0
  156. package/src/components/ui/scroll-area.tsx +53 -0
  157. package/src/components/ui/select.tsx +192 -0
  158. package/src/components/ui/separator.tsx +23 -0
  159. package/src/components/ui/sheet.tsx +127 -0
  160. package/src/components/ui/sidebar.tsx +723 -0
  161. package/src/components/ui/skeleton.tsx +13 -0
  162. package/src/components/ui/spinner.tsx +10 -0
  163. package/src/components/ui/switch.tsx +32 -0
  164. package/src/components/ui/table.tsx +99 -0
  165. package/src/components/ui/tabs.tsx +82 -0
  166. package/src/components/ui/textarea.tsx +18 -0
  167. package/src/components/ui/tooltip.tsx +70 -0
  168. package/src/config/component-registry.ts +190 -0
  169. package/src/config/index.ts +1099 -0
  170. package/src/hooks/README.md +269 -0
  171. package/src/hooks/admin-provider.tsx +110 -0
  172. package/src/hooks/index.ts +41 -0
  173. package/src/hooks/store.ts +248 -0
  174. package/src/hooks/use-auth.ts +168 -0
  175. package/src/hooks/use-collection-db.ts +209 -0
  176. package/src/hooks/use-collection.ts +156 -0
  177. package/src/hooks/use-global.ts +69 -0
  178. package/src/hooks/use-mobile.ts +21 -0
  179. package/src/lib/utils.ts +6 -0
  180. package/src/styles/index.css +340 -0
  181. package/src/utils/index.ts +6 -0
  182. package/src/views/auth/auth-layout.tsx +77 -0
  183. package/src/views/auth/forgot-password-form.tsx +192 -0
  184. package/src/views/auth/index.ts +21 -0
  185. package/src/views/auth/login-form.tsx +229 -0
  186. package/src/views/auth/reset-password-form.tsx +232 -0
  187. package/src/views/collection/auto-form-fields.tsx +982 -0
  188. package/src/views/collection/collection-form.tsx +186 -0
  189. package/src/views/collection/collection-list.tsx +223 -0
  190. package/src/views/collection/form-field.tsx +52 -0
  191. package/src/views/collection/index.ts +15 -0
  192. package/src/views/common/index.ts +8 -0
  193. package/src/views/common/locale-switcher.tsx +45 -0
  194. package/src/views/common/version-history.tsx +406 -0
  195. package/src/views/index.ts +25 -0
  196. package/src/views/layout/admin-layout.tsx +117 -0
  197. package/src/views/layout/admin-router.tsx +206 -0
  198. package/src/views/layout/admin-sidebar.tsx +185 -0
  199. package/src/views/layout/index.ts +12 -0
  200. package/tsconfig.json +13 -0
  201. package/tsconfig.tsbuildinfo +1 -0
  202. package/tsdown.config.ts +13 -0
  203. package/vitest.config.ts +29 -0
@@ -0,0 +1,237 @@
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
+ }
@@ -0,0 +1,47 @@
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
+ }
@@ -0,0 +1,50 @@
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
+ }
@@ -0,0 +1,65 @@
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
+ }
@@ -0,0 +1,67 @@
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
+ }
@@ -0,0 +1,51 @@
1
+ import { Controller } from "react-hook-form";
2
+ import { TextInput } from "../primitives/text-input";
3
+ import { FieldWrapper } from "./field-wrapper";
4
+ import { useResolvedControl } from "./field-utils";
5
+ import type { BaseFieldProps } from "./field-types";
6
+
7
+ export function EmailField({
8
+ name,
9
+ label,
10
+ description,
11
+ placeholder,
12
+ required,
13
+ disabled,
14
+ localized,
15
+ locale,
16
+ control,
17
+ className,
18
+ }: BaseFieldProps) {
19
+ const resolvedControl = useResolvedControl(control);
20
+
21
+ return (
22
+ <Controller
23
+ name={name}
24
+ control={resolvedControl}
25
+ render={({ field, fieldState }) => (
26
+ <FieldWrapper
27
+ name={name}
28
+ label={label}
29
+ description={description}
30
+ required={required}
31
+ disabled={disabled}
32
+ localized={localized}
33
+ locale={locale}
34
+ error={fieldState.error?.message}
35
+ >
36
+ <TextInput
37
+ id={name}
38
+ value={field.value ?? ""}
39
+ onChange={field.onChange}
40
+ type="email"
41
+ placeholder={placeholder}
42
+ disabled={disabled}
43
+ autoComplete="email"
44
+ aria-invalid={!!fieldState.error}
45
+ className={className}
46
+ />
47
+ </FieldWrapper>
48
+ )}
49
+ />
50
+ );
51
+ }