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,28 @@
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type DateColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ };
8
+
9
+ export function DateColumn({ source, label, sortable = true }: DateColumnProps) {
10
+ const def = useMemo(
11
+ () => ({
12
+ key: source,
13
+ source,
14
+ label,
15
+ sortable,
16
+ buildColumn: () => ({
17
+ title: label ?? source,
18
+ dataIndex: source,
19
+ key: source,
20
+ sorter: sortable ? true : undefined,
21
+ render: (v: string) => (v ? String(v).slice(0, 10) : "—"),
22
+ }),
23
+ }),
24
+ [source, label, sortable],
25
+ );
26
+ useRegisterColumn(def);
27
+ return null;
28
+ }
@@ -0,0 +1,31 @@
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type NumberColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ };
8
+
9
+ export function NumberColumn({
10
+ source,
11
+ label,
12
+ sortable = true,
13
+ }: NumberColumnProps) {
14
+ const def = useMemo(
15
+ () => ({
16
+ key: source,
17
+ source,
18
+ label,
19
+ sortable,
20
+ buildColumn: () => ({
21
+ title: label ?? source,
22
+ dataIndex: source,
23
+ key: source,
24
+ sorter: sortable ? true : undefined,
25
+ }),
26
+ }),
27
+ [source, label, sortable],
28
+ );
29
+ useRegisterColumn(def);
30
+ return null;
31
+ }
@@ -0,0 +1,85 @@
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps, DisplayProps, ReferenceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+ import { useChoices } from "../utils/useChoices";
5
+ import { renderDisplayValue } from "./TextColumn";
6
+
7
+ export type ReferenceColumnProps = BaseSourceProps &
8
+ ReferenceProps &
9
+ DisplayProps & {
10
+ sortable?: boolean;
11
+ };
12
+
13
+ function ReferenceColumnCell({
14
+ record,
15
+ source,
16
+ display,
17
+ reference,
18
+ choices,
19
+ optionLabel,
20
+ optionValue,
21
+ }: ReferenceColumnProps & { record: Record<string, unknown> }) {
22
+ const { labelForValue } = useChoices(
23
+ choices,
24
+ reference,
25
+ optionLabel,
26
+ optionValue,
27
+ );
28
+ const raw = record[source];
29
+ if (typeof display === "function") return <>{display(record)}</>;
30
+ if (display && display !== source) {
31
+ const nested = renderDisplayValue(record, source, display);
32
+ return <>{nested != null ? String(nested) : "—"}</>;
33
+ }
34
+ return <>{labelForValue(raw)}</>;
35
+ }
36
+
37
+ export function ReferenceColumn({
38
+ source,
39
+ label,
40
+ reference,
41
+ choices,
42
+ optionLabel = "name",
43
+ optionValue = "id",
44
+ display,
45
+ sortable = true,
46
+ }: ReferenceColumnProps) {
47
+ const def = useMemo(
48
+ () => ({
49
+ key: source,
50
+ source,
51
+ label,
52
+ sortable,
53
+ buildColumn: () => ({
54
+ title: label ?? source,
55
+ dataIndex: source,
56
+ key: source,
57
+ sorter: sortable ? true : undefined,
58
+ render: (_: unknown, record: Record<string, unknown>) => (
59
+ <ReferenceColumnCell
60
+ record={record}
61
+ source={source}
62
+ label={label}
63
+ reference={reference}
64
+ choices={choices}
65
+ optionLabel={optionLabel}
66
+ optionValue={optionValue}
67
+ display={display ?? optionLabel}
68
+ />
69
+ ),
70
+ }),
71
+ }),
72
+ [
73
+ source,
74
+ label,
75
+ sortable,
76
+ reference,
77
+ choices,
78
+ optionLabel,
79
+ optionValue,
80
+ display,
81
+ ],
82
+ );
83
+ useRegisterColumn(def);
84
+ return null;
85
+ }
@@ -0,0 +1,67 @@
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps, DisplayProps, ReferenceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+ import { useChoices } from "../utils/useChoices";
5
+
6
+ export type ReferenceManyColumnProps = BaseSourceProps &
7
+ ReferenceProps &
8
+ DisplayProps & {
9
+ sortable?: boolean;
10
+ };
11
+
12
+ function ReferenceManyColumnCell({
13
+ record,
14
+ source,
15
+ reference,
16
+ choices,
17
+ optionLabel,
18
+ optionValue,
19
+ }: ReferenceManyColumnProps & { record: Record<string, unknown> }) {
20
+ const { labelsForValues } = useChoices(
21
+ choices,
22
+ reference,
23
+ optionLabel,
24
+ optionValue,
25
+ );
26
+ const raw = record[source];
27
+ const values = Array.isArray(raw) ? raw : [];
28
+ return <>{labelsForValues(values)}</>;
29
+ }
30
+
31
+ export function ReferenceManyColumn({
32
+ source,
33
+ label,
34
+ reference,
35
+ choices,
36
+ optionLabel = "name",
37
+ optionValue = "id",
38
+ sortable = false,
39
+ }: ReferenceManyColumnProps) {
40
+ const def = useMemo(
41
+ () => ({
42
+ key: source,
43
+ source,
44
+ label,
45
+ sortable,
46
+ buildColumn: () => ({
47
+ title: label ?? source,
48
+ dataIndex: source,
49
+ key: source,
50
+ sorter: sortable ? true : undefined,
51
+ render: (_: unknown, record: Record<string, unknown>) => (
52
+ <ReferenceManyColumnCell
53
+ record={record}
54
+ source={source}
55
+ reference={reference}
56
+ choices={choices}
57
+ optionLabel={optionLabel}
58
+ optionValue={optionValue}
59
+ />
60
+ ),
61
+ }),
62
+ }),
63
+ [source, label, sortable, reference, choices, optionLabel, optionValue],
64
+ );
65
+ useRegisterColumn(def);
66
+ return null;
67
+ }
@@ -0,0 +1,38 @@
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { getByPath } from "../utils/getByPath";
4
+ import { useRegisterColumn } from "../context/ListContext";
5
+
6
+ export type TextColumnProps = BaseSourceProps & {
7
+ sortable?: boolean;
8
+ };
9
+
10
+ export function TextColumn({ source, label, sortable = true }: TextColumnProps) {
11
+ const def = useMemo(
12
+ () => ({
13
+ key: source,
14
+ source,
15
+ label,
16
+ sortable,
17
+ buildColumn: () => ({
18
+ title: label ?? source,
19
+ dataIndex: source,
20
+ key: source,
21
+ sorter: sortable ? true : undefined,
22
+ }),
23
+ }),
24
+ [source, label, sortable],
25
+ );
26
+ useRegisterColumn(def);
27
+ return null;
28
+ }
29
+
30
+ export function renderDisplayValue(
31
+ record: Record<string, unknown>,
32
+ source: string,
33
+ display?: string | ((record: Record<string, unknown>) => unknown),
34
+ ): unknown {
35
+ if (typeof display === "function") return display(record);
36
+ if (display) return getByPath(record, display);
37
+ return record[source];
38
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import type { FilterDefinition } from "../types";
11
+
12
+ type FilterContextValue = {
13
+ registerFilter: (def: FilterDefinition) => () => void;
14
+ filters: FilterDefinition[];
15
+ values: Record<string, unknown>;
16
+ setFilterValue: (source: string, value: unknown) => void;
17
+ };
18
+
19
+ const FilterContext = createContext<FilterContextValue | null>(null);
20
+
21
+ export function FilterContextProvider({
22
+ children,
23
+ values,
24
+ setFilterValue,
25
+ }: {
26
+ children: ReactNode;
27
+ values: Record<string, unknown>;
28
+ setFilterValue: (source: string, value: unknown) => void;
29
+ }) {
30
+ const [filters, setFilters] = useState<FilterDefinition[]>([]);
31
+
32
+ const registerFilter = useCallback((def: FilterDefinition) => {
33
+ setFilters((prev) => {
34
+ const idx = prev.findIndex((f) => f.key === def.key);
35
+ if (idx < 0) return [...prev, def];
36
+ if (prev[idx] === def) return prev;
37
+ const next = [...prev];
38
+ next[idx] = def;
39
+ return next;
40
+ });
41
+ return () => {
42
+ setFilters((prev) => prev.filter((f) => f.key !== def.key));
43
+ };
44
+ }, []);
45
+
46
+ const value = useMemo(
47
+ (): FilterContextValue => ({
48
+ filters,
49
+ values,
50
+ setFilterValue,
51
+ registerFilter,
52
+ }),
53
+ [filters, values, setFilterValue, registerFilter],
54
+ );
55
+
56
+ return (
57
+ <FilterContext.Provider value={value}>{children}</FilterContext.Provider>
58
+ );
59
+ }
60
+
61
+ export function useFilterContext() {
62
+ return useContext(FilterContext);
63
+ }
64
+
65
+ export function useRegisterFilter(def: FilterDefinition) {
66
+ const registerFilter = useFilterContext()?.registerFilter;
67
+ useEffect(() => {
68
+ if (!registerFilter) return;
69
+ return registerFilter(def);
70
+ }, [registerFilter, def]);
71
+ }
@@ -0,0 +1,39 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+
3
+ type FormMetaContextValue = {
4
+ resource: string;
5
+ isNew: boolean;
6
+ disabled?: boolean;
7
+ };
8
+
9
+ const FormMetaContext = createContext<FormMetaContextValue | null>(null);
10
+
11
+ export function FormMetaProvider({
12
+ children,
13
+ resource,
14
+ isNew,
15
+ disabled,
16
+ }: {
17
+ children: ReactNode;
18
+ resource: string;
19
+ isNew: boolean;
20
+ disabled?: boolean;
21
+ }) {
22
+ return (
23
+ <FormMetaContext.Provider value={{ resource, isNew, disabled }}>
24
+ {children}
25
+ </FormMetaContext.Provider>
26
+ );
27
+ }
28
+
29
+ export function useFormMeta() {
30
+ const ctx = useContext(FormMetaContext);
31
+ if (!ctx) {
32
+ throw new Error("Field components must be used within ResourceForm");
33
+ }
34
+ return ctx;
35
+ }
36
+
37
+ export function useFormMetaOptional() {
38
+ return useContext(FormMetaContext);
39
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ type MutableRefObject,
9
+ type ReactNode,
10
+ } from "react";
11
+ import { useFormSectionSourcesOptional } from "./FormSectionContext";
12
+
13
+ type FormFieldsContextValue = {
14
+ registerSource: (source: string) => () => void;
15
+ getSources: () => string[];
16
+ };
17
+
18
+ const FormFieldsContext = createContext<FormFieldsContextValue | null>(null);
19
+
20
+ export function FormFieldsProvider({
21
+ children,
22
+ sourcesRef: externalSourcesRef,
23
+ }: {
24
+ children: ReactNode;
25
+ /** Optional ref shared with submit handler outside this provider. */
26
+ sourcesRef?: MutableRefObject<Set<string>>;
27
+ }) {
28
+ const internalSourcesRef = useRef(new Set<string>());
29
+ const sourcesRef = externalSourcesRef ?? internalSourcesRef;
30
+
31
+ const registerSource = useCallback(
32
+ (source: string) => {
33
+ sourcesRef.current.add(source);
34
+ return () => {
35
+ sourcesRef.current.delete(source);
36
+ };
37
+ },
38
+ [sourcesRef],
39
+ );
40
+
41
+ const getSources = useCallback(
42
+ () => Array.from(sourcesRef.current),
43
+ [sourcesRef],
44
+ );
45
+
46
+ const value = useMemo(
47
+ () => ({ registerSource, getSources }),
48
+ [registerSource, getSources],
49
+ );
50
+
51
+ return (
52
+ <FormFieldsContext.Provider value={value}>
53
+ {children}
54
+ </FormFieldsContext.Provider>
55
+ );
56
+ }
57
+
58
+ export function useFormFieldsOptional() {
59
+ return useContext(FormFieldsContext);
60
+ }
61
+
62
+ export function useRegisterFormSource(source: string) {
63
+ const ctx = useFormFieldsOptional();
64
+ const registerSource = ctx?.registerSource;
65
+ const sectionSourcesRef = useFormSectionSourcesOptional();
66
+
67
+ useEffect(() => {
68
+ if (!registerSource) return;
69
+ const unregister = registerSource(source);
70
+ sectionSourcesRef?.current.add(source);
71
+ return () => {
72
+ unregister();
73
+ sectionSourcesRef?.current.delete(source);
74
+ };
75
+ }, [registerSource, sectionSourcesRef, source]);
76
+ }
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext, type MutableRefObject, type ReactNode } from "react";
2
+
3
+ const FormSectionSourcesContext =
4
+ createContext<MutableRefObject<Set<string>> | null>(null);
5
+
6
+ export function FormSectionSourcesProvider({
7
+ sourcesRef,
8
+ children,
9
+ }: {
10
+ sourcesRef: MutableRefObject<Set<string>>;
11
+ children: ReactNode;
12
+ }) {
13
+ return (
14
+ <FormSectionSourcesContext.Provider value={sourcesRef}>
15
+ {children}
16
+ </FormSectionSourcesContext.Provider>
17
+ );
18
+ }
19
+
20
+ export function useFormSectionSourcesOptional() {
21
+ return useContext(FormSectionSourcesContext);
22
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import type { InlineFieldDefinition } from "../types";
11
+
12
+ export type InlineFormSetLayout = "tabular" | "stacked";
13
+
14
+ type InlineFormSetMeta = {
15
+ registerField: (def: InlineFieldDefinition) => () => void;
16
+ arrayName: string;
17
+ layout: InlineFormSetLayout;
18
+ };
19
+
20
+ const InlineFormSetMetaContext = createContext<InlineFormSetMeta | null>(null);
21
+ const InlineFormSetFieldsContext = createContext<InlineFieldDefinition[]>([]);
22
+
23
+ export function InlineFormSetProvider({
24
+ children,
25
+ arrayName,
26
+ layout = "tabular",
27
+ }: {
28
+ children: ReactNode;
29
+ arrayName: string;
30
+ layout?: InlineFormSetLayout;
31
+ }) {
32
+ const [fields, setFields] = useState<InlineFieldDefinition[]>([]);
33
+
34
+ const registerField = useCallback((def: InlineFieldDefinition) => {
35
+ setFields((prev) => {
36
+ if (prev.some((f) => f.key === def.key)) return prev;
37
+ return [...prev, def];
38
+ });
39
+ return () => {
40
+ setFields((prev) => prev.filter((f) => f.key !== def.key));
41
+ };
42
+ }, []);
43
+
44
+ const meta = useMemo(
45
+ (): InlineFormSetMeta => ({ arrayName, registerField, layout }),
46
+ [arrayName, registerField, layout],
47
+ );
48
+
49
+ return (
50
+ <InlineFormSetMetaContext.Provider value={meta}>
51
+ <InlineFormSetFieldsContext.Provider value={fields}>
52
+ {children}
53
+ </InlineFormSetFieldsContext.Provider>
54
+ </InlineFormSetMetaContext.Provider>
55
+ );
56
+ }
57
+
58
+ export function useInlineFormSetContext() {
59
+ const meta = useContext(InlineFormSetMetaContext);
60
+ const fields = useContext(InlineFormSetFieldsContext);
61
+ if (!meta) return null;
62
+ return { ...meta, fields };
63
+ }
64
+
65
+ export function useRegisterInlineField(def: InlineFieldDefinition) {
66
+ const registerField = useContext(InlineFormSetMetaContext)?.registerField;
67
+ useEffect(() => {
68
+ if (!registerField) return;
69
+ return registerField(def);
70
+ }, [registerField, def]);
71
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import type { ColumnDefinition } from "../types";
11
+ import { sortPrioritiesFromSpecs } from "../utils/sortQueryParam";
12
+
13
+ type ListContextValue = {
14
+ registerColumn: (def: ColumnDefinition<Record<string, unknown>>) => () => void;
15
+ columns: ColumnDefinition<Record<string, unknown>>[];
16
+ toggleSort: (field: string) => void;
17
+ sortFields: Set<string>;
18
+ sortOrders: Map<string, "ASC" | "DESC">;
19
+ sortPriorities: Map<string, number>;
20
+ };
21
+
22
+ const ListContext = createContext<ListContextValue | null>(null);
23
+
24
+ export function ListContextProvider({
25
+ children,
26
+ toggleSort,
27
+ sort,
28
+ }: {
29
+ children: ReactNode;
30
+ toggleSort: (field: string) => void;
31
+ sort: { field: string; order: "ASC" | "DESC" }[];
32
+ }) {
33
+ const [columns, setColumns] = useState<
34
+ ColumnDefinition<Record<string, unknown>>[]
35
+ >([]);
36
+
37
+ const sortFields = useMemo(() => new Set(sort.map((s) => s.field)), [sort]);
38
+ const sortOrders = useMemo(
39
+ () => new Map(sort.map((s) => [s.field, s.order])),
40
+ [sort],
41
+ );
42
+ const sortPriorities = useMemo(() => sortPrioritiesFromSpecs(sort), [sort]);
43
+
44
+ const registerColumn = useCallback(
45
+ (def: ColumnDefinition<Record<string, unknown>>) => {
46
+ setColumns((prev) => {
47
+ const idx = prev.findIndex((c) => c.key === def.key);
48
+ if (idx < 0) return [...prev, def];
49
+ if (prev[idx] === def) return prev;
50
+ const next = [...prev];
51
+ next[idx] = def;
52
+ return next;
53
+ });
54
+ return () => {
55
+ setColumns((prev) => prev.filter((c) => c.key !== def.key));
56
+ };
57
+ },
58
+ [],
59
+ );
60
+
61
+ const value = useMemo(
62
+ (): ListContextValue => ({
63
+ columns,
64
+ toggleSort,
65
+ sortFields,
66
+ sortOrders,
67
+ sortPriorities,
68
+ registerColumn,
69
+ }),
70
+ [columns, toggleSort, sortFields, sortOrders, sortPriorities, registerColumn],
71
+ );
72
+
73
+ return (
74
+ <ListContext.Provider value={value}>{children}</ListContext.Provider>
75
+ );
76
+ }
77
+
78
+ export function useListContext() {
79
+ const ctx = useContext(ListContext);
80
+ if (!ctx) {
81
+ throw new Error("Column components must be used within ResourceList");
82
+ }
83
+ return ctx;
84
+ }
85
+
86
+ export function useRegisterColumn(
87
+ def: ColumnDefinition<Record<string, unknown>>,
88
+ ) {
89
+ const { registerColumn } = useListContext();
90
+ useEffect(() => {
91
+ return registerColumn(def);
92
+ }, [registerColumn, def]);
93
+ }
@@ -0,0 +1,32 @@
1
+ import { Switch } from "antd";
2
+ import type { BaseSourceProps, FieldRules } from "../types";
3
+ import { useInlineOrFormField } from "./useInlineOrFormField";
4
+
5
+ export type BooleanFieldProps = BaseSourceProps & {
6
+ required?: boolean;
7
+ rules?: FieldRules;
8
+ };
9
+
10
+ export function BooleanField({
11
+ source,
12
+ label,
13
+ required,
14
+ rules,
15
+ }: BooleanFieldProps) {
16
+ const field = useInlineOrFormField(
17
+ source,
18
+ label,
19
+ required,
20
+ rules,
21
+ ({ value, onChange, disabled }) => (
22
+ <Switch
23
+ checked={Boolean(value)}
24
+ onChange={onChange}
25
+ disabled={disabled}
26
+ />
27
+ ),
28
+ );
29
+
30
+ if (field.mode === "inline") return null;
31
+ return field.element;
32
+ }
@@ -0,0 +1,47 @@
1
+ import { DatePicker } from "antd";
2
+ import dayjs from "dayjs";
3
+ import type { BaseSourceProps, FieldRules } from "../types";
4
+ import { useInlineOrFormField } from "./useInlineOrFormField";
5
+
6
+ const DATE_FORMAT = "YYYY-MM-DD";
7
+
8
+ export type DateFieldProps = BaseSourceProps & {
9
+ required?: boolean;
10
+ rules?: FieldRules;
11
+ showTime?: boolean;
12
+ };
13
+
14
+ export function DateField({
15
+ source,
16
+ label,
17
+ required,
18
+ rules,
19
+ showTime,
20
+ }: DateFieldProps) {
21
+ const field = useInlineOrFormField(
22
+ source,
23
+ label,
24
+ required,
25
+ rules,
26
+ ({ value, onChange, onBlur, disabled }) => (
27
+ <DatePicker
28
+ value={value ? dayjs(String(value)) : null}
29
+ onChange={(d) =>
30
+ onChange(
31
+ d
32
+ ? d.format(showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT)
33
+ : null,
34
+ )
35
+ }
36
+ onBlur={onBlur}
37
+ showTime={showTime}
38
+ disabled={disabled}
39
+ format={showTime ? `${DATE_FORMAT} HH:mm:ss` : DATE_FORMAT}
40
+ style={{ width: "100%" }}
41
+ />
42
+ ),
43
+ );
44
+
45
+ if (field.mode === "inline") return null;
46
+ return field.element;
47
+ }