ding-react-admin 1.0.0

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +3067 -0
  5. package/dist/src/app/AdminApp.d.ts +7 -0
  6. package/dist/src/app/AdminApp.d.ts.map +1 -0
  7. package/dist/src/components/AuthAlternateLink.d.ts +7 -0
  8. package/dist/src/components/AuthAlternateLink.d.ts.map +1 -0
  9. package/dist/src/components/DensitySwitch.d.ts +2 -0
  10. package/dist/src/components/DensitySwitch.d.ts.map +1 -0
  11. package/dist/src/components/ThemeSwitch.d.ts +2 -0
  12. package/dist/src/components/ThemeSwitch.d.ts.map +1 -0
  13. package/dist/src/components/ThemeToolbar.d.ts +2 -0
  14. package/dist/src/components/ThemeToolbar.d.ts.map +1 -0
  15. package/dist/src/components/index.d.ts +5 -0
  16. package/dist/src/components/index.d.ts.map +1 -0
  17. package/dist/src/context/AppThemeProvider.d.ts +13 -0
  18. package/dist/src/context/AppThemeProvider.d.ts.map +1 -0
  19. package/dist/src/context/AuthProvider.d.ts +18 -0
  20. package/dist/src/context/AuthProvider.d.ts.map +1 -0
  21. package/dist/src/context/DataProvider.d.ts +10 -0
  22. package/dist/src/context/DataProvider.d.ts.map +1 -0
  23. package/dist/src/context/PermissionsProvider.d.ts +18 -0
  24. package/dist/src/context/PermissionsProvider.d.ts.map +1 -0
  25. package/dist/src/crud/FilterBar.d.ts +6 -0
  26. package/dist/src/crud/FilterBar.d.ts.map +1 -0
  27. package/dist/src/crud/FormSteps.d.ts +20 -0
  28. package/dist/src/crud/FormSteps.d.ts.map +1 -0
  29. package/dist/src/crud/FormTabs.d.ts +13 -0
  30. package/dist/src/crud/FormTabs.d.ts.map +1 -0
  31. package/dist/src/crud/InlineFormSet.d.ts +29 -0
  32. package/dist/src/crud/InlineFormSet.d.ts.map +1 -0
  33. package/dist/src/crud/ListActionsBar.d.ts +15 -0
  34. package/dist/src/crud/ListActionsBar.d.ts.map +1 -0
  35. package/dist/src/crud/ResourceForm.d.ts +21 -0
  36. package/dist/src/crud/ResourceForm.d.ts.map +1 -0
  37. package/dist/src/crud/ResourceFormModal.d.ts +10 -0
  38. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -0
  39. package/dist/src/crud/ResourceList.d.ts +38 -0
  40. package/dist/src/crud/ResourceList.d.ts.map +1 -0
  41. package/dist/src/crud/columns/BooleanColumn.d.ts +6 -0
  42. package/dist/src/crud/columns/BooleanColumn.d.ts.map +1 -0
  43. package/dist/src/crud/columns/CustomColumn.d.ts +9 -0
  44. package/dist/src/crud/columns/CustomColumn.d.ts.map +1 -0
  45. package/dist/src/crud/columns/DateColumn.d.ts +6 -0
  46. package/dist/src/crud/columns/DateColumn.d.ts.map +1 -0
  47. package/dist/src/crud/columns/NumberColumn.d.ts +6 -0
  48. package/dist/src/crud/columns/NumberColumn.d.ts.map +1 -0
  49. package/dist/src/crud/columns/ReferenceColumn.d.ts +6 -0
  50. package/dist/src/crud/columns/ReferenceColumn.d.ts.map +1 -0
  51. package/dist/src/crud/columns/ReferenceManyColumn.d.ts +6 -0
  52. package/dist/src/crud/columns/ReferenceManyColumn.d.ts.map +1 -0
  53. package/dist/src/crud/columns/TextColumn.d.ts +7 -0
  54. package/dist/src/crud/columns/TextColumn.d.ts.map +1 -0
  55. package/dist/src/crud/context/FilterContext.d.ts +17 -0
  56. package/dist/src/crud/context/FilterContext.d.ts.map +1 -0
  57. package/dist/src/crud/context/FormContext.d.ts +16 -0
  58. package/dist/src/crud/context/FormContext.d.ts.map +1 -0
  59. package/dist/src/crud/context/FormFieldsContext.d.ts +14 -0
  60. package/dist/src/crud/context/FormFieldsContext.d.ts.map +1 -0
  61. package/dist/src/crud/context/FormSectionContext.d.ts +7 -0
  62. package/dist/src/crud/context/FormSectionContext.d.ts.map +1 -0
  63. package/dist/src/crud/context/InlineFormContext.d.ts +16 -0
  64. package/dist/src/crud/context/InlineFormContext.d.ts.map +1 -0
  65. package/dist/src/crud/context/ListContext.d.ts +22 -0
  66. package/dist/src/crud/context/ListContext.d.ts.map +1 -0
  67. package/dist/src/crud/fields/BooleanField.d.ts +7 -0
  68. package/dist/src/crud/fields/BooleanField.d.ts.map +1 -0
  69. package/dist/src/crud/fields/DateField.d.ts +8 -0
  70. package/dist/src/crud/fields/DateField.d.ts.map +1 -0
  71. package/dist/src/crud/fields/FieldWrapper.d.ts +29 -0
  72. package/dist/src/crud/fields/FieldWrapper.d.ts.map +1 -0
  73. package/dist/src/crud/fields/NumberField.d.ts +12 -0
  74. package/dist/src/crud/fields/NumberField.d.ts.map +1 -0
  75. package/dist/src/crud/fields/PasswordField.d.ts +11 -0
  76. package/dist/src/crud/fields/PasswordField.d.ts.map +1 -0
  77. package/dist/src/crud/fields/ReferenceField.d.ts +18 -0
  78. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -0
  79. package/dist/src/crud/fields/ReferenceManyField.d.ts +9 -0
  80. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -0
  81. package/dist/src/crud/fields/SelectField.d.ts +10 -0
  82. package/dist/src/crud/fields/SelectField.d.ts.map +1 -0
  83. package/dist/src/crud/fields/TextField.d.ts +10 -0
  84. package/dist/src/crud/fields/TextField.d.ts.map +1 -0
  85. package/dist/src/crud/fields/useInlineOrFormField.d.ts +10 -0
  86. package/dist/src/crud/fields/useInlineOrFormField.d.ts.map +1 -0
  87. package/dist/src/crud/filters/BooleanFilter.d.ts +4 -0
  88. package/dist/src/crud/filters/BooleanFilter.d.ts.map +1 -0
  89. package/dist/src/crud/filters/DateFilter.d.ts +4 -0
  90. package/dist/src/crud/filters/DateFilter.d.ts.map +1 -0
  91. package/dist/src/crud/filters/NumberFilter.d.ts +4 -0
  92. package/dist/src/crud/filters/NumberFilter.d.ts.map +1 -0
  93. package/dist/src/crud/filters/ReferenceFilter.d.ts +13 -0
  94. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -0
  95. package/dist/src/crud/filters/SelectFilter.d.ts +7 -0
  96. package/dist/src/crud/filters/SelectFilter.d.ts.map +1 -0
  97. package/dist/src/crud/filters/TextFilter.d.ts +6 -0
  98. package/dist/src/crud/filters/TextFilter.d.ts.map +1 -0
  99. package/dist/src/crud/index.d.ts +44 -0
  100. package/dist/src/crud/index.d.ts.map +1 -0
  101. package/dist/src/crud/types.d.ts +99 -0
  102. package/dist/src/crud/types.d.ts.map +1 -0
  103. package/dist/src/crud/utils/formErrors.d.ts +9 -0
  104. package/dist/src/crud/utils/formErrors.d.ts.map +1 -0
  105. package/dist/src/crud/utils/formSectionErrors.d.ts +6 -0
  106. package/dist/src/crud/utils/formSectionErrors.d.ts.map +1 -0
  107. package/dist/src/crud/utils/getByPath.d.ts +3 -0
  108. package/dist/src/crud/utils/getByPath.d.ts.map +1 -0
  109. package/dist/src/crud/utils/inlineArrayName.d.ts +3 -0
  110. package/dist/src/crud/utils/inlineArrayName.d.ts.map +1 -0
  111. package/dist/src/crud/utils/pickBySources.d.ts +6 -0
  112. package/dist/src/crud/utils/pickBySources.d.ts.map +1 -0
  113. package/dist/src/crud/utils/setByPath.d.ts +3 -0
  114. package/dist/src/crud/utils/setByPath.d.ts.map +1 -0
  115. package/dist/src/crud/utils/sortQueryParam.d.ts +7 -0
  116. package/dist/src/crud/utils/sortQueryParam.d.ts.map +1 -0
  117. package/dist/src/crud/utils/useChoices.d.ts +10 -0
  118. package/dist/src/crud/utils/useChoices.d.ts.map +1 -0
  119. package/dist/src/crud/utils/useListQueryState.d.ts +22 -0
  120. package/dist/src/crud/utils/useListQueryState.d.ts.map +1 -0
  121. package/dist/src/data/createMemoryResourceHandlers.d.ts +23 -0
  122. package/dist/src/data/createMemoryResourceHandlers.d.ts.map +1 -0
  123. package/dist/src/data/createRestResourceHandlers.d.ts +22 -0
  124. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -0
  125. package/dist/src/data/dataProviderTypes.d.ts +67 -0
  126. package/dist/src/data/dataProviderTypes.d.ts.map +1 -0
  127. package/dist/src/data/dataUtils.test.d.ts +2 -0
  128. package/dist/src/data/dataUtils.test.d.ts.map +1 -0
  129. package/dist/src/data/inMemoryList.d.ts +12 -0
  130. package/dist/src/data/inMemoryList.d.ts.map +1 -0
  131. package/dist/src/data/parseFormErrorHelpers.d.ts +35 -0
  132. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -0
  133. package/dist/src/data/parseFormErrorHelpers.test.d.ts +2 -0
  134. package/dist/src/data/parseFormErrorHelpers.test.d.ts.map +1 -0
  135. package/dist/src/data/resourceHandlers.d.ts +33 -0
  136. package/dist/src/data/resourceHandlers.d.ts.map +1 -0
  137. package/dist/src/data/sortHelpers.d.ts +17 -0
  138. package/dist/src/data/sortHelpers.d.ts.map +1 -0
  139. package/dist/src/index.d.ts +31 -0
  140. package/dist/src/index.d.ts.map +1 -0
  141. package/dist/src/layouts/AdminLayout.d.ts +3 -0
  142. package/dist/src/layouts/AdminLayout.d.ts.map +1 -0
  143. package/dist/src/layouts/AuthPageLayout.d.ts +11 -0
  144. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -0
  145. package/dist/src/pages/LoginPage.d.ts +3 -0
  146. package/dist/src/pages/LoginPage.d.ts.map +1 -0
  147. package/dist/src/pages/PlaceholderPage.d.ts +4 -0
  148. package/dist/src/pages/PlaceholderPage.d.ts.map +1 -0
  149. package/dist/src/permissions/resourcePermissions.d.ts +22 -0
  150. package/dist/src/permissions/resourcePermissions.d.ts.map +1 -0
  151. package/dist/src/router/createAdminRouter.d.ts +3 -0
  152. package/dist/src/router/createAdminRouter.d.ts.map +1 -0
  153. package/dist/src/router/guards.d.ts +20 -0
  154. package/dist/src/router/guards.d.ts.map +1 -0
  155. package/dist/src/router/routeAccess.d.ts +21 -0
  156. package/dist/src/router/routeAccess.d.ts.map +1 -0
  157. package/dist/src/types.d.ts +123 -0
  158. package/dist/src/types.d.ts.map +1 -0
  159. package/package.json +58 -0
  160. package/src/app/AdminApp.tsx +34 -0
  161. package/src/components/AuthAlternateLink.tsx +23 -0
  162. package/src/components/DensitySwitch.tsx +31 -0
  163. package/src/components/ThemeSwitch.tsx +28 -0
  164. package/src/components/ThemeToolbar.tsx +34 -0
  165. package/src/components/index.ts +4 -0
  166. package/src/context/AppThemeProvider.tsx +128 -0
  167. package/src/context/AuthProvider.tsx +77 -0
  168. package/src/context/DataProvider.tsx +30 -0
  169. package/src/context/PermissionsProvider.tsx +57 -0
  170. package/src/crud/FilterBar.tsx +36 -0
  171. package/src/crud/FormSteps.tsx +135 -0
  172. package/src/crud/FormTabs.tsx +126 -0
  173. package/src/crud/InlineFormSet.tsx +231 -0
  174. package/src/crud/ListActionsBar.tsx +106 -0
  175. package/src/crud/ResourceForm.tsx +281 -0
  176. package/src/crud/ResourceFormModal.tsx +121 -0
  177. package/src/crud/ResourceList.tsx +633 -0
  178. package/src/crud/columns/BooleanColumn.tsx +32 -0
  179. package/src/crud/columns/CustomColumn.tsx +33 -0
  180. package/src/crud/columns/DateColumn.tsx +28 -0
  181. package/src/crud/columns/NumberColumn.tsx +31 -0
  182. package/src/crud/columns/ReferenceColumn.tsx +85 -0
  183. package/src/crud/columns/ReferenceManyColumn.tsx +67 -0
  184. package/src/crud/columns/TextColumn.tsx +38 -0
  185. package/src/crud/context/FilterContext.tsx +71 -0
  186. package/src/crud/context/FormContext.tsx +39 -0
  187. package/src/crud/context/FormFieldsContext.tsx +76 -0
  188. package/src/crud/context/FormSectionContext.tsx +22 -0
  189. package/src/crud/context/InlineFormContext.tsx +71 -0
  190. package/src/crud/context/ListContext.tsx +93 -0
  191. package/src/crud/fields/BooleanField.tsx +32 -0
  192. package/src/crud/fields/DateField.tsx +47 -0
  193. package/src/crud/fields/FieldWrapper.tsx +109 -0
  194. package/src/crud/fields/NumberField.tsx +50 -0
  195. package/src/crud/fields/PasswordField.tsx +98 -0
  196. package/src/crud/fields/ReferenceField.tsx +191 -0
  197. package/src/crud/fields/ReferenceManyField.tsx +70 -0
  198. package/src/crud/fields/SelectField.tsx +42 -0
  199. package/src/crud/fields/TextField.tsx +44 -0
  200. package/src/crud/fields/useInlineOrFormField.tsx +91 -0
  201. package/src/crud/filters/BooleanFilter.tsx +38 -0
  202. package/src/crud/filters/DateFilter.tsx +35 -0
  203. package/src/crud/filters/NumberFilter.tsx +33 -0
  204. package/src/crud/filters/ReferenceFilter.tsx +173 -0
  205. package/src/crud/filters/SelectFilter.tsx +44 -0
  206. package/src/crud/filters/TextFilter.tsx +36 -0
  207. package/src/crud/index.ts +73 -0
  208. package/src/crud/types.ts +130 -0
  209. package/src/crud/utils/formErrors.ts +38 -0
  210. package/src/crud/utils/formSectionErrors.ts +85 -0
  211. package/src/crud/utils/getByPath.ts +13 -0
  212. package/src/crud/utils/inlineArrayName.ts +4 -0
  213. package/src/crud/utils/pickBySources.ts +24 -0
  214. package/src/crud/utils/setByPath.ts +18 -0
  215. package/src/crud/utils/sortQueryParam.ts +37 -0
  216. package/src/crud/utils/useChoices.ts +182 -0
  217. package/src/crud/utils/useListQueryState.ts +191 -0
  218. package/src/data/createMemoryResourceHandlers.ts +78 -0
  219. package/src/data/createRestResourceHandlers.ts +57 -0
  220. package/src/data/dataProviderTypes.ts +101 -0
  221. package/src/data/dataUtils.test.ts +108 -0
  222. package/src/data/inMemoryList.ts +97 -0
  223. package/src/data/parseFormErrorHelpers.test.ts +84 -0
  224. package/src/data/parseFormErrorHelpers.ts +224 -0
  225. package/src/data/resourceHandlers.ts +124 -0
  226. package/src/data/sortHelpers.ts +48 -0
  227. package/src/index.ts +182 -0
  228. package/src/layouts/AdminLayout.tsx +381 -0
  229. package/src/layouts/AuthPageLayout.tsx +81 -0
  230. package/src/pages/LoginPage.tsx +80 -0
  231. package/src/pages/PlaceholderPage.tsx +9 -0
  232. package/src/permissions/resourcePermissions.ts +47 -0
  233. package/src/router/createAdminRouter.tsx +64 -0
  234. package/src/router/guards.tsx +63 -0
  235. package/src/router/routeAccess.ts +89 -0
  236. package/src/types.ts +135 -0
  237. package/src/vite-env.d.ts +1 -0
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,33 @@
1
+ import { InputNumber } from "antd";
2
+ import { useMemo } from "react";
3
+ import type { BaseSourceProps } from "../types";
4
+ import { useRegisterFilter } from "../context/FilterContext";
5
+
6
+ export type NumberFilterProps = BaseSourceProps;
7
+
8
+ export function NumberFilter({ source, label }: NumberFilterProps) {
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
+ <InputNumber
22
+ placeholder={label ?? source}
23
+ value={value as number | undefined}
24
+ onChange={(v) => onChange(v ?? undefined)}
25
+ style={{ minWidth: 120 }}
26
+ />
27
+ ),
28
+ }),
29
+ [source, label],
30
+ );
31
+ useRegisterFilter(def);
32
+ return null;
33
+ }
@@ -0,0 +1,173 @@
1
+ import { Select } from "antd";
2
+ import { useMemo, useState } from "react";
3
+ import type { BaseSourceProps, ChoiceOption, ReferenceProps } from "../types";
4
+ import { useRegisterFilter } from "../context/FilterContext";
5
+ import { useChoices } from "../utils/useChoices";
6
+
7
+ export type SelectFilterProps = BaseSourceProps & {
8
+ choices: ChoiceOption[];
9
+ multiple?: boolean;
10
+ };
11
+
12
+ function SelectFilterInput({
13
+ source,
14
+ label,
15
+ choices,
16
+ multiple,
17
+ value,
18
+ onChange,
19
+ }: SelectFilterProps & {
20
+ value: unknown;
21
+ onChange: (value: unknown) => void;
22
+ }) {
23
+ return (
24
+ <Select
25
+ allowClear
26
+ mode={multiple ? "multiple" : undefined}
27
+ placeholder={label ?? source}
28
+ value={value as string | number | (string | number)[] | undefined}
29
+ onChange={onChange}
30
+ options={choices}
31
+ style={{ minWidth: 160 }}
32
+ />
33
+ );
34
+ }
35
+
36
+ export function SelectFilter({
37
+ source,
38
+ label,
39
+ choices,
40
+ multiple,
41
+ }: SelectFilterProps) {
42
+ const def = useMemo(
43
+ () => ({
44
+ key: source,
45
+ source,
46
+ label,
47
+ render: ({
48
+ value,
49
+ onChange,
50
+ }: {
51
+ value: unknown;
52
+ onChange: (value: unknown) => void;
53
+ }) => (
54
+ <SelectFilterInput
55
+ source={source}
56
+ label={label}
57
+ choices={choices}
58
+ multiple={multiple}
59
+ value={value}
60
+ onChange={onChange}
61
+ />
62
+ ),
63
+ }),
64
+ [source, label, choices, multiple],
65
+ );
66
+ useRegisterFilter(def);
67
+ return null;
68
+ }
69
+
70
+ export type ReferenceFilterProps = BaseSourceProps &
71
+ ReferenceProps & {
72
+ multiple?: boolean;
73
+ search?: boolean;
74
+ };
75
+
76
+ function ReferenceFilterInput({
77
+ source,
78
+ label,
79
+ reference,
80
+ choices,
81
+ optionLabel,
82
+ optionValue,
83
+ multiple,
84
+ search,
85
+ value,
86
+ onChange,
87
+ }: ReferenceFilterProps & {
88
+ value: unknown;
89
+ onChange: (value: unknown) => void;
90
+ }) {
91
+ const [searchText, setSearchText] = useState<string | undefined>();
92
+ const { options, loading } = useChoices(
93
+ choices,
94
+ reference,
95
+ optionLabel,
96
+ optionValue,
97
+ search ? searchText : undefined,
98
+ );
99
+ return (
100
+ <Select
101
+ allowClear
102
+ mode={multiple ? "multiple" : undefined}
103
+ placeholder={label ?? source}
104
+ value={value as string | number | (string | number)[] | undefined}
105
+ onChange={onChange}
106
+ options={options.map((o) => ({
107
+ label: o.label,
108
+ value: o.value as string | number,
109
+ }))}
110
+ loading={loading}
111
+ showSearch={search}
112
+ filterOption={search ? false : undefined}
113
+ onSearch={search ? setSearchText : undefined}
114
+ optionFilterProp="label"
115
+ style={{ minWidth: 180 }}
116
+ />
117
+ );
118
+ }
119
+
120
+ export function ReferenceFilter({
121
+ source,
122
+ label,
123
+ reference,
124
+ choices,
125
+ optionLabel = "name",
126
+ optionValue = "id",
127
+ multiple,
128
+ search,
129
+ }: ReferenceFilterProps) {
130
+ const def = useMemo(
131
+ () => ({
132
+ key: source,
133
+ source,
134
+ label,
135
+ render: ({
136
+ value,
137
+ onChange,
138
+ }: {
139
+ value: unknown;
140
+ onChange: (value: unknown) => void;
141
+ }) => (
142
+ <ReferenceFilterInput
143
+ source={source}
144
+ label={label}
145
+ reference={reference}
146
+ choices={choices}
147
+ optionLabel={optionLabel}
148
+ optionValue={optionValue}
149
+ multiple={multiple}
150
+ search={search}
151
+ value={value}
152
+ onChange={onChange}
153
+ />
154
+ ),
155
+ }),
156
+ [
157
+ source,
158
+ label,
159
+ reference,
160
+ choices,
161
+ optionLabel,
162
+ optionValue,
163
+ multiple,
164
+ search,
165
+ ],
166
+ );
167
+ useRegisterFilter(def);
168
+ return null;
169
+ }
170
+
171
+ export function ReferenceManyFilter(props: ReferenceFilterProps) {
172
+ return <ReferenceFilter {...props} multiple />;
173
+ }
@@ -0,0 +1,44 @@
1
+ import { Select } from "antd";
2
+ import { useMemo } from "react";
3
+ import type { BaseSourceProps, ChoiceOption } from "../types";
4
+ import { useRegisterFilter } from "../context/FilterContext";
5
+
6
+ export type SelectFilterProps = BaseSourceProps & {
7
+ choices: ChoiceOption[];
8
+ multiple?: boolean;
9
+ };
10
+
11
+ export function SelectFilter({
12
+ source,
13
+ label,
14
+ choices,
15
+ multiple,
16
+ }: SelectFilterProps) {
17
+ const def = useMemo(
18
+ () => ({
19
+ key: source,
20
+ source,
21
+ label,
22
+ render: ({
23
+ value,
24
+ onChange,
25
+ }: {
26
+ value: unknown;
27
+ onChange: (value: unknown) => void;
28
+ }) => (
29
+ <Select
30
+ allowClear
31
+ mode={multiple ? "multiple" : undefined}
32
+ placeholder={label ?? source}
33
+ value={value as string | number | (string | number)[] | undefined}
34
+ onChange={onChange}
35
+ options={choices}
36
+ style={{ minWidth: 160 }}
37
+ />
38
+ ),
39
+ }),
40
+ [source, label, choices, multiple],
41
+ );
42
+ useRegisterFilter(def);
43
+ return null;
44
+ }
@@ -0,0 +1,36 @@
1
+ import { Input } from "antd";
2
+ import { useMemo } from "react";
3
+ import type { BaseSourceProps } from "../types";
4
+ import { useRegisterFilter } from "../context/FilterContext";
5
+
6
+ export type TextFilterProps = BaseSourceProps & {
7
+ placeholder?: string;
8
+ };
9
+
10
+ export function TextFilter({ source, label, placeholder }: TextFilterProps) {
11
+ const def = useMemo(
12
+ () => ({
13
+ key: source,
14
+ source,
15
+ label,
16
+ render: ({
17
+ value,
18
+ onChange,
19
+ }: {
20
+ value: unknown;
21
+ onChange: (value: unknown) => void;
22
+ }) => (
23
+ <Input
24
+ allowClear
25
+ placeholder={placeholder ?? label ?? source}
26
+ value={(value as string) ?? ""}
27
+ onChange={(e) => onChange(e.target.value || undefined)}
28
+ style={{ minWidth: 160 }}
29
+ />
30
+ ),
31
+ }),
32
+ [source, label, placeholder],
33
+ );
34
+ useRegisterFilter(def);
35
+ return null;
36
+ }
@@ -0,0 +1,73 @@
1
+ export { ResourceList, useResourceListContext } from "./ResourceList";
2
+ export type { ResourceListProps } from "./ResourceList";
3
+ export { FilterBar } from "./FilterBar";
4
+ export { ResourceForm } from "./ResourceForm";
5
+ export type {
6
+ ResourceFormProps,
7
+ ResourceFormInlineConfig,
8
+ } from "./ResourceForm";
9
+ export { ResourceFormModal } from "./ResourceFormModal";
10
+ export type { ResourceFormModalProps } from "./ResourceFormModal";
11
+ export {
12
+ InlineFormSet,
13
+ saveInlineRows,
14
+ loadInlineRows,
15
+ } from "./InlineFormSet";
16
+ export type { InlineFormSetProps, SaveInlineOptions, InlineFormSetLayout } from "./InlineFormSet";
17
+ export { FormTabs, FormTab } from "./FormTabs";
18
+ export type { FormTabsProps, FormTabProps } from "./FormTabs";
19
+ export { FormSteps, FormStep } from "./FormSteps";
20
+ export type { FormStepsProps, FormStepProps } from "./FormSteps";
21
+
22
+ export { TextField } from "./fields/TextField";
23
+ export { NumberField } from "./fields/NumberField";
24
+ export { BooleanField } from "./fields/BooleanField";
25
+ export { DateField } from "./fields/DateField";
26
+ export { SelectField } from "./fields/SelectField";
27
+ export { PasswordField } from "./fields/PasswordField";
28
+ export { ReferenceField } from "./fields/ReferenceField";
29
+ export { ReferenceManyField } from "./fields/ReferenceManyField";
30
+
31
+ export { TextColumn } from "./columns/TextColumn";
32
+ export { NumberColumn } from "./columns/NumberColumn";
33
+ export { BooleanColumn } from "./columns/BooleanColumn";
34
+ export { DateColumn } from "./columns/DateColumn";
35
+ export { ReferenceColumn } from "./columns/ReferenceColumn";
36
+ export { ReferenceManyColumn } from "./columns/ReferenceManyColumn";
37
+ export { CustomColumn } from "./columns/CustomColumn";
38
+
39
+ export { TextFilter } from "./filters/TextFilter";
40
+ export { NumberFilter } from "./filters/NumberFilter";
41
+ export { BooleanFilter } from "./filters/BooleanFilter";
42
+ export { DateFilter } from "./filters/DateFilter";
43
+ export { SelectFilter } from "./filters/SelectFilter";
44
+ export {
45
+ ReferenceFilter,
46
+ ReferenceManyFilter,
47
+ } from "./filters/ReferenceFilter";
48
+
49
+ export { useListQueryState } from "./utils/useListQueryState";
50
+ export type {
51
+ ListQueryState,
52
+ ListQueryActions,
53
+ } from "./utils/useListQueryState";
54
+ export { useChoices } from "./utils/useChoices";
55
+ export { getByPath } from "./utils/getByPath";
56
+ export { pickBySources } from "./utils/pickBySources";
57
+ export { inlineArrayName } from "./utils/inlineArrayName";
58
+ export { setByPath } from "./utils/setByPath";
59
+ export { useRegisterFormSource } from "./context/FormFieldsContext";
60
+
61
+ export type {
62
+ BaseSourceProps,
63
+ ChoiceOption,
64
+ ChoicesLoader,
65
+ ReferenceProps,
66
+ DisplayProps,
67
+ EditMode,
68
+ FieldRules,
69
+ ResourceListBuiltInActions,
70
+ ResourceListBulkAction,
71
+ ResourceListBulkActionHelpers,
72
+ ResourceListRowActionsHelpers,
73
+ } from "./types";
@@ -0,0 +1,130 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ColumnsType } from "antd/es/table";
3
+ import type { DataProvider } from "../data/dataProviderTypes";
4
+ import type { RegisterOptions } from "react-hook-form";
5
+
6
+ export type BaseSourceProps = {
7
+ source: string;
8
+ label?: string;
9
+ };
10
+
11
+ export type ChoiceOption = {
12
+ label: string;
13
+ value: unknown;
14
+ /** Full record when loaded from a reference resource (for dependent fields). */
15
+ record?: Record<string, unknown>;
16
+ };
17
+
18
+ export type ChoicesContext = {
19
+ dataProvider: DataProvider;
20
+ search?: string;
21
+ };
22
+
23
+ export type ChoicesLoader =
24
+ | { resource: string; filter?: Record<string, unknown> }
25
+ | ChoiceOption[]
26
+ | ((ctx: ChoicesContext) => Promise<ChoiceOption[]>);
27
+
28
+ export type ReferenceProps = {
29
+ reference?: string;
30
+ choices?: ChoicesLoader;
31
+ optionLabel?: string | ((record: Record<string, unknown>) => string);
32
+ optionValue?: string;
33
+ };
34
+
35
+ export type DisplayProps = {
36
+ display?: string | ((record: Record<string, unknown>) => ReactNode);
37
+ };
38
+
39
+ export type FieldRules = RegisterOptions;
40
+
41
+ export type ColumnDefinition<T extends Record<string, unknown>> = {
42
+ key: string;
43
+ source: string;
44
+ label?: string;
45
+ sortable?: boolean;
46
+ buildColumn: () => ColumnsType<T>[number];
47
+ };
48
+
49
+ export type FilterDefinition = {
50
+ key: string;
51
+ source: string;
52
+ label?: string;
53
+ render: (props: {
54
+ value: unknown;
55
+ onChange: (value: unknown) => void;
56
+ }) => ReactNode;
57
+ };
58
+
59
+ export type InlineFieldDefinition = {
60
+ key: string;
61
+ source: string;
62
+ label?: string;
63
+ /** Column width in tabular inline tables (Ant Design Table `width`). */
64
+ width?: number | string;
65
+ /** Minimum column width in tabular inline tables. */
66
+ minWidth?: number | string;
67
+ render: (props: {
68
+ name: string;
69
+ index: number;
70
+ }) => ReactNode;
71
+ };
72
+
73
+ export type InlineFieldOptions = {
74
+ width?: number | string;
75
+ minWidth?: number | string;
76
+ };
77
+
78
+ export type FieldRenderProps = {
79
+ value: unknown;
80
+ onChange: (value: unknown) => void;
81
+ onBlur: () => void;
82
+ disabled?: boolean;
83
+ /** Set when rendered inside `InlineFormSet`. */
84
+ name?: string;
85
+ index?: number;
86
+ };
87
+
88
+ export type EditMode = "page" | "modal" | "both";
89
+
90
+ /** Toggle built-in row actions. Omitted keys default to `true` (still gated by permissions). */
91
+ export type ResourceListBuiltInActions = {
92
+ edit?: boolean;
93
+ quickEdit?: boolean;
94
+ delete?: boolean;
95
+ };
96
+
97
+ export type ResourceListRowActionsHelpers = {
98
+ reload: () => void;
99
+ openEditModal: (id: string | number) => void;
100
+ };
101
+
102
+ export type ResourceListBulkActionHelpers = {
103
+ reload: () => void;
104
+ clearSelection: () => void;
105
+ };
106
+
107
+ /** Bulk list action (Django admin–style). Runs on selected row ids. */
108
+ export type ResourceListBulkAction = {
109
+ key: string;
110
+ label: string;
111
+ /** Optional confirmation message. Return false to cancel. */
112
+ confirm?:
113
+ | string
114
+ | ((
115
+ selectedIds: (string | number)[],
116
+ helpers: ResourceListBulkActionHelpers,
117
+ ) => string | false | Promise<string | false>);
118
+ execute: (
119
+ selectedIds: (string | number)[],
120
+ helpers: ResourceListBulkActionHelpers,
121
+ ) => void | Promise<void>;
122
+ };
123
+
124
+ export const LIST_QUERY_RESERVED = new Set([
125
+ "page",
126
+ "perPage",
127
+ "sort",
128
+ "create",
129
+ "edit",
130
+ ]);
@@ -0,0 +1,38 @@
1
+ import type { MessageInstance } from "antd/es/message/interface";
2
+ import type { FieldPath, FieldValues, UseFormReturn } from "react-hook-form";
3
+ import type { DataProvider, ParseFormErrorContext } from "../../data/dataProviderTypes";
4
+
5
+ function toMessages(value: string | string[] | undefined): string[] {
6
+ if (!value) return [];
7
+ return Array.isArray(value) ? value : [value];
8
+ }
9
+
10
+ export function applyFormErrors<T extends FieldValues>(
11
+ form: UseFormReturn<T>,
12
+ parsed: { fields?: Record<string, string | string[]>; global?: string | string[] },
13
+ message: MessageInstance,
14
+ ) {
15
+ for (const msg of toMessages(parsed.global)) {
16
+ message.error(msg);
17
+ }
18
+ for (const [path, msg] of Object.entries(parsed.fields ?? {})) {
19
+ const text = Array.isArray(msg) ? msg.join(", ") : msg;
20
+ form.setError(path as FieldPath<T>, { type: "server", message: text });
21
+ }
22
+ }
23
+
24
+ export function parseAndApplyFormErrors<T extends FieldValues>(
25
+ dp: DataProvider,
26
+ form: UseFormReturn<T>,
27
+ message: MessageInstance,
28
+ error: unknown,
29
+ context: ParseFormErrorContext,
30
+ ): boolean {
31
+ const parsed = dp.parseFormError?.(error, context);
32
+ if (!parsed) return false;
33
+ const hasFields = Object.keys(parsed.fields ?? {}).length > 0;
34
+ const hasGlobal = toMessages(parsed.global).length > 0;
35
+ if (!hasFields && !hasGlobal) return false;
36
+ applyFormErrors(form, parsed, message);
37
+ return true;
38
+ }
@@ -0,0 +1,85 @@
1
+ import { useEffect, useRef, type MutableRefObject } from "react";
2
+ import {
3
+ useFormContext,
4
+ useFormState,
5
+ type FieldPath,
6
+ type FieldValues,
7
+ type FormState,
8
+ type UseFormGetFieldState,
9
+ } from "react-hook-form";
10
+
11
+ export function sectionHasError<T extends FieldValues>(
12
+ sources: Set<string>,
13
+ getFieldState: UseFormGetFieldState<T>,
14
+ formState: FormState<T>,
15
+ ): boolean {
16
+ for (const source of sources) {
17
+ if (getFieldState(source as FieldPath<T>, formState).invalid) {
18
+ return true;
19
+ }
20
+ }
21
+ return false;
22
+ }
23
+
24
+ export function useSectionSourceRefs(count: number) {
25
+ const refs = useRef<MutableRefObject<Set<string>>[]>([]);
26
+
27
+ while (refs.current.length < count) {
28
+ refs.current.push({ current: new Set() });
29
+ }
30
+ if (refs.current.length > count) {
31
+ refs.current.length = count;
32
+ }
33
+
34
+ return refs.current;
35
+ }
36
+
37
+ export function useActivateFirstErrorSection(
38
+ sectionRefs: MutableRefObject<Set<string>>[],
39
+ activate: (index: number) => void,
40
+ ) {
41
+ const { control, getFieldState, setFocus } = useFormContext<FieldValues>();
42
+ const formState = useFormState({ control });
43
+ const prevSubmitCount = useRef(0);
44
+ const prevErrorCount = useRef(0);
45
+
46
+ useEffect(() => {
47
+ if (formState.submitCount === 0) return;
48
+
49
+ const errorCount = Object.keys(formState.errors).length;
50
+ const submitChanged = formState.submitCount !== prevSubmitCount.current;
51
+ const serverErrorsArrived =
52
+ !submitChanged && errorCount > 0 && prevErrorCount.current === 0;
53
+
54
+ prevSubmitCount.current = formState.submitCount;
55
+ prevErrorCount.current = errorCount;
56
+
57
+ if ((!submitChanged && !serverErrorsArrived) || errorCount === 0) {
58
+ return;
59
+ }
60
+
61
+ const first = sectionRefs.findIndex((ref) =>
62
+ sectionHasError(ref.current, getFieldState, formState),
63
+ );
64
+ if (first < 0) return;
65
+
66
+ activate(first);
67
+
68
+ const firstInvalid = [...sectionRefs[first].current].find((source) =>
69
+ getFieldState(source as FieldPath<FieldValues>, formState).invalid,
70
+ );
71
+ if (firstInvalid) {
72
+ requestAnimationFrame(() => {
73
+ void setFocus(firstInvalid as FieldPath<FieldValues>);
74
+ });
75
+ }
76
+ }, [
77
+ activate,
78
+ formState,
79
+ formState.errors,
80
+ formState.submitCount,
81
+ getFieldState,
82
+ sectionRefs,
83
+ setFocus,
84
+ ]);
85
+ }
@@ -0,0 +1,13 @@
1
+ /** Read nested value from a record using dot notation, e.g. `brand.name`. */
2
+ export function getByPath(
3
+ record: Record<string, unknown>,
4
+ path: string,
5
+ ): unknown {
6
+ const parts = path.split(".");
7
+ let cur: unknown = record;
8
+ for (const part of parts) {
9
+ if (cur == null || typeof cur !== "object") return undefined;
10
+ cur = (cur as Record<string, unknown>)[part];
11
+ }
12
+ return cur;
13
+ }