ding-react-admin 1.0.4 → 2.0.4

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 (230) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -201
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +2073 -1202
  5. package/dist/src/components/NavMenuLabel.d.ts +8 -0
  6. package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
  7. package/dist/src/components/NavMenuSearch.d.ts +9 -0
  8. package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
  9. package/dist/src/components/ScrollableArea.d.ts +10 -0
  10. package/dist/src/components/ScrollableArea.d.ts.map +1 -0
  11. package/dist/src/components/index.d.ts +3 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/context/AuthProvider.d.ts +2 -0
  14. package/dist/src/context/AuthProvider.d.ts.map +1 -1
  15. package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
  16. package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
  17. package/dist/src/crud/InlineFormSet.d.ts +2 -17
  18. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceForm.d.ts +2 -10
  20. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  22. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  23. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  24. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  25. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  26. package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
  27. package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
  28. package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
  29. package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
  30. package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
  31. package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
  32. package/dist/src/crud/fields/FileField.d.ts +12 -0
  33. package/dist/src/crud/fields/FileField.d.ts.map +1 -0
  34. package/dist/src/crud/fields/ImageField.d.ts +14 -0
  35. package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
  36. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  37. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  38. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  39. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  40. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  41. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  42. package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
  43. package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
  44. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
  45. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
  46. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  47. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  48. package/dist/src/crud/index.d.ts +27 -9
  49. package/dist/src/crud/index.d.ts.map +1 -1
  50. package/dist/src/crud/types.d.ts +21 -7
  51. package/dist/src/crud/types.d.ts.map +1 -1
  52. package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
  53. package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
  54. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
  55. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
  56. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
  57. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
  58. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  59. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  60. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  61. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  62. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  63. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  64. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  65. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  66. package/dist/src/crud/utils/getFormValue.d.ts +3 -0
  67. package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
  68. package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
  69. package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
  70. package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
  71. package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
  72. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +8 -0
  73. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
  74. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  75. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  76. package/dist/src/crud/utils/setFormValue.d.ts +3 -0
  77. package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
  78. package/dist/src/crud/utils/toFormData.d.ts +13 -0
  79. package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
  80. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  81. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  82. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  83. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  84. package/dist/src/crud/utils/useFormRecord.d.ts +44 -0
  85. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
  86. package/dist/src/data/createRestResourceHandlers.d.ts +5 -5
  87. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
  88. package/dist/src/data/dataProviderTypes.d.ts +6 -5
  89. package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
  90. package/dist/src/data/parseFormErrorHelpers.d.ts +23 -6
  91. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  92. package/dist/src/data/resourceHandlers.d.ts +1 -1
  93. package/dist/src/data/resourceHandlers.d.ts.map +1 -1
  94. package/dist/src/index.d.ts +4 -4
  95. package/dist/src/index.d.ts.map +1 -1
  96. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  97. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  98. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  99. package/dist/src/layouts/navFilter.d.ts +5 -0
  100. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  101. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  102. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  103. package/dist/src/types.d.ts +12 -0
  104. package/dist/src/types.d.ts.map +1 -1
  105. package/package.json +79 -59
  106. package/src/app/AdminApp.tsx +34 -34
  107. package/src/components/AuthAlternateLink.tsx +23 -23
  108. package/src/components/DensitySwitch.tsx +31 -31
  109. package/src/components/NavMenuLabel.tsx +26 -0
  110. package/src/components/NavMenuSearch.tsx +76 -0
  111. package/src/components/ScrollableArea.tsx +46 -0
  112. package/src/components/ThemeSwitch.tsx +28 -28
  113. package/src/components/ThemeToolbar.tsx +34 -34
  114. package/src/components/index.ts +7 -4
  115. package/src/components/navMenu.css +21 -0
  116. package/src/components/scrollable.css +28 -0
  117. package/src/context/AppThemeProvider.tsx +128 -128
  118. package/src/context/AuthProvider.tsx +89 -77
  119. package/src/context/DataProvider.tsx +30 -30
  120. package/src/context/PermissionsProvider.tsx +57 -57
  121. package/src/crud/FilterBar.tsx +36 -36
  122. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  123. package/src/crud/FormSteps.tsx +135 -135
  124. package/src/crud/FormTabs.tsx +126 -126
  125. package/src/crud/InlineFormSet.tsx +162 -219
  126. package/src/crud/ListActionsBar.tsx +106 -106
  127. package/src/crud/ResourceForm.tsx +83 -166
  128. package/src/crud/ResourceFormModal.tsx +75 -133
  129. package/src/crud/ResourceList.tsx +1 -0
  130. package/src/crud/ResourceRecordForm.tsx +166 -0
  131. package/src/crud/columns/BooleanColumn.tsx +32 -32
  132. package/src/crud/columns/CustomColumn.tsx +33 -33
  133. package/src/crud/columns/DateColumn.tsx +28 -28
  134. package/src/crud/columns/ImageColumn.tsx +58 -0
  135. package/src/crud/columns/NumberColumn.tsx +31 -31
  136. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  137. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  138. package/src/crud/columns/TextColumn.tsx +38 -38
  139. package/src/crud/context/FilterContext.tsx +71 -71
  140. package/src/crud/context/FormContext.tsx +39 -39
  141. package/src/crud/context/FormSectionContext.tsx +22 -22
  142. package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
  143. package/src/crud/context/ListContext.tsx +93 -93
  144. package/src/crud/context/PayloadFieldsContext.tsx +62 -0
  145. package/src/crud/fields/BooleanField.tsx +38 -38
  146. package/src/crud/fields/DateField.tsx +53 -53
  147. package/src/crud/fields/FieldWrapper.tsx +87 -87
  148. package/src/crud/fields/FileField.tsx +123 -0
  149. package/src/crud/fields/ImageField.tsx +120 -0
  150. package/src/crud/fields/NumberField.tsx +52 -52
  151. package/src/crud/fields/PasswordField.tsx +99 -99
  152. package/src/crud/fields/ReferenceField.tsx +237 -96
  153. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  154. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  155. package/src/crud/fields/SelectField.tsx +48 -48
  156. package/src/crud/fields/TextField.tsx +46 -46
  157. package/src/crud/fields/uploadFieldUtils.ts +21 -0
  158. package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
  159. package/src/crud/filters/BooleanFilter.tsx +38 -38
  160. package/src/crud/filters/DateFilter.tsx +35 -35
  161. package/src/crud/filters/NumberFilter.tsx +33 -33
  162. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  163. package/src/crud/filters/SelectFilter.tsx +44 -44
  164. package/src/crud/filters/TextFilter.tsx +36 -36
  165. package/src/crud/index.ts +99 -90
  166. package/src/crud/types.ts +146 -132
  167. package/src/crud/utils/buildFormPayload.ts +24 -0
  168. package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
  169. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
  170. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  171. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  172. package/src/crud/utils/formErrors.test.ts +168 -0
  173. package/src/crud/utils/formErrors.ts +138 -38
  174. package/src/crud/utils/formSectionErrors.ts +85 -85
  175. package/src/crud/utils/{getByPath.ts → getFormValue.ts} +13 -13
  176. package/src/crud/utils/hasUploadValues.ts +9 -0
  177. package/src/crud/utils/nestedFieldPath.ts +8 -0
  178. package/src/crud/utils/prepareFormSubmitBody.ts +22 -0
  179. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  180. package/src/crud/utils/{setByPath.ts → setFormValue.ts} +18 -18
  181. package/src/crud/utils/sortQueryParam.ts +37 -37
  182. package/src/crud/utils/toFormData.ts +79 -0
  183. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  184. package/src/crud/utils/useAbortableEffect.ts +17 -17
  185. package/src/crud/utils/useChoices.ts +330 -182
  186. package/src/crud/utils/useFormRecord.ts +179 -0
  187. package/src/crud/utils/useListQueryState.ts +191 -191
  188. package/src/data/abortError.test.ts +19 -19
  189. package/src/data/abortError.ts +10 -10
  190. package/src/data/createMemoryResourceHandlers.ts +78 -78
  191. package/src/data/createRestResourceHandlers.ts +63 -57
  192. package/src/data/dataProviderTypes.ts +7 -5
  193. package/src/data/dataUtils.test.ts +228 -108
  194. package/src/data/inMemoryList.ts +97 -97
  195. package/src/data/parseFormErrorHelpers.test.ts +201 -84
  196. package/src/data/parseFormErrorHelpers.ts +376 -224
  197. package/src/data/resourceHandlers.ts +1 -1
  198. package/src/data/sortHelpers.ts +48 -48
  199. package/src/index.ts +211 -195
  200. package/src/layouts/AdminLayout.tsx +530 -381
  201. package/src/layouts/AuthPageLayout.tsx +87 -81
  202. package/src/layouts/navFilter.ts +49 -0
  203. package/src/layouts/navMenuItems.tsx +44 -0
  204. package/src/pages/LoginPage.tsx +80 -80
  205. package/src/pages/PlaceholderPage.tsx +9 -9
  206. package/src/permissions/resourcePermissions.ts +47 -47
  207. package/src/router/createAdminRouter.tsx +64 -64
  208. package/src/router/guards.tsx +63 -63
  209. package/src/router/routeAccess.ts +89 -89
  210. package/src/types.ts +10 -0
  211. package/src/vite-env.d.ts +1 -1
  212. package/dist/src/crud/context/SubmitFieldsContext.d.ts +0 -18
  213. package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +0 -1
  214. package/dist/src/crud/utils/getByPath.d.ts +0 -3
  215. package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
  216. package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
  217. package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
  218. package/dist/src/crud/utils/inlineFieldName.d.ts +0 -3
  219. package/dist/src/crud/utils/inlineFieldName.d.ts.map +0 -1
  220. package/dist/src/crud/utils/pickBySources.d.ts +0 -6
  221. package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
  222. package/dist/src/crud/utils/setByPath.d.ts +0 -3
  223. package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
  224. package/dist/src/crud/utils/useResourceFormData.d.ts +0 -47
  225. package/dist/src/crud/utils/useResourceFormData.d.ts.map +0 -1
  226. package/src/crud/context/SubmitFieldsContext.tsx +0 -62
  227. package/src/crud/utils/inlineArrayName.ts +0 -4
  228. package/src/crud/utils/inlineFieldName.ts +0 -8
  229. package/src/crud/utils/pickBySources.ts +0 -24
  230. package/src/crud/utils/useResourceFormData.ts +0 -231
