@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,104 @@
1
+ import "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { RelationSelect } from "./relation-select";
5
+ import { RelationPicker } from "./relation-picker";
6
+
7
+ //#region src/components/fields/relation-field.tsx
8
+ /**
9
+ * Unified relation field component that integrates with react-hook-form.
10
+ *
11
+ * Automatically chooses between RelationSelect (single) and RelationPicker (multiple)
12
+ * based on the `type` prop.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Single relation (one-to-one)
17
+ * <RelationField
18
+ * name="author"
19
+ * targetCollection="users"
20
+ * type="single"
21
+ * label="Author"
22
+ * required
23
+ * />
24
+ *
25
+ * // Multiple relations (many-to-many)
26
+ * <RelationField
27
+ * name="tags"
28
+ * targetCollection="tags"
29
+ * type="multiple"
30
+ * label="Tags"
31
+ * maxItems={5}
32
+ * orderable
33
+ * />
34
+ * ```
35
+ */
36
+ function RelationField({ name, targetCollection, type, label, description, localized, locale, filter, required, disabled, readOnly, placeholder, orderable, maxItems, renderOption, renderValue, renderItem, renderFormFields, control: controlProp }) {
37
+ const formContext = useFormContext();
38
+ const control = controlProp ?? formContext?.control;
39
+ if (!control) {
40
+ console.warn("RelationField: No form control found. Make sure to use within FormProvider or pass control prop.");
41
+ return null;
42
+ }
43
+ return /* @__PURE__ */ jsx(Controller, {
44
+ name,
45
+ control,
46
+ rules: { required: required ? `${label || name} is required` : void 0 },
47
+ render: ({ field, fieldState }) => {
48
+ const error = fieldState.error?.message;
49
+ if (type === "single") return /* @__PURE__ */ jsxs("div", {
50
+ className: "space-y-1",
51
+ children: [/* @__PURE__ */ jsx(RelationSelect, {
52
+ name,
53
+ value: field.value,
54
+ onChange: field.onChange,
55
+ targetCollection,
56
+ label,
57
+ localized,
58
+ locale,
59
+ filter,
60
+ required,
61
+ disabled,
62
+ readOnly,
63
+ placeholder,
64
+ error,
65
+ renderOption,
66
+ renderValue,
67
+ renderFormFields
68
+ }), description && !error && /* @__PURE__ */ jsx("p", {
69
+ className: "text-muted-foreground text-xs",
70
+ children: description
71
+ })]
72
+ });
73
+ return /* @__PURE__ */ jsxs("div", {
74
+ className: "space-y-1",
75
+ children: [/* @__PURE__ */ jsx(RelationPicker, {
76
+ name,
77
+ value: field.value,
78
+ onChange: field.onChange,
79
+ targetCollection,
80
+ label,
81
+ localized,
82
+ locale,
83
+ filter,
84
+ required,
85
+ disabled,
86
+ readOnly,
87
+ placeholder,
88
+ error,
89
+ orderable,
90
+ maxItems,
91
+ renderOption,
92
+ renderItem,
93
+ renderFormFields
94
+ }), description && !error && /* @__PURE__ */ jsx("p", {
95
+ className: "text-muted-foreground text-xs",
96
+ children: description
97
+ })]
98
+ });
99
+ }
100
+ });
101
+ }
102
+
103
+ //#endregion
104
+ export { RelationField };
@@ -0,0 +1,229 @@
1
+ import * as React$1 from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useAdminContext } from "../../hooks/admin-provider";
5
+ import { Button } from "../ui/button";
6
+ import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "../ui/combobox";
7
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../ui/sheet";
8
+ import { DotsSixVertical, Pencil, Plus, X } from "@phosphor-icons/react";
9
+ import { LocaleBadge } from "./locale-badge";
10
+
11
+ //#region src/components/fields/relation-picker.tsx
12
+ /**
13
+ * RelationPicker Component
14
+ *
15
+ * Multiple relation field (one-to-many, many-to-many) with:
16
+ * - Multi-select picker to choose existing items
17
+ * - Plus button to create new related item (opens side sheet)
18
+ * - Edit button on each selected item (opens side sheet)
19
+ * - Remove button on each selected item
20
+ * - Optional drag-and-drop reordering
21
+ */
22
+ function RelationPicker({ name, value = [], onChange, targetCollection, label, filter, required, disabled, readOnly, placeholder, error, localized, locale: localeProp, orderable = false, maxItems, renderItem, renderOption, renderFormFields }) {
23
+ const { client, locale: contextLocale } = useAdminContext();
24
+ const locale = localeProp ?? contextLocale;
25
+ const localeKey = locale ?? "default";
26
+ const [isSheetOpen, setIsSheetOpen] = React$1.useState(false);
27
+ const [sheetMode, setSheetMode] = React$1.useState("create");
28
+ const [editingItemId, setEditingItemId] = React$1.useState();
29
+ const selectedIds = value || [];
30
+ const { data: allOptions, isLoading } = useQuery({
31
+ queryKey: [
32
+ "relation",
33
+ targetCollection,
34
+ localeKey,
35
+ filter
36
+ ],
37
+ queryFn: async () => {
38
+ const api = client.collections?.[targetCollection];
39
+ if (!api?.list) return [];
40
+ return (await api.list({ ...filter ? filter({}) : {} })).data || [];
41
+ }
42
+ });
43
+ const { data: selectedItems } = useQuery({
44
+ queryKey: [
45
+ "relation",
46
+ targetCollection,
47
+ localeKey,
48
+ "selected",
49
+ selectedIds
50
+ ],
51
+ queryFn: async () => {
52
+ if (!selectedIds.length) return [];
53
+ const api = client.collections?.[targetCollection];
54
+ if (!api?.get) return [];
55
+ return await Promise.all(selectedIds.map((id) => api.get(id)));
56
+ },
57
+ enabled: selectedIds.length > 0
58
+ });
59
+ const handleAdd = (itemId) => {
60
+ if (selectedIds.includes(itemId)) return;
61
+ if (maxItems && selectedIds.length >= maxItems) return;
62
+ onChange([...selectedIds, itemId]);
63
+ };
64
+ const handleRemove = (itemId) => {
65
+ onChange(selectedIds.filter((id) => id !== itemId));
66
+ };
67
+ const handleOpenCreate = () => {
68
+ setSheetMode("create");
69
+ setEditingItemId(void 0);
70
+ setIsSheetOpen(true);
71
+ };
72
+ const handleOpenEdit = (itemId) => {
73
+ setSheetMode("edit");
74
+ setEditingItemId(itemId);
75
+ setIsSheetOpen(true);
76
+ };
77
+ const getDisplayValue = (item) => {
78
+ return item?._title || item?.id || "";
79
+ };
80
+ const getOptionDisplay = (item) => {
81
+ if (renderOption) return renderOption(item);
82
+ return item?._title || item?.id || "";
83
+ };
84
+ const availableOptions = allOptions?.filter((opt) => !selectedIds.includes(opt.id)) || [];
85
+ const canAddMore = !maxItems || selectedIds.length < maxItems;
86
+ return /* @__PURE__ */ jsxs("div", {
87
+ className: "space-y-2",
88
+ children: [
89
+ label && /* @__PURE__ */ jsxs("div", {
90
+ className: "flex items-center gap-2",
91
+ children: [/* @__PURE__ */ jsxs("label", {
92
+ htmlFor: name,
93
+ className: "text-sm font-medium",
94
+ children: [
95
+ label,
96
+ required && /* @__PURE__ */ jsx("span", {
97
+ className: "text-destructive",
98
+ children: "*"
99
+ }),
100
+ maxItems && /* @__PURE__ */ jsxs("span", {
101
+ className: "ml-2 text-xs text-muted-foreground",
102
+ children: [
103
+ "(",
104
+ selectedIds.length,
105
+ "/",
106
+ maxItems,
107
+ ")"
108
+ ]
109
+ })
110
+ ]
111
+ }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
112
+ }),
113
+ selectedItems && selectedItems.length > 0 && /* @__PURE__ */ jsx("div", {
114
+ className: "space-y-2 rounded-lg border p-3",
115
+ children: selectedItems.map((item, index) => /* @__PURE__ */ jsxs("div", {
116
+ className: "flex items-center gap-2 rounded-md border bg-card p-2",
117
+ children: [
118
+ orderable && !readOnly && /* @__PURE__ */ jsx("button", {
119
+ type: "button",
120
+ className: "cursor-grab text-muted-foreground hover:text-foreground",
121
+ disabled,
122
+ children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4" })
123
+ }),
124
+ /* @__PURE__ */ jsx("div", {
125
+ className: "flex-1",
126
+ children: renderItem ? renderItem(item, index) : /* @__PURE__ */ jsx("span", {
127
+ className: "text-sm",
128
+ children: getDisplayValue(item)
129
+ })
130
+ }),
131
+ !readOnly && /* @__PURE__ */ jsx(Button, {
132
+ type: "button",
133
+ variant: "ghost",
134
+ size: "icon",
135
+ className: "h-7 w-7",
136
+ onClick: () => handleOpenEdit(item.id),
137
+ disabled,
138
+ title: "Edit",
139
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
140
+ }),
141
+ !readOnly && (!required || selectedIds.length > 1) && /* @__PURE__ */ jsx(Button, {
142
+ type: "button",
143
+ variant: "ghost",
144
+ size: "icon",
145
+ className: "h-7 w-7",
146
+ onClick: () => handleRemove(item.id),
147
+ disabled,
148
+ title: "Remove",
149
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
150
+ })
151
+ ]
152
+ }, item.id))
153
+ }),
154
+ !readOnly && canAddMore && /* @__PURE__ */ jsxs("div", {
155
+ className: "flex gap-2",
156
+ children: [/* @__PURE__ */ jsx("div", {
157
+ className: "flex-1",
158
+ children: /* @__PURE__ */ jsxs(Combobox, {
159
+ value: "",
160
+ onValueChange: (value$1) => {
161
+ if (value$1) handleAdd(value$1);
162
+ },
163
+ disabled: disabled || isLoading,
164
+ children: [/* @__PURE__ */ jsx(ComboboxInput, {
165
+ placeholder: placeholder || `Add ${label || targetCollection}...`,
166
+ disabled: disabled || isLoading
167
+ }), /* @__PURE__ */ jsxs(ComboboxContent, { children: [/* @__PURE__ */ jsx(ComboboxList, { children: availableOptions.map((opt) => /* @__PURE__ */ jsx(ComboboxItem, {
168
+ value: opt.id,
169
+ children: getOptionDisplay(opt)
170
+ }, opt.id)) }), /* @__PURE__ */ jsx(ComboboxEmpty, { children: isLoading ? "Loading..." : "No more options available" })] })]
171
+ })
172
+ }), /* @__PURE__ */ jsx(Button, {
173
+ type: "button",
174
+ variant: "outline",
175
+ size: "icon",
176
+ onClick: handleOpenCreate,
177
+ disabled,
178
+ title: `Create new ${label || targetCollection}`,
179
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
180
+ })]
181
+ }),
182
+ selectedIds.length === 0 && /* @__PURE__ */ jsx("div", {
183
+ className: "rounded-lg border border-dashed p-4 text-center",
184
+ children: /* @__PURE__ */ jsx("p", {
185
+ className: "text-sm text-muted-foreground",
186
+ children: placeholder || `No ${label || targetCollection} selected`
187
+ })
188
+ }),
189
+ error && /* @__PURE__ */ jsx("p", {
190
+ className: "text-sm text-destructive",
191
+ children: error
192
+ }),
193
+ /* @__PURE__ */ jsx(Sheet, {
194
+ open: isSheetOpen,
195
+ onOpenChange: setIsSheetOpen,
196
+ children: /* @__PURE__ */ jsxs(SheetContent, {
197
+ side: "right",
198
+ className: "w-full sm:max-w-lg overflow-y-auto",
199
+ children: [/* @__PURE__ */ jsxs(SheetHeader, { children: [/* @__PURE__ */ jsxs(SheetTitle, { children: [
200
+ sheetMode === "create" ? "Create" : "Edit",
201
+ " ",
202
+ label || targetCollection
203
+ ] }), /* @__PURE__ */ jsx(SheetDescription, { children: sheetMode === "create" ? `Fill in the details to create a new ${label || targetCollection}` : `Update the details of this ${label || targetCollection}` })] }), /* @__PURE__ */ jsx("div", {
204
+ className: "mt-6",
205
+ children: renderFormFields ? renderFormFields(targetCollection, editingItemId) : /* @__PURE__ */ jsx("div", {
206
+ className: "rounded-lg border border-dashed p-8 text-center",
207
+ children: /* @__PURE__ */ jsxs("p", {
208
+ className: "text-sm text-muted-foreground",
209
+ children: [
210
+ "Form fields not configured.",
211
+ /* @__PURE__ */ jsx("br", {}),
212
+ "Pass ",
213
+ /* @__PURE__ */ jsx("code", {
214
+ className: "text-xs",
215
+ children: "renderFormFields"
216
+ }),
217
+ " prop to enable create/edit."
218
+ ]
219
+ })
220
+ })
221
+ })]
222
+ })
223
+ })
224
+ ]
225
+ });
226
+ }
227
+
228
+ //#endregion
229
+ export { RelationPicker };
@@ -0,0 +1,188 @@
1
+ import * as React$1 from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { useAdminContext } from "../../hooks/admin-provider";
5
+ import { Button } from "../ui/button";
6
+ import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
7
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../ui/sheet";
8
+ import { Spinner } from "../ui/spinner";
9
+ import { Pencil, Plus, X } from "@phosphor-icons/react";
10
+ import { LocaleBadge } from "./locale-badge";
11
+
12
+ //#region src/components/fields/relation-select.tsx
13
+ /**
14
+ * RelationSelect Component
15
+ *
16
+ * Single relation field (one-to-one) with:
17
+ * - Select dropdown to choose existing item
18
+ * - Plus button to create new related item (opens side sheet)
19
+ * - Edit button to modify selected item (opens side sheet)
20
+ */
21
+ function RelationSelect({ name, value, onChange, targetCollection, label, filter, required, disabled, readOnly, placeholder, error, localized, locale: localeProp, renderOption, renderValue, renderFormFields }) {
22
+ const { client, locale: contextLocale } = useAdminContext();
23
+ const locale = localeProp ?? contextLocale;
24
+ const localeKey = locale ?? "default";
25
+ const [isSheetOpen, setIsSheetOpen] = React$1.useState(false);
26
+ const [sheetMode, setSheetMode] = React$1.useState("create");
27
+ const { data: options, isLoading } = useQuery({
28
+ queryKey: [
29
+ "relation",
30
+ targetCollection,
31
+ localeKey,
32
+ filter
33
+ ],
34
+ queryFn: async () => {
35
+ const api = client.collections?.[targetCollection];
36
+ if (!api?.list) return [];
37
+ return (await api.list({ ...filter ? filter({}) : {} })).data || [];
38
+ }
39
+ });
40
+ const { data: selectedItem } = useQuery({
41
+ queryKey: [
42
+ "relation",
43
+ targetCollection,
44
+ localeKey,
45
+ value
46
+ ],
47
+ queryFn: async () => {
48
+ if (!value) return null;
49
+ const api = client.collections?.[targetCollection];
50
+ if (!api?.get) return null;
51
+ return await api.get(value);
52
+ },
53
+ enabled: !!value
54
+ });
55
+ const handleOpenCreate = () => {
56
+ setSheetMode("create");
57
+ setIsSheetOpen(true);
58
+ };
59
+ const handleOpenEdit = () => {
60
+ if (!value) return;
61
+ setSheetMode("edit");
62
+ setIsSheetOpen(true);
63
+ };
64
+ const handleClear = () => {
65
+ onChange(null);
66
+ };
67
+ const handleValueChange = (newValue) => {
68
+ if (newValue) onChange(newValue);
69
+ };
70
+ const getDisplayValue = (item) => {
71
+ if (renderValue) return renderValue(item);
72
+ return item?._title || item?.id || "";
73
+ };
74
+ const getOptionDisplay = (item) => {
75
+ if (renderOption) return renderOption(item);
76
+ return item?._title || item?.id || "";
77
+ };
78
+ return /* @__PURE__ */ jsxs("div", {
79
+ className: "space-y-2",
80
+ children: [
81
+ label && /* @__PURE__ */ jsxs("div", {
82
+ className: "flex items-center gap-2",
83
+ children: [/* @__PURE__ */ jsxs("label", {
84
+ htmlFor: name,
85
+ className: "text-sm font-medium",
86
+ children: [label, required && /* @__PURE__ */ jsx("span", {
87
+ className: "text-destructive",
88
+ children: "*"
89
+ })]
90
+ }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
91
+ }),
92
+ /* @__PURE__ */ jsxs("div", {
93
+ className: "flex gap-2",
94
+ children: [/* @__PURE__ */ jsx("div", {
95
+ className: "flex-1",
96
+ children: /* @__PURE__ */ jsxs(Select, {
97
+ value: value || void 0,
98
+ onValueChange: handleValueChange,
99
+ disabled: disabled || readOnly || isLoading,
100
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
101
+ id: name,
102
+ className: error ? "border-destructive" : "",
103
+ children: /* @__PURE__ */ jsx("span", {
104
+ className: "truncate",
105
+ children: selectedItem ? getDisplayValue(selectedItem) : placeholder || `Select ${label || targetCollection}...`
106
+ })
107
+ }), /* @__PURE__ */ jsx(SelectContent, { children: isLoading ? /* @__PURE__ */ jsx("div", {
108
+ className: "flex items-center justify-center p-4",
109
+ children: /* @__PURE__ */ jsx(Spinner, { className: "h-4 w-4" })
110
+ }) : options && options.length > 0 ? options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
111
+ value: option.id,
112
+ children: getOptionDisplay(option)
113
+ }, option.id)) : /* @__PURE__ */ jsx("div", {
114
+ className: "p-4 text-center text-sm text-muted-foreground",
115
+ children: "No options available"
116
+ }) })]
117
+ })
118
+ }), !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
119
+ value && !required && /* @__PURE__ */ jsx(Button, {
120
+ type: "button",
121
+ variant: "outline",
122
+ size: "icon",
123
+ onClick: handleClear,
124
+ disabled,
125
+ title: "Clear selection",
126
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
127
+ }),
128
+ value && /* @__PURE__ */ jsx(Button, {
129
+ type: "button",
130
+ variant: "outline",
131
+ size: "icon",
132
+ onClick: handleOpenEdit,
133
+ disabled,
134
+ title: `Edit ${label || targetCollection}`,
135
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
136
+ }),
137
+ /* @__PURE__ */ jsx(Button, {
138
+ type: "button",
139
+ variant: "outline",
140
+ size: "icon",
141
+ onClick: handleOpenCreate,
142
+ disabled,
143
+ title: `Create new ${label || targetCollection}`,
144
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
145
+ })
146
+ ] })]
147
+ }),
148
+ error && /* @__PURE__ */ jsx("p", {
149
+ className: "text-sm text-destructive",
150
+ children: error
151
+ }),
152
+ /* @__PURE__ */ jsx(Sheet, {
153
+ open: isSheetOpen,
154
+ onOpenChange: setIsSheetOpen,
155
+ children: /* @__PURE__ */ jsxs(SheetContent, {
156
+ side: "right",
157
+ className: "w-full sm:max-w-lg overflow-y-auto",
158
+ children: [/* @__PURE__ */ jsxs(SheetHeader, { children: [/* @__PURE__ */ jsxs(SheetTitle, { children: [
159
+ sheetMode === "create" ? "Create" : "Edit",
160
+ " ",
161
+ label || targetCollection
162
+ ] }), /* @__PURE__ */ jsx(SheetDescription, { children: sheetMode === "create" ? `Fill in the details to create a new ${label || targetCollection}` : `Update the details of this ${label || targetCollection}` })] }), /* @__PURE__ */ jsx("div", {
163
+ className: "mt-6",
164
+ children: renderFormFields ? renderFormFields(targetCollection, sheetMode === "edit" ? value || void 0 : void 0) : /* @__PURE__ */ jsx("div", {
165
+ className: "rounded-lg border border-dashed p-8 text-center",
166
+ children: /* @__PURE__ */ jsxs("p", {
167
+ className: "text-sm text-muted-foreground",
168
+ children: [
169
+ "Form fields not configured.",
170
+ /* @__PURE__ */ jsx("br", {}),
171
+ "Pass ",
172
+ /* @__PURE__ */ jsx("code", {
173
+ className: "text-xs",
174
+ children: "renderFormFields"
175
+ }),
176
+ " prop to enable create/edit."
177
+ ]
178
+ })
179
+ })
180
+ })]
181
+ })
182
+ })
183
+ ]
184
+ });
185
+ }
186
+
187
+ //#endregion
188
+ export { RelationSelect };