@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,253 @@
1
+ import * as React$1 from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { Button } from "../ui/button";
4
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog";
5
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../ui/sheet";
6
+ import { CaretDown, CaretUp, Pencil, Plus, Trash } from "@phosphor-icons/react";
7
+ import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
8
+ import { LocaleBadge } from "./locale-badge";
9
+
10
+ //#region src/components/fields/embedded-collection.tsx
11
+ /**
12
+ * EmbeddedCollectionField Component
13
+ *
14
+ * Manages embedded collections with inline, modal, or drawer editing.
15
+ */
16
+ function EmbeddedCollectionField({ name, value, collection, mode = "inline", orderable = false, rowLabel, label, description, required, disabled, readOnly, error, placeholder, localized, locale, renderFields, minItems, maxItems }) {
17
+ const { control } = useFormContext();
18
+ const { fields, append, remove, move } = useFieldArray({
19
+ control,
20
+ name
21
+ });
22
+ const values = useWatch({
23
+ control,
24
+ name
25
+ }) ?? value;
26
+ const [activeIndex, setActiveIndex] = React$1.useState(null);
27
+ const [isOpen, setIsOpen] = React$1.useState(false);
28
+ const canAddMore = !maxItems || fields.length < maxItems;
29
+ const canRemove = !readOnly && (!minItems || fields.length > minItems);
30
+ const fallbackLabel = label || collection || "Item";
31
+ const resolveRowLabel = React$1.useCallback((item, index) => {
32
+ if (typeof rowLabel === "function") {
33
+ const labelValue = rowLabel(item);
34
+ if (labelValue) return labelValue;
35
+ }
36
+ if (typeof rowLabel === "string") {
37
+ const labelValue = item?.[rowLabel];
38
+ if (labelValue) return String(labelValue);
39
+ }
40
+ if (item?.title) return String(item.title);
41
+ if (item?.name) return String(item.name);
42
+ return `${fallbackLabel} ${index + 1}`;
43
+ }, [fallbackLabel, rowLabel]);
44
+ const handleAdd = () => {
45
+ if (disabled || readOnly || !canAddMore) return;
46
+ append({});
47
+ if (mode !== "inline") {
48
+ const nextIndex = fields.length;
49
+ setActiveIndex(nextIndex);
50
+ setIsOpen(true);
51
+ }
52
+ };
53
+ const handleRemove = (index) => {
54
+ if (!canRemove) return;
55
+ remove(index);
56
+ if (activeIndex === null) return;
57
+ if (index === activeIndex) {
58
+ setIsOpen(false);
59
+ setActiveIndex(null);
60
+ } else if (index < activeIndex) setActiveIndex(activeIndex - 1);
61
+ };
62
+ const handleMove = (from, to) => {
63
+ if (to < 0 || to >= fields.length) return;
64
+ move(from, to);
65
+ if (activeIndex === null) return;
66
+ if (activeIndex === from) setActiveIndex(to);
67
+ else if (activeIndex === to) setActiveIndex(from);
68
+ };
69
+ const handleOpenChange = (open) => {
70
+ setIsOpen(open);
71
+ if (!open) setActiveIndex(null);
72
+ };
73
+ const renderItemFields = (index) => {
74
+ if (!renderFields) return /* @__PURE__ */ jsx("div", {
75
+ className: "rounded-lg border border-dashed p-4 text-center",
76
+ children: /* @__PURE__ */ jsx("p", {
77
+ className: "text-sm text-muted-foreground",
78
+ children: "Form fields not configured."
79
+ })
80
+ });
81
+ return renderFields(index);
82
+ };
83
+ const emptyState = /* @__PURE__ */ jsx("div", {
84
+ className: "rounded-lg border border-dashed p-4 text-center",
85
+ children: /* @__PURE__ */ jsx("p", {
86
+ className: "text-sm text-muted-foreground",
87
+ children: placeholder || `No ${fallbackLabel} added yet`
88
+ })
89
+ });
90
+ const editorContent = activeIndex !== null ? renderItemFields(activeIndex) : null;
91
+ const editorTitle = activeIndex !== null ? resolveRowLabel(values?.[activeIndex], activeIndex) : fallbackLabel;
92
+ const showEditor = mode === "modal" || mode === "drawer";
93
+ return /* @__PURE__ */ jsxs("div", {
94
+ className: "space-y-2",
95
+ children: [
96
+ label && /* @__PURE__ */ jsxs("div", {
97
+ className: "flex items-center gap-2",
98
+ children: [/* @__PURE__ */ jsxs("label", {
99
+ htmlFor: name,
100
+ className: "text-sm font-medium",
101
+ children: [
102
+ label,
103
+ required && /* @__PURE__ */ jsx("span", {
104
+ className: "text-destructive",
105
+ children: "*"
106
+ }),
107
+ maxItems && /* @__PURE__ */ jsxs("span", {
108
+ className: "ml-2 text-xs text-muted-foreground",
109
+ children: [
110
+ "(",
111
+ fields.length,
112
+ "/",
113
+ maxItems,
114
+ ")"
115
+ ]
116
+ })
117
+ ]
118
+ }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
119
+ }),
120
+ description && /* @__PURE__ */ jsx("p", {
121
+ className: "text-sm text-muted-foreground",
122
+ children: description
123
+ }),
124
+ /* @__PURE__ */ jsx("div", {
125
+ className: "space-y-3",
126
+ children: fields.length === 0 ? emptyState : fields.map((field, index) => {
127
+ const itemValue = values?.[index];
128
+ const itemLabel = resolveRowLabel(itemValue, index);
129
+ const canMoveUp = orderable && index > 0;
130
+ const canMoveDown = orderable && index < fields.length - 1;
131
+ return /* @__PURE__ */ jsxs("div", {
132
+ className: "rounded-lg border bg-card",
133
+ children: [/* @__PURE__ */ jsxs("div", {
134
+ className: "flex items-center justify-between border-b px-3 py-2",
135
+ children: [/* @__PURE__ */ jsxs("div", {
136
+ className: "flex items-center gap-2",
137
+ children: [/* @__PURE__ */ jsxs("span", {
138
+ className: "text-xs text-muted-foreground",
139
+ children: ["#", index + 1]
140
+ }), /* @__PURE__ */ jsx("span", {
141
+ className: "text-sm font-medium",
142
+ children: itemLabel
143
+ })]
144
+ }), /* @__PURE__ */ jsxs("div", {
145
+ className: "flex items-center gap-1",
146
+ children: [
147
+ orderable && !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
148
+ type: "button",
149
+ variant: "ghost",
150
+ size: "icon",
151
+ className: "h-6 w-6",
152
+ onClick: () => handleMove(index, index - 1),
153
+ disabled: !canMoveUp || disabled,
154
+ title: "Move up",
155
+ children: /* @__PURE__ */ jsx(CaretUp, { className: "h-3 w-3" })
156
+ }), /* @__PURE__ */ jsx(Button, {
157
+ type: "button",
158
+ variant: "ghost",
159
+ size: "icon",
160
+ className: "h-6 w-6",
161
+ onClick: () => handleMove(index, index + 1),
162
+ disabled: !canMoveDown || disabled,
163
+ title: "Move down",
164
+ children: /* @__PURE__ */ jsx(CaretDown, { className: "h-3 w-3" })
165
+ })] }),
166
+ mode !== "inline" && /* @__PURE__ */ jsx(Button, {
167
+ type: "button",
168
+ variant: "ghost",
169
+ size: "icon",
170
+ className: "h-6 w-6",
171
+ onClick: () => {
172
+ setActiveIndex(index);
173
+ setIsOpen(true);
174
+ },
175
+ disabled: disabled || !renderFields,
176
+ title: readOnly ? "View" : "Edit",
177
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
178
+ }),
179
+ !readOnly && canRemove && /* @__PURE__ */ jsx(Button, {
180
+ type: "button",
181
+ variant: "ghost",
182
+ size: "icon",
183
+ className: "h-6 w-6",
184
+ onClick: () => handleRemove(index),
185
+ disabled,
186
+ title: "Remove",
187
+ children: /* @__PURE__ */ jsx(Trash, { className: "h-3 w-3" })
188
+ })
189
+ ]
190
+ })]
191
+ }), mode === "inline" && /* @__PURE__ */ jsx("div", {
192
+ className: "space-y-3 p-3",
193
+ children: renderItemFields(index)
194
+ })]
195
+ }, field.id);
196
+ })
197
+ }),
198
+ !readOnly && canAddMore && /* @__PURE__ */ jsxs(Button, {
199
+ type: "button",
200
+ variant: "outline",
201
+ onClick: handleAdd,
202
+ disabled,
203
+ children: [
204
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
205
+ "Add ",
206
+ fallbackLabel
207
+ ]
208
+ }),
209
+ error && /* @__PURE__ */ jsx("p", {
210
+ className: "text-sm text-destructive",
211
+ children: error
212
+ }),
213
+ showEditor && mode === "modal" && /* @__PURE__ */ jsx(Dialog, {
214
+ open: isOpen,
215
+ onOpenChange: handleOpenChange,
216
+ children: /* @__PURE__ */ jsxs(DialogContent, {
217
+ className: "sm:max-w-2xl",
218
+ children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: editorTitle }), description && /* @__PURE__ */ jsx(DialogDescription, { children: description })] }), /* @__PURE__ */ jsx("div", {
219
+ className: "space-y-4",
220
+ children: editorContent
221
+ })]
222
+ })
223
+ }),
224
+ showEditor && mode === "drawer" && /* @__PURE__ */ jsx(Sheet, {
225
+ open: isOpen,
226
+ onOpenChange: handleOpenChange,
227
+ children: /* @__PURE__ */ jsxs(SheetContent, {
228
+ side: "right",
229
+ className: "sm:max-w-lg",
230
+ children: [
231
+ /* @__PURE__ */ jsxs(SheetHeader, { children: [/* @__PURE__ */ jsx(SheetTitle, { children: editorTitle }), description && /* @__PURE__ */ jsx(SheetDescription, { children: description })] }),
232
+ /* @__PURE__ */ jsx("div", {
233
+ className: "px-4 pb-4",
234
+ children: editorContent
235
+ }),
236
+ /* @__PURE__ */ jsx("div", {
237
+ className: "px-4 pb-4",
238
+ children: /* @__PURE__ */ jsx(Button, {
239
+ type: "button",
240
+ variant: "outline",
241
+ onClick: () => handleOpenChange(false),
242
+ children: "Close"
243
+ })
244
+ })
245
+ ]
246
+ })
247
+ })
248
+ ]
249
+ });
250
+ }
251
+
252
+ //#endregion
253
+ export { EmbeddedCollectionField };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,10 @@
1
+ import { useFormContext } from "react-hook-form";
2
+
3
+ //#region src/components/fields/field-utils.ts
4
+ function useResolvedControl(control) {
5
+ const form = useFormContext();
6
+ return control ?? form.control;
7
+ }
8
+
9
+ //#endregion
10
+ export { useResolvedControl };
@@ -0,0 +1,34 @@
1
+ import "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from "../ui/field";
4
+ import { LocaleBadge } from "./locale-badge";
5
+
6
+ //#region src/components/fields/field-wrapper.tsx
7
+ function FieldWrapper({ name, label, description, required, disabled, error, localized, locale, children }) {
8
+ return /* @__PURE__ */ jsx(Field, {
9
+ "data-disabled": disabled,
10
+ "data-invalid": !!error,
11
+ children: /* @__PURE__ */ jsxs("div", {
12
+ className: "space-y-2",
13
+ children: [
14
+ label && /* @__PURE__ */ jsxs(FieldLabel, {
15
+ htmlFor: name,
16
+ className: "flex items-center gap-2",
17
+ children: [/* @__PURE__ */ jsxs("span", {
18
+ className: "flex items-center gap-1",
19
+ children: [label, required && /* @__PURE__ */ jsx("span", {
20
+ className: "text-destructive",
21
+ children: "*"
22
+ })]
23
+ }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
24
+ }),
25
+ /* @__PURE__ */ jsx(FieldContent, { children }),
26
+ description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
27
+ error && /* @__PURE__ */ jsx(FieldError, { errors: [{ message: error }] })
28
+ ]
29
+ })
30
+ });
31
+ }
32
+
33
+ //#endregion
34
+ export { FieldWrapper };
@@ -0,0 +1,23 @@
1
+ import { LocaleBadge } from "./locale-badge";
2
+ import { FieldWrapper } from "./field-wrapper";
3
+ import { useResolvedControl } from "./field-utils";
4
+ import { RelationSelect } from "./relation-select";
5
+ import { RelationPicker } from "./relation-picker";
6
+ import { RelationField } from "./relation-field";
7
+ import { JsonField } from "./json-field";
8
+ import { EmbeddedCollectionField } from "./embedded-collection";
9
+ import { ArrayField } from "./array-field";
10
+ import { RichTextEditor } from "./rich-text-editor";
11
+ import { TextField } from "./text-field";
12
+ import { EmailField } from "./email-field";
13
+ import { PasswordField } from "./password-field";
14
+ import { TextareaField } from "./textarea-field";
15
+ import { NumberField } from "./number-field";
16
+ import { CheckboxField } from "./checkbox-field";
17
+ import { SwitchField } from "./switch-field";
18
+ import { SelectField } from "./select-field";
19
+ import { DateField } from "./date-field";
20
+ import { DatetimeField } from "./datetime-field";
21
+ import { CustomField } from "./custom-field";
22
+
23
+ export { ArrayField, CheckboxField, CustomField, DateField, DatetimeField, EmailField, EmbeddedCollectionField, FieldWrapper, JsonField, LocaleBadge, NumberField, PasswordField, RelationField, RelationPicker, RelationSelect, RichTextEditor, SelectField, SwitchField, TextField, TextareaField, useResolvedControl };
@@ -0,0 +1,243 @@
1
+ import * as React$1 from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Button } from "../ui/button";
4
+ import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from "../ui/field";
5
+ import { Textarea } from "../ui/textarea";
6
+ import { cn } from "../../lib/utils";
7
+ import { Code, ListBullets, WarningCircle } from "@phosphor-icons/react";
8
+ import { Controller, useFormContext } from "react-hook-form";
9
+
10
+ //#region src/components/fields/json-field.tsx
11
+ /**
12
+ * JsonField Component
13
+ *
14
+ * JSON field with two editing modes:
15
+ * - "code" - Raw JSON editing with syntax highlighting
16
+ * - "form" - Structured form editing (if schema provided)
17
+ *
18
+ * Integrates with react-hook-form via Controller.
19
+ */
20
+ /**
21
+ * JSON field with code editor and optional form mode.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // Basic JSON editor
26
+ * <JsonField
27
+ * name="metadata"
28
+ * label="Metadata"
29
+ * description="Additional metadata as JSON"
30
+ * />
31
+ *
32
+ * // With custom form mode
33
+ * <JsonField
34
+ * name="settings"
35
+ * label="Settings"
36
+ * renderForm={({ value, onChange }) => (
37
+ * <SettingsForm value={value} onChange={onChange} />
38
+ * )}
39
+ * />
40
+ * ```
41
+ */
42
+ function JsonField({ name, label, description, required, disabled, readOnly, placeholder = "{\n \"key\": \"value\"\n}", defaultMode = "code", allowModeSwitch = true, minHeight = 200, maxHeight = 400, renderForm, control: controlProp, className }) {
43
+ const formContext = useFormContext();
44
+ const control = controlProp ?? formContext?.control;
45
+ const [mode, setMode] = React$1.useState(defaultMode);
46
+ if (!control) {
47
+ console.warn("JsonField: No form control found. Make sure to use within FormProvider or pass control prop.");
48
+ return null;
49
+ }
50
+ const showModeSwitch = allowModeSwitch && renderForm;
51
+ return /* @__PURE__ */ jsx(Controller, {
52
+ name,
53
+ control,
54
+ rules: {
55
+ required: required ? `${label || name} is required` : void 0,
56
+ validate: (value) => {
57
+ if (!value) return true;
58
+ if (mode === "code" && typeof value === "string") try {
59
+ JSON.parse(value);
60
+ return true;
61
+ } catch {
62
+ return "Invalid JSON format";
63
+ }
64
+ return true;
65
+ }
66
+ },
67
+ render: ({ field, fieldState }) => {
68
+ const error = fieldState.error?.message;
69
+ return /* @__PURE__ */ jsxs(Field, {
70
+ "data-invalid": !!error,
71
+ className,
72
+ children: [/* @__PURE__ */ jsxs("div", {
73
+ className: "flex items-center justify-between",
74
+ children: [label && /* @__PURE__ */ jsxs(FieldLabel, {
75
+ htmlFor: name,
76
+ children: [label, required && /* @__PURE__ */ jsx("span", {
77
+ className: "text-destructive ml-1",
78
+ children: "*"
79
+ })]
80
+ }), showModeSwitch && /* @__PURE__ */ jsxs("div", {
81
+ className: "flex gap-1",
82
+ children: [/* @__PURE__ */ jsx(Button, {
83
+ type: "button",
84
+ variant: mode === "code" ? "secondary" : "ghost",
85
+ size: "icon-xs",
86
+ onClick: () => setMode("code"),
87
+ disabled,
88
+ title: "Code editor",
89
+ children: /* @__PURE__ */ jsx(Code, { weight: "bold" })
90
+ }), /* @__PURE__ */ jsx(Button, {
91
+ type: "button",
92
+ variant: mode === "form" ? "secondary" : "ghost",
93
+ size: "icon-xs",
94
+ onClick: () => setMode("form"),
95
+ disabled,
96
+ title: "Form editor",
97
+ children: /* @__PURE__ */ jsx(ListBullets, { weight: "bold" })
98
+ })]
99
+ })]
100
+ }), /* @__PURE__ */ jsxs(FieldContent, { children: [
101
+ mode === "code" ? /* @__PURE__ */ jsx(JsonCodeEditor, {
102
+ value: field.value,
103
+ onChange: field.onChange,
104
+ disabled,
105
+ readOnly,
106
+ placeholder,
107
+ minHeight,
108
+ maxHeight,
109
+ error: !!error
110
+ }) : renderForm ? /* @__PURE__ */ jsx(JsonFormEditor, {
111
+ value: field.value,
112
+ onChange: field.onChange,
113
+ disabled,
114
+ readOnly,
115
+ renderForm
116
+ }) : /* @__PURE__ */ jsx(JsonCodeEditor, {
117
+ value: field.value,
118
+ onChange: field.onChange,
119
+ disabled,
120
+ readOnly,
121
+ placeholder,
122
+ minHeight,
123
+ maxHeight,
124
+ error: !!error
125
+ }),
126
+ description && !error && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
127
+ /* @__PURE__ */ jsx(FieldError, { children: error })
128
+ ] })]
129
+ });
130
+ }
131
+ });
132
+ }
133
+ /**
134
+ * Code editor for JSON (using Textarea for simplicity)
135
+ * Can be replaced with Monaco/CodeMirror in the future
136
+ */
137
+ function JsonCodeEditor({ value, onChange, disabled, readOnly, placeholder, minHeight, maxHeight, error }) {
138
+ const [localValue, setLocalValue] = React$1.useState(() => {
139
+ if (typeof value === "string") return value;
140
+ if (value === null || value === void 0) return "";
141
+ try {
142
+ return JSON.stringify(value, null, 2);
143
+ } catch {
144
+ return "";
145
+ }
146
+ });
147
+ const [parseError, setParseError] = React$1.useState(null);
148
+ React$1.useEffect(() => {
149
+ if (typeof value === "string") setLocalValue(value);
150
+ else if (value !== null && value !== void 0) try {
151
+ setLocalValue(JSON.stringify(value, null, 2));
152
+ } catch {}
153
+ else setLocalValue("");
154
+ }, [value]);
155
+ const handleChange = (e) => {
156
+ const newValue = e.target.value;
157
+ setLocalValue(newValue);
158
+ if (!newValue.trim()) {
159
+ setParseError(null);
160
+ onChange(null);
161
+ return;
162
+ }
163
+ try {
164
+ const parsed = JSON.parse(newValue);
165
+ setParseError(null);
166
+ onChange(parsed);
167
+ } catch (err) {
168
+ setParseError("Invalid JSON");
169
+ onChange(newValue);
170
+ }
171
+ };
172
+ const handleFormat = () => {
173
+ try {
174
+ const parsed = JSON.parse(localValue);
175
+ setLocalValue(JSON.stringify(parsed, null, 2));
176
+ setParseError(null);
177
+ onChange(parsed);
178
+ } catch {}
179
+ };
180
+ return /* @__PURE__ */ jsxs("div", {
181
+ className: "space-y-2",
182
+ children: [/* @__PURE__ */ jsxs("div", {
183
+ className: "relative",
184
+ children: [/* @__PURE__ */ jsx(Textarea, {
185
+ value: localValue,
186
+ onChange: handleChange,
187
+ disabled,
188
+ readOnly,
189
+ placeholder,
190
+ className: cn("font-mono text-xs", error || parseError ? "border-destructive" : ""),
191
+ style: {
192
+ minHeight: `${minHeight}px`,
193
+ maxHeight: maxHeight ? `${maxHeight}px` : void 0,
194
+ resize: maxHeight ? "none" : "vertical"
195
+ },
196
+ "aria-invalid": !!error || !!parseError
197
+ }), parseError && /* @__PURE__ */ jsxs("div", {
198
+ className: "text-destructive absolute right-2 top-2 flex items-center gap-1 text-xs",
199
+ children: [/* @__PURE__ */ jsx(WarningCircle, {
200
+ weight: "fill",
201
+ className: "size-3"
202
+ }), parseError]
203
+ })]
204
+ }), !readOnly && !disabled && localValue && /* @__PURE__ */ jsx("div", {
205
+ className: "flex justify-end",
206
+ children: /* @__PURE__ */ jsx(Button, {
207
+ type: "button",
208
+ variant: "ghost",
209
+ size: "xs",
210
+ onClick: handleFormat,
211
+ disabled: !!parseError,
212
+ children: "Format JSON"
213
+ })
214
+ })]
215
+ });
216
+ }
217
+ /**
218
+ * Form-based editor wrapper
219
+ */
220
+ function JsonFormEditor({ value, onChange, disabled, readOnly, renderForm }) {
221
+ const safeValue = React$1.useMemo(() => {
222
+ if (typeof value === "object" && value !== null) return value;
223
+ if (typeof value === "string") try {
224
+ return JSON.parse(value);
225
+ } catch {
226
+ return {};
227
+ }
228
+ return {};
229
+ }, [value]);
230
+ if (!renderForm) return null;
231
+ return /* @__PURE__ */ jsx("div", {
232
+ className: "rounded-lg border p-4",
233
+ children: renderForm({
234
+ value: safeValue,
235
+ onChange,
236
+ disabled,
237
+ readOnly
238
+ })
239
+ });
240
+ }
241
+
242
+ //#endregion
243
+ export { JsonField };
@@ -0,0 +1,16 @@
1
+ import "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Badge } from "../ui/badge";
4
+
5
+ //#region src/components/fields/locale-badge.tsx
6
+ function LocaleBadge({ locale }) {
7
+ if (!locale) return null;
8
+ return /* @__PURE__ */ jsx(Badge, {
9
+ variant: "secondary",
10
+ className: "uppercase text-[10px] tracking-wide",
11
+ children: locale
12
+ });
13
+ }
14
+
15
+ //#endregion
16
+ export { LocaleBadge };
@@ -0,0 +1,39 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { NumberInput } from "../primitives/number-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/number-field.tsx
8
+ function NumberField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, min, max, step, showButtons }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(NumberInput, {
22
+ id: name,
23
+ value: field.value ?? null,
24
+ onChange: field.onChange,
25
+ placeholder,
26
+ disabled,
27
+ min,
28
+ max,
29
+ step,
30
+ showButtons,
31
+ "aria-invalid": !!fieldState.error,
32
+ className
33
+ })
34
+ })
35
+ });
36
+ }
37
+
38
+ //#endregion
39
+ export { NumberField };
@@ -0,0 +1,37 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { TextInput } from "../primitives/text-input";
3
+ import { Controller } from "react-hook-form";
4
+ import { FieldWrapper } from "./field-wrapper";
5
+ import { useResolvedControl } from "./field-utils";
6
+
7
+ //#region src/components/fields/password-field.tsx
8
+ function PasswordField({ name, label, description, placeholder, required, disabled, localized, locale, control, className }) {
9
+ return /* @__PURE__ */ jsx(Controller, {
10
+ name,
11
+ control: useResolvedControl(control),
12
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(FieldWrapper, {
13
+ name,
14
+ label,
15
+ description,
16
+ required,
17
+ disabled,
18
+ localized,
19
+ locale,
20
+ error: fieldState.error?.message,
21
+ children: /* @__PURE__ */ jsx(TextInput, {
22
+ id: name,
23
+ value: field.value ?? "",
24
+ onChange: field.onChange,
25
+ type: "password",
26
+ placeholder,
27
+ disabled,
28
+ autoComplete: "current-password",
29
+ "aria-invalid": !!fieldState.error,
30
+ className
31
+ })
32
+ })
33
+ });
34
+ }
35
+
36
+ //#endregion
37
+ export { PasswordField };