@@ -1,76 +1,200 @@
1
- import { Select } from "antd";
2
- import { useMemo, useState } from "react";
3
- import type { BaseSourceProps, FieldRules, ReferenceProps } from "../types";
4
- import { useChoices } from "../utils/useChoices";
5
- import { FieldWrapper } from "./FieldWrapper";
6
-
7
- export type ReferenceManyFieldProps = BaseSourceProps &
8
- ReferenceProps & {
9
- name?: string;
10
- required?: boolean;
11
- rules?: FieldRules;
12
- search?: boolean;
13
- allowClear?: boolean;
14
- hideLabel?: boolean;
15
- };
16
-
17
- export function ReferenceManyField({
18
- source,
19
- name,
20
- label,
21
- reference,
22
- choices,
23
- optionLabel = "name",
24
- optionValue = "id",
25
- required,
26
- rules,
27
- search,
28
- allowClear = true,
29
- hideLabel,
30
- }: ReferenceManyFieldProps) {
31
- const [searchText, setSearchText] = useState<string | undefined>();
32
- const { options, loading } = useChoices(
33
- choices,
34
- reference,
35
- optionLabel,
36
- optionValue,
37
- search ? searchText : undefined,
38
- );
39
-
40
- const selectOptions = useMemo(
41
- () =>
42
- options.map((o) => ({
43
- label: o.label,
44
- value: o.value as string | number,
45
- })),
46
- [options],
47
- );
48
-
49
- return (
50
- <FieldWrapper
51
- source={source}
52
- name={name}
53
- label={label}
54
- required={required}
55
- rules={rules}
56
- hideLabel={hideLabel}
57
- >
58
- {({ value, onChange, disabled }) => (
59
- <Select
60
- mode="multiple"
61
- value={(value as (string | number)[] | undefined) ?? []}
62
- onChange={onChange}
63
- options={selectOptions}
64
- loading={loading}
65
- showSearch={search}
66
- filterOption={search ? false : undefined}
67
- onSearch={search ? setSearchText : undefined}
68
- allowClear={allowClear}
69
- disabled={disabled}
70
- optionFilterProp="label"
71
- style={{ width: "100%" }}
72
- />
73
- )}
74
- </FieldWrapper>
75
- );
76
- }
1
+ import { Select } from "antd";
2
+ import { useMemo, useState } from "react";
3
+ import { useWatch } from "react-hook-form";
4
+ import type { BaseSourceProps, FieldRules, ReferenceProps } from "../types";
5
+ import { valuesAsIds } from "../utils/choiceSelectionUtils";
6
+ import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
7
+ import { useChoices } from "../utils/useChoices";
8
+ import { FieldWrapper } from "./FieldWrapper";
9
+ import { ReferenceInputActions } from "./ReferenceInputActions";
10
+ import type { ResourcePermissions } from "../../permissions/resourcePermissions";
11
+ import type { ReactNode } from "react";
12
+
13
+ export type ReferenceManyFieldProps = BaseSourceProps &
14
+ ReferenceProps & {
15
+ name?: string;
16
+ required?: boolean;
17
+ rules?: FieldRules;
18
+ search?: boolean;
19
+ allowClear?: boolean;
20
+ hideLabel?: boolean;
21
+ referenceForm?: ReactNode;
22
+ referencePermissions?: ResourcePermissions;
23
+ referenceTitle?: string;
24
+ referenceDefaultValues?: Record<string, unknown>;
25
+ referenceModalWidth?: number;
26
+ /** When false, hide add button even if catalog defines a form. Default true. */
27
+ referenceActions?: boolean;
28
+ };
29
+
30
+ type ReferenceManyFieldSelectProps = Omit<
31
+ ReferenceManyFieldProps,
32
+ "source" | "label" | "required" | "rules" | "hideLabel" | "name"
33
+ > & {
34
+ value: unknown;
35
+ onChange: (value: unknown) => void;
36
+ disabled?: boolean;
37
+ selectedRecords?: Record<string, unknown> | Record<string, unknown>[];
38
+ };
39
+
40
+ function ReferenceManyFieldSelect({
41
+ reference,
42
+ choices,
43
+ optionLabel = "name",
44
+ optionValue = "id",
45
+ search,
46
+ allowClear = true,
47
+ lazy = true,
48
+ fetchSelected = true,
49
+ value,
50
+ onChange,
51
+ disabled,
52
+ selectedRecords,
53
+ referenceForm,
54
+ referencePermissions,
55
+ referenceTitle,
56
+ referenceDefaultValues,
57
+ referenceModalWidth,
58
+ referenceActions = true,
59
+ }: ReferenceManyFieldSelectProps) {
60
+ const [searchText, setSearchText] = useState<string | undefined>();
61
+ const [dropdownOpen, setDropdownOpen] = useState(false);
62
+ const active = dropdownOpen || Boolean(searchText);
63
+ const selectValue = valuesAsIds(value, optionValue);
64
+
65
+ const { options, loading, reload } = useChoices(
66
+ choices,
67
+ reference,
68
+ optionLabel,
69
+ optionValue,
70
+ search ? searchText : undefined,
71
+ {
72
+ lazy,
73
+ active,
74
+ selectedValues: value,
75
+ selectedRecords,
76
+ fetchSelected,
77
+ },
78
+ );
79
+
80
+ const selectOptions = useMemo(
81
+ () =>
82
+ options.map((o) => ({
83
+ label: o.label,
84
+ value: o.value as string | number,
85
+ })),
86
+ [options],
87
+ );
88
+
89
+ const select = (
90
+ <Select
91
+ mode="multiple"
92
+ value={selectValue}
93
+ onChange={onChange}
94
+ options={selectOptions}
95
+ loading={loading}
96
+ notFoundContent={referenceSelectNotFoundContent(loading)}
97
+ showSearch={search}
98
+ filterOption={search ? false : undefined}
99
+ onSearch={search ? setSearchText : undefined}
100
+ onDropdownVisibleChange={(open) => {
101
+ setDropdownOpen(open);
102
+ if (!open) setSearchText(undefined);
103
+ }}
104
+ allowClear={allowClear}
105
+ disabled={disabled}
106
+ optionFilterProp="label"
107
+ style={{ width: "100%" }}
108
+ />
109
+ );
110
+
111
+ if (!referenceActions) return select;
112
+
113
+ return (
114
+ <div style={{ display: "flex", gap: 8, width: "100%", alignItems: "flex-start" }}>
115
+ <div style={{ flex: 1, minWidth: 0 }}>{select}</div>
116
+ <ReferenceInputActions
117
+ reference={reference}
118
+ referenceForm={referenceForm}
119
+ referencePermissions={referencePermissions}
120
+ referenceTitle={referenceTitle}
121
+ referenceDefaultValues={referenceDefaultValues}
122
+ referenceModalWidth={referenceModalWidth}
123
+ disabled={disabled}
124
+ onCreated={(record) => {
125
+ const id = record[optionValue];
126
+ const current = Array.isArray(selectValue) ? selectValue : [];
127
+ if (current.some((v) => v === id)) {
128
+ void reload();
129
+ return;
130
+ }
131
+ onChange([...current, id]);
132
+ void reload();
133
+ }}
134
+ />
135
+ </div>
136
+ );
137
+ }
138
+
139
+ export function ReferenceManyField({
140
+ source,
141
+ name,
142
+ label,
143
+ reference,
144
+ choices,
145
+ optionLabel = "name",
146
+ optionValue = "id",
147
+ required,
148
+ rules,
149
+ search,
150
+ allowClear = true,
151
+ hideLabel,
152
+ lazy = true,
153
+ recordSource,
154
+ fetchSelected = true,
155
+ referenceForm,
156
+ referencePermissions,
157
+ referenceTitle,
158
+ referenceDefaultValues,
159
+ referenceModalWidth,
160
+ referenceActions = true,
161
+ }: ReferenceManyFieldProps) {
162
+ const embeddedRecords = useWatch({
163
+ name: recordSource ?? "",
164
+ disabled: !recordSource,
165
+ }) as Record<string, unknown> | Record<string, unknown>[] | undefined;
166
+
167
+ return (
168
+ <FieldWrapper
169
+ source={source}
170
+ name={name}
171
+ label={label}
172
+ required={required}
173
+ rules={rules}
174
+ hideLabel={hideLabel}
175
+ >
176
+ {({ value, onChange, disabled }) => (
177
+ <ReferenceManyFieldSelect
178
+ reference={reference}
179
+ choices={choices}
180
+ optionLabel={optionLabel}
181
+ optionValue={optionValue}
182
+ search={search}
183
+ allowClear={allowClear}
184
+ lazy={lazy}
185
+ fetchSelected={fetchSelected}
186
+ value={value}
187
+ onChange={onChange}
188
+ disabled={disabled}
189
+ selectedRecords={recordSource ? embeddedRecords : undefined}
190
+ referenceForm={referenceForm}
191
+ referencePermissions={referencePermissions}
192
+ referenceTitle={referenceTitle}
193
+ referenceDefaultValues={referenceDefaultValues}
194
+ referenceModalWidth={referenceModalWidth}
195
+ referenceActions={referenceActions}
196
+ />
197
+ )}
198
+ </FieldWrapper>
199
+ );
200
+ }
@@ -1,48 +1,48 @@
1
- import { Select } from "antd";
2
- import type { BaseSourceProps, ChoiceOption, FieldRules } from "../types";
3
- import { FieldWrapper } from "./FieldWrapper";
4
-
5
- export type SelectFieldProps = BaseSourceProps & {
6
- name?: string;
7
- required?: boolean;
8
- rules?: FieldRules;
9
- choices: ChoiceOption[];
10
- mode?: "multiple";
11
- allowClear?: boolean;
12
- hideLabel?: boolean;
13
- };
14
-
15
- export function SelectField({
16
- source,
17
- name,
18
- label,
19
- required,
20
- rules,
21
- choices,
22
- mode,
23
- allowClear,
24
- hideLabel,
25
- }: SelectFieldProps) {
26
- return (
27
- <FieldWrapper
28
- source={source}
29
- name={name}
30
- label={label}
31
- required={required}
32
- rules={rules}
33
- hideLabel={hideLabel}
34
- >
35
- {({ value, onChange, disabled }) => (
36
- <Select
37
- value={value as string | number | (string | number)[] | undefined}
38
- onChange={onChange}
39
- options={choices}
40
- mode={mode}
41
- allowClear={allowClear}
42
- disabled={disabled}
43
- style={{ width: "100%" }}
44
- />
45
- )}
46
- </FieldWrapper>
47
- );
48
- }
1
+ import { Select } from "antd";
2
+ import type { BaseSourceProps, ChoiceOption, FieldRules } from "../types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+
5
+ export type SelectFieldProps = BaseSourceProps & {
6
+ name?: string;
7
+ required?: boolean;
8
+ rules?: FieldRules;
9
+ choices: ChoiceOption[];
10
+ mode?: "multiple";
11
+ allowClear?: boolean;
12
+ hideLabel?: boolean;
13
+ };
14
+
15
+ export function SelectField({
16
+ source,
17
+ name,
18
+ label,
19
+ required,
20
+ rules,
21
+ choices,
22
+ mode,
23
+ allowClear,
24
+ hideLabel,
25
+ }: SelectFieldProps) {
26
+ return (
27
+ <FieldWrapper
28
+ source={source}
29
+ name={name}
30
+ label={label}
31
+ required={required}
32
+ rules={rules}
33
+ hideLabel={hideLabel}
34
+ >
35
+ {({ value, onChange, disabled }) => (
36
+ <Select
37
+ value={value as string | number | (string | number)[] | undefined}
38
+ onChange={onChange}
39
+ options={choices}
40
+ mode={mode}
41
+ allowClear={allowClear}
42
+ disabled={disabled}
43
+ style={{ width: "100%" }}
44
+ />
45
+ )}
46
+ </FieldWrapper>
47
+ );
48
+ }
@@ -1,46 +1,46 @@
1
- import { Input } from "antd";
2
- import type { CSSProperties } from "react";
3
- import type { BaseSourceProps, FieldRules } from "../types";
4
- import { FieldWrapper } from "./FieldWrapper";
5
-
6
- export type TextFieldProps = BaseSourceProps & {
7
- name?: string;
8
- required?: boolean;
9
- rules?: FieldRules;
10
- placeholder?: string;
11
- inputStyle?: CSSProperties;
12
- hideLabel?: boolean;
13
- };
14
-
15
- export function TextField({
16
- source,
17
- name,
18
- label,
19
- required,
20
- rules,
21
- placeholder,
22
- inputStyle,
23
- hideLabel,
24
- }: TextFieldProps) {
25
- return (
26
- <FieldWrapper
27
- source={source}
28
- name={name}
29
- label={label}
30
- required={required}
31
- rules={rules}
32
- hideLabel={hideLabel}
33
- >
34
- {({ value, onChange, onBlur, disabled }) => (
35
- <Input
36
- value={value as string | undefined}
37
- onChange={(e) => onChange(e.target.value)}
38
- onBlur={onBlur}
39
- placeholder={placeholder}
40
- disabled={disabled}
41
- style={inputStyle}
42
- />
43
- )}
44
- </FieldWrapper>
45
- );
46
- }
1
+ import { Input } from "antd";
2
+ import type { CSSProperties } from "react";
3
+ import type { BaseSourceProps, FieldRules } from "../types";
4
+ import { FieldWrapper } from "./FieldWrapper";
5
+
6
+ export type TextFieldProps = BaseSourceProps & {
7
+ name?: string;
8
+ required?: boolean;
9
+ rules?: FieldRules;
10
+ placeholder?: string;
11
+ inputStyle?: CSSProperties;
12
+ hideLabel?: boolean;
13
+ };
14
+
15
+ export function TextField({
16
+ source,
17
+ name,
18
+ label,
19
+ required,
20
+ rules,
21
+ placeholder,
22
+ inputStyle,
23
+ hideLabel,
24
+ }: TextFieldProps) {
25
+ return (
26
+ <FieldWrapper
27
+ source={source}
28
+ name={name}
29
+ label={label}
30
+ required={required}
31
+ rules={rules}
32
+ hideLabel={hideLabel}
33
+ >
34
+ {({ value, onChange, onBlur, disabled }) => (
35
+ <Input
36
+ value={value as string | undefined}
37
+ onChange={(e) => onChange(e.target.value)}
38
+ onBlur={onBlur}
39
+ placeholder={placeholder}
40
+ disabled={disabled}
41
+ style={inputStyle}
42
+ />
43
+ )}
44
+ </FieldWrapper>
45
+ );
46
+ }
@@ -0,0 +1,21 @@
1
+ /** Form value for file/image fields — backend-agnostic. */
2
+ export type UploadFieldValue = File | string | null | undefined;
3
+
4
+ export function hasUploadValue(value: UploadFieldValue): boolean {
5
+ if (value instanceof File) return true;
6
+ return typeof value === "string" && value.length > 0;
7
+ }
8
+
9
+ export function getUploadFileName(value: UploadFieldValue): string | undefined {
10
+ if (value instanceof File) return value.name;
11
+ if (typeof value === "string" && value.length > 0) {
12
+ try {
13
+ const pathname = new URL(value, "http://local").pathname;
14
+ const name = pathname.split("/").filter(Boolean).pop();
15
+ return name || value;
16
+ } catch {
17
+ return value.split("/").filter(Boolean).pop() || value;
18
+ }
19
+ }
20
+ return undefined;
21
+ }
@@ -0,0 +1,20 @@
1
+ import { useEffect, useState } from "react";
2
+ import type { UploadFieldValue } from "./uploadFieldUtils";
3
+
4
+ /** Preview URL for uploads — `URL.createObjectURL` for `File`, direct URL for existing strings. */
5
+ export function useUploadPreviewUrl(value: UploadFieldValue): string | undefined {
6
+ const [objectUrl, setObjectUrl] = useState<string | undefined>();
7
+
8
+ useEffect(() => {
9
+ if (value instanceof File) {
10
+ const url = URL.createObjectURL(value);
11
+ setObjectUrl(url);
12
+ return () => URL.revokeObjectURL(url);
13
+ }
14
+ setObjectUrl(undefined);
15
+ }, [value]);
16
+
17
+ if (value instanceof File) return objectUrl;
18
+ if (typeof value === "string" && value.length > 0) return value;
19
+ return undefined;
20
+ }
@@ -1,38 +1,38 @@
1
- import { Select } from "antd";
2
- import { useMemo } from "react";
3
- import type { BaseSourceProps } from "../types";
4
- import { useRegisterFilter } from "../context/FilterContext";
5
-
6
- export type BooleanFilterProps = BaseSourceProps;
7
-
8
- export function BooleanFilter({ source, label }: BooleanFilterProps) {
9
- const def = useMemo(
10
- () => ({
11
- key: source,
12
- source,
13
- label,
14
- render: ({
15
- value,
16
- onChange,
17
- }: {
18
- value: unknown;
19
- onChange: (value: unknown) => void;
20
- }) => (
21
- <Select
22
- allowClear
23
- placeholder={label ?? source}
24
- value={value as boolean | undefined}
25
- onChange={(v) => onChange(v)}
26
- options={[
27
- { label: "Yes", value: true },
28
- { label: "No", value: false },
29
- ]}
30
- style={{ minWidth: 100 }}
31
- />
32
- ),
33
- }),
34
- [source, label],
35
- );
36
- useRegisterFilter(def);
37
- return null;
38
- }
1
+ import { Select } from "antd";
2
+ import { useMemo } from "react";
3
+ import type { BaseSourceProps } from "../types";
4
+ import { useRegisterFilter } from "../context/FilterContext";
5
+
6
+ export type BooleanFilterProps = BaseSourceProps;
7
+
8
+ export function BooleanFilter({ source, label }: BooleanFilterProps) {
9
+ const def = useMemo(
10
+ () => ({
11
+ key: source,
12
+ source,
13
+ label,
14
+ render: ({
15
+ value,
16
+ onChange,
17
+ }: {
18
+ value: unknown;
19
+ onChange: (value: unknown) => void;
20
+ }) => (
21
+ <Select
22
+ allowClear
23
+ placeholder={label ?? source}
24
+ value={value as boolean | undefined}
25
+ onChange={(v) => onChange(v)}
26
+ options={[
27
+ { label: "Yes", value: true },
28
+ { label: "No", value: false },
29
+ ]}
30
+ style={{ minWidth: 100 }}
31
+ />
32
+ ),
33
+ }),
34
+ [source, label],
35
+ );
36
+ useRegisterFilter(def);
37
+ return null;
38
+ }
@@ -1,35 +1,35 @@
1
- import { DatePicker } from "antd";
2
- import dayjs from "dayjs";
3
- import { useMemo } from "react";
4
- import type { BaseSourceProps } from "../types";
5
- import { useRegisterFilter } from "../context/FilterContext";
6
-
7
- export type DateFilterProps = BaseSourceProps;
8
-
9
- export function DateFilter({ source, label }: DateFilterProps) {
10
- const def = useMemo(
11
- () => ({
12
- key: source,
13
- source,
14
- label,
15
- render: ({
16
- value,
17
- onChange,
18
- }: {
19
- value: unknown;
20
- onChange: (value: unknown) => void;
21
- }) => (
22
- <DatePicker
23
- allowClear
24
- placeholder={label ?? source}
25
- value={value ? dayjs(String(value)) : null}
26
- onChange={(d) => onChange(d ? d.format("YYYY-MM-DD") : undefined)}
27
- style={{ minWidth: 160 }}
28
- />
29
- ),
30
- }),
31
- [source, label],
32
- );
33
- useRegisterFilter(def);
34
- return null;
35
- }
1
+ import { DatePicker } from "antd";
2
+ import dayjs from "dayjs";
3
+ import { useMemo } from "react";
4
+ import type { BaseSourceProps } from "../types";
5
+ import { useRegisterFilter } from "../context/FilterContext";
6
+
7
+ export type DateFilterProps = BaseSourceProps;
8
+
9
+ export function DateFilter({ source, label }: DateFilterProps) {
10
+ const def = useMemo(
11
+ () => ({
12
+ key: source,
13
+ source,
14
+ label,
15
+ render: ({
16
+ value,
17
+ onChange,
18
+ }: {
19
+ value: unknown;
20
+ onChange: (value: unknown) => void;
21
+ }) => (
22
+ <DatePicker
23
+ allowClear
24
+ placeholder={label ?? source}
25
+ value={value ? dayjs(String(value)) : null}
26
+ onChange={(d) => onChange(d ? d.format("YYYY-MM-DD") : undefined)}
27
+ style={{ minWidth: 160 }}
28
+ />
29
+ ),
30
+ }),
31
+ [source, label],
32
+ );
33
+ useRegisterFilter(def);
34
+ return null;
35
+ }