@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,327 @@
1
+ /**
2
+ * RelationSelect Component
3
+ *
4
+ * Single relation field (one-to-one) with:
5
+ * - Select dropdown to choose existing item
6
+ * - Plus button to create new related item (opens side sheet)
7
+ * - Edit button to modify selected item (opens side sheet)
8
+ */
9
+
10
+ import * as React from "react";
11
+ import { useQuery } from "@tanstack/react-query";
12
+ import { Plus, Pencil, X } from "@phosphor-icons/react";
13
+ import { Button } from "../ui/button";
14
+ import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
15
+ import {
16
+ Sheet,
17
+ SheetContent,
18
+ SheetDescription,
19
+ SheetHeader,
20
+ SheetTitle,
21
+ } from "../ui/sheet";
22
+ import { Spinner } from "../ui/spinner";
23
+ import { LocaleBadge } from "./locale-badge";
24
+ import { useAdminContext } from "../../hooks/admin-provider";
25
+ import type { Questpie } from "questpie";
26
+
27
+ export interface RelationSelectProps<T extends Questpie<any>> {
28
+ /**
29
+ * Field name
30
+ */
31
+ name: string;
32
+
33
+ /**
34
+ * Current value (ID of related item)
35
+ */
36
+ value?: string | null;
37
+
38
+ /**
39
+ * Change handler
40
+ */
41
+ onChange: (value: string | null) => void;
42
+
43
+ /**
44
+ * Target collection name
45
+ */
46
+ targetCollection: string;
47
+
48
+ /**
49
+ * Label for the field
50
+ */
51
+ label?: string;
52
+
53
+ /**
54
+ * Localized field
55
+ */
56
+ localized?: boolean;
57
+
58
+ /**
59
+ * Active locale
60
+ */
61
+ locale?: string;
62
+
63
+ /**
64
+ * Filter options based on form values
65
+ */
66
+ filter?: (formValues: any) => any;
67
+
68
+ /**
69
+ * Is the field required
70
+ */
71
+ required?: boolean;
72
+
73
+ /**
74
+ * Is the field disabled
75
+ */
76
+ disabled?: boolean;
77
+
78
+ /**
79
+ * Is the field readonly
80
+ */
81
+ readOnly?: boolean;
82
+
83
+ /**
84
+ * Placeholder text
85
+ */
86
+ placeholder?: string;
87
+
88
+ /**
89
+ * Error message
90
+ */
91
+ error?: string;
92
+
93
+ /**
94
+ * Custom render function for dropdown options
95
+ */
96
+ renderOption?: (item: any) => React.ReactNode;
97
+
98
+ /**
99
+ * Custom render function for selected value
100
+ */
101
+ renderValue?: (item: any) => React.ReactNode;
102
+
103
+ /**
104
+ * Form fields to render in create/edit sheet
105
+ */
106
+ renderFormFields?: (collection: string, itemId?: string) => React.ReactNode;
107
+ }
108
+
109
+ export function RelationSelect<T extends Questpie<any>>({
110
+ name,
111
+ value,
112
+ onChange,
113
+ targetCollection,
114
+ label,
115
+ filter,
116
+ required,
117
+ disabled,
118
+ readOnly,
119
+ placeholder,
120
+ error,
121
+ localized,
122
+ locale: localeProp,
123
+ renderOption,
124
+ renderValue,
125
+ renderFormFields,
126
+ }: RelationSelectProps<T>) {
127
+ const { client, locale: contextLocale } = useAdminContext<T>();
128
+ const locale = localeProp ?? contextLocale;
129
+ const localeKey = locale ?? "default";
130
+ const [isSheetOpen, setIsSheetOpen] = React.useState(false);
131
+ const [sheetMode, setSheetMode] = React.useState<"create" | "edit">("create");
132
+
133
+ // Fetch options for dropdown
134
+ const { data: options, isLoading } = useQuery({
135
+ queryKey: ["relation", targetCollection, localeKey, filter],
136
+ queryFn: async () => {
137
+ const api = (client as any).collections?.[targetCollection];
138
+ if (!api?.list) return [];
139
+ const result = await api.list({
140
+ ...(filter ? filter({}) : {}),
141
+ });
142
+ return result.data || [];
143
+ },
144
+ });
145
+
146
+ // Fetch selected item details
147
+ const { data: selectedItem } = useQuery({
148
+ queryKey: ["relation", targetCollection, localeKey, value],
149
+ queryFn: async () => {
150
+ if (!value) return null;
151
+ const api = (client as any).collections?.[targetCollection];
152
+ if (!api?.get) return null;
153
+ return await api.get(value);
154
+ },
155
+ enabled: !!value,
156
+ });
157
+
158
+ const handleOpenCreate = () => {
159
+ setSheetMode("create");
160
+ setIsSheetOpen(true);
161
+ };
162
+
163
+ const handleOpenEdit = () => {
164
+ if (!value) return;
165
+ setSheetMode("edit");
166
+ setIsSheetOpen(true);
167
+ };
168
+
169
+ const handleClear = () => {
170
+ onChange(null);
171
+ };
172
+
173
+ const handleValueChange = (newValue: string | null) => {
174
+ if (newValue) {
175
+ onChange(newValue);
176
+ }
177
+ };
178
+
179
+ const getDisplayValue = (item: any) => {
180
+ if (renderValue) return renderValue(item);
181
+ return item?._title || item?.id || "";
182
+ };
183
+
184
+ const getOptionDisplay = (item: any) => {
185
+ if (renderOption) return renderOption(item);
186
+ return item?._title || item?.id || "";
187
+ };
188
+
189
+ return (
190
+ <div className="space-y-2">
191
+ {label && (
192
+ <div className="flex items-center gap-2">
193
+ <label htmlFor={name} className="text-sm font-medium">
194
+ {label}
195
+ {required && <span className="text-destructive">*</span>}
196
+ </label>
197
+ {localized && <LocaleBadge locale={locale || "i18n"} />}
198
+ </div>
199
+ )}
200
+
201
+ <div className="flex gap-2">
202
+ {/* Select Dropdown */}
203
+ <div className="flex-1">
204
+ <Select
205
+ value={value || undefined}
206
+ onValueChange={handleValueChange}
207
+ disabled={disabled || readOnly || isLoading}
208
+ >
209
+ <SelectTrigger
210
+ id={name}
211
+ className={error ? "border-destructive" : ""}
212
+ >
213
+ <span className="truncate">
214
+ {selectedItem
215
+ ? getDisplayValue(selectedItem)
216
+ : placeholder || `Select ${label || targetCollection}...`}
217
+ </span>
218
+ </SelectTrigger>
219
+ <SelectContent>
220
+ {isLoading ? (
221
+ <div className="flex items-center justify-center p-4">
222
+ <Spinner className="h-4 w-4" />
223
+ </div>
224
+ ) : options && options.length > 0 ? (
225
+ options.map((option: any) => (
226
+ <SelectItem key={option.id} value={option.id}>
227
+ {getOptionDisplay(option)}
228
+ </SelectItem>
229
+ ))
230
+ ) : (
231
+ <div className="p-4 text-center text-sm text-muted-foreground">
232
+ No options available
233
+ </div>
234
+ )}
235
+ </SelectContent>
236
+ </Select>
237
+ </div>
238
+
239
+ {/* Action Buttons */}
240
+ {!readOnly && (
241
+ <>
242
+ {/* Clear button (only if value is set and not required) */}
243
+ {value && !required && (
244
+ <Button
245
+ type="button"
246
+ variant="outline"
247
+ size="icon"
248
+ onClick={handleClear}
249
+ disabled={disabled}
250
+ title="Clear selection"
251
+ >
252
+ <X className="h-4 w-4" />
253
+ </Button>
254
+ )}
255
+
256
+ {/* Edit button (only if value is set) */}
257
+ {value && (
258
+ <Button
259
+ type="button"
260
+ variant="outline"
261
+ size="icon"
262
+ onClick={handleOpenEdit}
263
+ disabled={disabled}
264
+ title={`Edit ${label || targetCollection}`}
265
+ >
266
+ <Pencil className="h-4 w-4" />
267
+ </Button>
268
+ )}
269
+
270
+ {/* Create button */}
271
+ <Button
272
+ type="button"
273
+ variant="outline"
274
+ size="icon"
275
+ onClick={handleOpenCreate}
276
+ disabled={disabled}
277
+ title={`Create new ${label || targetCollection}`}
278
+ >
279
+ <Plus className="h-4 w-4" />
280
+ </Button>
281
+ </>
282
+ )}
283
+ </div>
284
+
285
+ {/* Error message */}
286
+ {error && <p className="text-sm text-destructive">{error}</p>}
287
+
288
+ {/* Side Sheet for Create/Edit */}
289
+ <Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
290
+ <SheetContent
291
+ side="right"
292
+ className="w-full sm:max-w-lg overflow-y-auto"
293
+ >
294
+ <SheetHeader>
295
+ <SheetTitle>
296
+ {sheetMode === "create" ? "Create" : "Edit"}{" "}
297
+ {label || targetCollection}
298
+ </SheetTitle>
299
+ <SheetDescription>
300
+ {sheetMode === "create"
301
+ ? `Fill in the details to create a new ${label || targetCollection}`
302
+ : `Update the details of this ${label || targetCollection}`}
303
+ </SheetDescription>
304
+ </SheetHeader>
305
+
306
+ <div className="mt-6">
307
+ {renderFormFields ? (
308
+ renderFormFields(
309
+ targetCollection,
310
+ sheetMode === "edit" ? value || undefined : undefined,
311
+ )
312
+ ) : (
313
+ <div className="rounded-lg border border-dashed p-8 text-center">
314
+ <p className="text-sm text-muted-foreground">
315
+ Form fields not configured.
316
+ <br />
317
+ Pass <code className="text-xs">renderFormFields</code> prop to
318
+ enable create/edit.
319
+ </p>
320
+ </div>
321
+ )}
322
+ </div>
323
+ </SheetContent>
324
+ </Sheet>
325
+ </div>
326
+ );
327
+ }