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,63 +1,63 @@
1
- import type { ReactElement } from "react";
2
- import { Navigate } from "react-router-dom";
3
- import { useAuth } from "../context/AuthProvider";
4
- import { usePermissions } from "../context/PermissionsProvider";
5
-
6
- export function Guard({
7
- when,
8
- redirect,
9
- children,
10
- }: {
11
- when: boolean;
12
- redirect: string;
13
- children: ReactElement;
14
- }) {
15
- return when ? children : <Navigate to={redirect} replace />;
16
- }
17
-
18
- export function Protected({
19
- children,
20
- redirectTo = "/login",
21
- }: {
22
- children: ReactElement;
23
- redirectTo?: string;
24
- }) {
25
- const { isAuthenticated } = useAuth();
26
- return (
27
- <Guard when={isAuthenticated} redirect={redirectTo}>
28
- {children}
29
- </Guard>
30
- );
31
- }
32
-
33
- export function GuestOnly({
34
- children,
35
- redirectTo = "/",
36
- }: {
37
- children: ReactElement;
38
- redirectTo?: string;
39
- }) {
40
- const { isAuthenticated } = useAuth();
41
- return (
42
- <Guard when={!isAuthenticated} redirect={redirectTo}>
43
- {children}
44
- </Guard>
45
- );
46
- }
47
-
48
- export function RequirePermission({
49
- permission,
50
- redirect,
51
- children,
52
- }: {
53
- permission: string;
54
- redirect: string;
55
- children: ReactElement;
56
- }) {
57
- const can = usePermissions();
58
- return (
59
- <Guard when={can(permission)} redirect={redirect}>
60
- {children}
61
- </Guard>
62
- );
63
- }
1
+ import type { ReactElement } from "react";
2
+ import { Navigate } from "react-router-dom";
3
+ import { useAuth } from "../context/AuthProvider";
4
+ import { usePermissions } from "../context/PermissionsProvider";
5
+
6
+ export function Guard({
7
+ when,
8
+ redirect,
9
+ children,
10
+ }: {
11
+ when: boolean;
12
+ redirect: string;
13
+ children: ReactElement;
14
+ }) {
15
+ return when ? children : <Navigate to={redirect} replace />;
16
+ }
17
+
18
+ export function Protected({
19
+ children,
20
+ redirectTo = "/login",
21
+ }: {
22
+ children: ReactElement;
23
+ redirectTo?: string;
24
+ }) {
25
+ const { isAuthenticated } = useAuth();
26
+ return (
27
+ <Guard when={isAuthenticated} redirect={redirectTo}>
28
+ {children}
29
+ </Guard>
30
+ );
31
+ }
32
+
33
+ export function GuestOnly({
34
+ children,
35
+ redirectTo = "/",
36
+ }: {
37
+ children: ReactElement;
38
+ redirectTo?: string;
39
+ }) {
40
+ const { isAuthenticated } = useAuth();
41
+ return (
42
+ <Guard when={!isAuthenticated} redirect={redirectTo}>
43
+ {children}
44
+ </Guard>
45
+ );
46
+ }
47
+
48
+ export function RequirePermission({
49
+ permission,
50
+ redirect,
51
+ children,
52
+ }: {
53
+ permission: string;
54
+ redirect: string;
55
+ children: ReactElement;
56
+ }) {
57
+ const can = usePermissions();
58
+ return (
59
+ <Guard when={can(permission)} redirect={redirect}>
60
+ {children}
61
+ </Guard>
62
+ );
63
+ }
@@ -1,89 +1,89 @@
1
- import type { RouteObject } from "react-router-dom";
2
- import type { AdminRouteChild, AuthRedirects, RouteAccess } from "../types";
3
-
4
- export function getRouteAccess(route: AdminRouteChild): RouteAccess {
5
- return route.access ?? "protected";
6
- }
7
-
8
- export function partitionAdminRoutes(routes: AdminRouteChild[]) {
9
- const guest: AdminRouteChild[] = [];
10
- const publicRoutes: AdminRouteChild[] = [];
11
- const protectedRoutes: AdminRouteChild[] = [];
12
-
13
- for (const route of routes) {
14
- const access = getRouteAccess(route);
15
- if (access === "guest") guest.push(route);
16
- else if (access === "public") publicRoutes.push(route);
17
- else protectedRoutes.push(route);
18
- }
19
-
20
- return { guest, public: publicRoutes, protected: protectedRoutes };
21
- }
22
-
23
- /** React Router path segment for a top-level route (`/login` → `login`). */
24
- export function toRouterSegment(path: string): string {
25
- return path.replace(/^\/+/, "");
26
- }
27
-
28
- export function toAbsolutePath(path: string): string {
29
- return `/${toRouterSegment(path)}`;
30
- }
31
-
32
- /**
33
- * Resolve redirect targets from declared routes and optional overrides.
34
- * Throws when protected or guest routes exist but paths cannot be determined.
35
- */
36
- export function deriveAuthPaths(
37
- routes: AdminRouteChild[],
38
- redirects?: AuthRedirects,
39
- ): { loginPath: string; homePath: string } {
40
- const { guest, protected: protectedRoutes } = partitionAdminRoutes(routes);
41
- const firstGuest = guest.find((r) => "path" in r && r.path);
42
- const indexProtected = protectedRoutes.find(
43
- (r) => "index" in r && r.index,
44
- );
45
- const firstProtectedPath = protectedRoutes.find(
46
- (r) => "path" in r && r.path,
47
- );
48
-
49
- let loginPath: string | undefined = redirects?.unauthenticated;
50
- if (!loginPath && firstGuest && "path" in firstGuest && firstGuest.path) {
51
- loginPath = toAbsolutePath(firstGuest.path);
52
- }
53
-
54
- let homePath: string | undefined = redirects?.afterLogin;
55
- if (!homePath) {
56
- if (indexProtected) homePath = "/";
57
- else if (
58
- firstProtectedPath &&
59
- "path" in firstProtectedPath &&
60
- firstProtectedPath.path
61
- ) {
62
- homePath = toAbsolutePath(firstProtectedPath.path);
63
- }
64
- }
65
-
66
- if (protectedRoutes.length > 0 && !loginPath) {
67
- throw new Error(
68
- 'createAdminRouter: protected routes require redirects.unauthenticated or a guest route (access: "guest").',
69
- );
70
- }
71
-
72
- if (guest.length > 0 && !homePath) {
73
- throw new Error(
74
- 'createAdminRouter: guest routes require redirects.afterLogin or a protected route (index or path).',
75
- );
76
- }
77
-
78
- return {
79
- loginPath: loginPath ?? "/",
80
- homePath: homePath ?? "/",
81
- };
82
- }
83
-
84
- export function toProtectedRouteObject(route: AdminRouteChild): RouteObject {
85
- if ("index" in route && route.index) {
86
- return { index: true, element: route.element };
87
- }
88
- return { path: route.path!, element: route.element };
89
- }
1
+ import type { RouteObject } from "react-router-dom";
2
+ import type { AdminRouteChild, AuthRedirects, RouteAccess } from "../types";
3
+
4
+ export function getRouteAccess(route: AdminRouteChild): RouteAccess {
5
+ return route.access ?? "protected";
6
+ }
7
+
8
+ export function partitionAdminRoutes(routes: AdminRouteChild[]) {
9
+ const guest: AdminRouteChild[] = [];
10
+ const publicRoutes: AdminRouteChild[] = [];
11
+ const protectedRoutes: AdminRouteChild[] = [];
12
+
13
+ for (const route of routes) {
14
+ const access = getRouteAccess(route);
15
+ if (access === "guest") guest.push(route);
16
+ else if (access === "public") publicRoutes.push(route);
17
+ else protectedRoutes.push(route);
18
+ }
19
+
20
+ return { guest, public: publicRoutes, protected: protectedRoutes };
21
+ }
22
+
23
+ /** React Router path segment for a top-level route (`/login` → `login`). */
24
+ export function toRouterSegment(path: string): string {
25
+ return path.replace(/^\/+/, "");
26
+ }
27
+
28
+ export function toAbsolutePath(path: string): string {
29
+ return `/${toRouterSegment(path)}`;
30
+ }
31
+
32
+ /**
33
+ * Resolve redirect targets from declared routes and optional overrides.
34
+ * Throws when protected or guest routes exist but paths cannot be determined.
35
+ */
36
+ export function deriveAuthPaths(
37
+ routes: AdminRouteChild[],
38
+ redirects?: AuthRedirects,
39
+ ): { loginPath: string; homePath: string } {
40
+ const { guest, protected: protectedRoutes } = partitionAdminRoutes(routes);
41
+ const firstGuest = guest.find((r) => "path" in r && r.path);
42
+ const indexProtected = protectedRoutes.find(
43
+ (r) => "index" in r && r.index,
44
+ );
45
+ const firstProtectedPath = protectedRoutes.find(
46
+ (r) => "path" in r && r.path,
47
+ );
48
+
49
+ let loginPath: string | undefined = redirects?.unauthenticated;
50
+ if (!loginPath && firstGuest && "path" in firstGuest && firstGuest.path) {
51
+ loginPath = toAbsolutePath(firstGuest.path);
52
+ }
53
+
54
+ let homePath: string | undefined = redirects?.afterLogin;
55
+ if (!homePath) {
56
+ if (indexProtected) homePath = "/";
57
+ else if (
58
+ firstProtectedPath &&
59
+ "path" in firstProtectedPath &&
60
+ firstProtectedPath.path
61
+ ) {
62
+ homePath = toAbsolutePath(firstProtectedPath.path);
63
+ }
64
+ }
65
+
66
+ if (protectedRoutes.length > 0 && !loginPath) {
67
+ throw new Error(
68
+ 'createAdminRouter: protected routes require redirects.unauthenticated or a guest route (access: "guest").',
69
+ );
70
+ }
71
+
72
+ if (guest.length > 0 && !homePath) {
73
+ throw new Error(
74
+ 'createAdminRouter: guest routes require redirects.afterLogin or a protected route (index or path).',
75
+ );
76
+ }
77
+
78
+ return {
79
+ loginPath: loginPath ?? "/",
80
+ homePath: homePath ?? "/",
81
+ };
82
+ }
83
+
84
+ export function toProtectedRouteObject(route: AdminRouteChild): RouteObject {
85
+ if ("index" in route && route.index) {
86
+ return { index: true, element: route.element };
87
+ }
88
+ return { path: route.path!, element: route.element };
89
+ }
package/src/types.ts CHANGED
@@ -35,6 +35,11 @@ export type AuthAdapter = {
35
35
  logout: () => void;
36
36
  /** Synchronous read of session (e.g. token). Used after mount and after login/logout. */
37
37
  getToken: () => string | null;
38
+ /**
39
+ * Label for the account menu in `AdminLayout` (top-right button).
40
+ * Return `null` when no user is loaded. Defaults to `"User"` when omitted.
41
+ */
42
+ getUserLabel?: () => string | null;
38
43
  };
39
44
 
40
45
  /** How a route participates in auth when using `AdminApp` / `createAdminRouter`. */
@@ -77,6 +82,11 @@ export type AdminLayoutProps = {
77
82
  loginPath?: string;
78
83
  /** localStorage key for sider collapsed state. Default `ding-react-admin-sider-collapsed`. */
79
84
  siderCollapsedStorageKey?: string;
85
+ /**
86
+ * Sidebar menu search. Enabled by default; pass `false` to hide.
87
+ * Use an object to customize the placeholder.
88
+ */
89
+ navSearch?: boolean | { placeholder?: string };
80
90
  };
81
91
 
82
92
  export type { AuthPageLayoutProps } from "./layouts/AuthPageLayout";
package/src/vite-env.d.ts CHANGED
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
@@ -1,18 +0,0 @@
1
- import { MutableRefObject, ReactNode } from 'react';
2
- /** Tracks which top-level field sources are included in the save payload. */
3
- export declare function SubmitFieldsProvider({ children, fieldsRef, }: {
4
- children: ReactNode;
5
- fieldsRef: MutableRefObject<Set<string>>;
6
- }): import("react/jsx-runtime").JSX.Element;
7
- export declare function useSubmitFieldsOptional(): MutableRefObject<Set<string>> | null;
8
- /**
9
- * Remember a field's `source` for submit (`pickBySources`).
10
- * Top-level fields only — skip for inline row cells (nested `name` paths).
11
- */
12
- export declare function useSubmitField(source: string, enabled?: boolean): void;
13
- /**
14
- * Remember a field's `source` for tab/step error highlighting.
15
- * Top-level fields only — no-op outside FormTabs / FormSteps.
16
- */
17
- export declare function useSectionField(source: string, enabled?: boolean): void;
18
- //# sourceMappingURL=SubmitFieldsContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SubmitFieldsContext.d.ts","sourceRoot":"","sources":["../../../../src/crud/context/SubmitFieldsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAC1C,2CAMA;AAED,wBAAgB,uBAAuB,yCAEtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,QAU5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,UAAO,QAU7D"}
@@ -1,3 +0,0 @@
1
- /** Read nested value from a record using dot notation, e.g. `brand.name`. */
2
- export declare function getByPath(record: Record<string, unknown>, path: string): unknown;
3
- //# sourceMappingURL=getByPath.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getByPath.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/getByPath.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,GACX,OAAO,CAQT"}
@@ -1,3 +0,0 @@
1
- /** Shared field-array name for InlineFormSet + ResourceForm inline loading. */
2
- export declare function inlineArrayName(resource: string, name?: string): string;
3
- //# sourceMappingURL=inlineArrayName.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"inlineArrayName.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/inlineArrayName.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,UAE9D"}
@@ -1,3 +0,0 @@
1
- /** RHF field path for one cell in an inline field array row. */
2
- export declare function inlineFieldName(arrayName: string, index: number, source: string): string;
3
- //# sourceMappingURL=inlineFieldName.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"inlineFieldName.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/inlineFieldName.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,UAGf"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Build a submit payload from form values using declared field `source` paths.
3
- * Flat `username` → `{ username }`; nested `invoiceLine.product` → `{ invoiceLine: { product } }`.
4
- */
5
- export declare function pickBySources(values: Record<string, unknown>, sources: string[]): Record<string, unknown>;
6
- //# sourceMappingURL=pickBySources.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pickBySources.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/pickBySources.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB"}
@@ -1,3 +0,0 @@
1
- /** Write a value into a record using dot notation, e.g. `invoiceLine.product`. */
2
- export declare function setByPath(record: Record<string, unknown>, path: string, value: unknown): void;
3
- //# sourceMappingURL=setByPath.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setByPath.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/setByPath.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,IAAI,CAYN"}
@@ -1,47 +0,0 @@
1
- import { MessageInstance } from 'antd/es/message/interface';
2
- import { MutableRefObject } from 'react';
3
- import { NavigateFunction } from 'react-router-dom';
4
- import { DataProvider } from '../../data/dataProviderTypes';
5
- import { FieldValues, UseFormReturn } from 'react-hook-form';
6
- import { InlineFormSetProps } from '../InlineFormSet';
7
- export type ResourceFormInlineConfig = Pick<InlineFormSetProps, "resource" | "foreignKey" | "name">;
8
- export declare function resolveInlineArrayName(config: ResourceFormInlineConfig): string;
9
- type LoadOptions<T extends FieldValues> = {
10
- dp: DataProvider;
11
- resource: string;
12
- id: string | undefined;
13
- isNew: boolean;
14
- form: UseFormReturn<T>;
15
- message: MessageInstance;
16
- defaultValues?: Partial<T>;
17
- inlines?: ResourceFormInlineConfig[];
18
- };
19
- /** Edit mode: fetch parent (+ inline rows) → form.reset → bump formVersion for field arrays. */
20
- export declare function useResourceFormLoad<T extends FieldValues>({ dp, resource, id, isNew, form, message, defaultValues, inlines, }: LoadOptions<T>): {
21
- loading: boolean;
22
- formVersion: number;
23
- existingInlineIds: Record<string, (string | number)[]>;
24
- };
25
- type SubmitOptions<T extends FieldValues & {
26
- id?: unknown;
27
- }> = {
28
- dp: DataProvider;
29
- resource: string;
30
- id: string | undefined;
31
- isNew: boolean;
32
- form: UseFormReturn<T>;
33
- message: MessageInstance;
34
- navigate: NavigateFunction;
35
- listPath: string;
36
- submitFieldsRef: MutableRefObject<Set<string>>;
37
- inlines?: ResourceFormInlineConfig[];
38
- existingInlineIds: Record<string, (string | number)[]>;
39
- onSaved?: (record: T) => void;
40
- stayOnPage?: boolean;
41
- };
42
- /** Save: pickBySources → create/update parent → saveInlineRows for each inline config. */
43
- export declare function useResourceFormSubmit<T extends FieldValues & {
44
- id?: unknown;
45
- }>({ dp, resource, id, isNew, form, message, navigate, listPath, submitFieldsRef, inlines, existingInlineIds, onSaved, stayOnPage, }: SubmitOptions<T>): (values: T) => Promise<void>;
46
- export {};
47
- //# sourceMappingURL=useResourceFormData.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useResourceFormData.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/useResourceFormData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,kBAAkB,EAClB,UAAU,GAAG,YAAY,GAAG,MAAM,CACnC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,UAEtE;AAED,KAAK,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI;IACxC,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,eAAe,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC,CAAC;AAEF,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,WAAW,EAAE,EACzD,EAAE,EACF,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,OAAO,EACP,aAAa,EACb,OAAO,GACR,EAAE,WAAW,CAAC,CAAC,CAAC;;;;EAyDhB;AAED,KAAK,aAAa,CAAC,CAAC,SAAS,WAAW,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,IAAI;IAC7D,EAAE,EAAE,YAAY,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,WAAW,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,EAC9E,EAAE,EACF,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,UAAU,GACX,EAAE,aAAa,CAAC,CAAC,CAAC,YAEA,CAAC,mBAoFnB"}
@@ -1,62 +0,0 @@
1
- import {
2
- createContext,
3
- useContext,
4
- useEffect,
5
- type MutableRefObject,
6
- type ReactNode,
7
- } from "react";
8
- import { useFormSectionSourcesOptional } from "./FormSectionContext";
9
-
10
- const SubmitFieldsContext =
11
- createContext<MutableRefObject<Set<string>> | null>(null);
12
-
13
- /** Tracks which top-level field sources are included in the save payload. */
14
- export function SubmitFieldsProvider({
15
- children,
16
- fieldsRef,
17
- }: {
18
- children: ReactNode;
19
- fieldsRef: MutableRefObject<Set<string>>;
20
- }) {
21
- return (
22
- <SubmitFieldsContext.Provider value={fieldsRef}>
23
- {children}
24
- </SubmitFieldsContext.Provider>
25
- );
26
- }
27
-
28
- export function useSubmitFieldsOptional() {
29
- return useContext(SubmitFieldsContext);
30
- }
31
-
32
- /**
33
- * Remember a field's `source` for submit (`pickBySources`).
34
- * Top-level fields only — skip for inline row cells (nested `name` paths).
35
- */
36
- export function useSubmitField(source: string, enabled = true) {
37
- const fieldsRef = useSubmitFieldsOptional();
38
-
39
- useEffect(() => {
40
- if (!enabled || !fieldsRef) return;
41
- fieldsRef.current.add(source);
42
- return () => {
43
- fieldsRef.current.delete(source);
44
- };
45
- }, [fieldsRef, source, enabled]);
46
- }
47
-
48
- /**
49
- * Remember a field's `source` for tab/step error highlighting.
50
- * Top-level fields only — no-op outside FormTabs / FormSteps.
51
- */
52
- export function useSectionField(source: string, enabled = true) {
53
- const sectionRef = useFormSectionSourcesOptional();
54
-
55
- useEffect(() => {
56
- if (!enabled || !sectionRef) return;
57
- sectionRef.current.add(source);
58
- return () => {
59
- sectionRef.current.delete(source);
60
- };
61
- }, [sectionRef, source, enabled]);
62
- }
@@ -1,4 +0,0 @@
1
- /** Shared field-array name for InlineFormSet + ResourceForm inline loading. */
2
- export function inlineArrayName(resource: string, name?: string) {
3
- return name ?? `__inline_${resource.replace(/[^a-z0-9]/gi, "_")}`;
4
- }
@@ -1,8 +0,0 @@
1
- /** RHF field path for one cell in an inline field array row. */
2
- export function inlineFieldName(
3
- arrayName: string,
4
- index: number,
5
- source: string,
6
- ) {
7
- return `${arrayName}.${index}.${source}`;
8
- }
@@ -1,24 +0,0 @@
1
- import { getByPath } from "./getByPath";
2
- import { setByPath } from "./setByPath";
3
-
4
- /**
5
- * Build a submit payload from form values using declared field `source` paths.
6
- * Flat `username` → `{ username }`; nested `invoiceLine.product` → `{ invoiceLine: { product } }`.
7
- */
8
- export function pickBySources(
9
- values: Record<string, unknown>,
10
- sources: string[],
11
- ): Record<string, unknown> {
12
- if (sources.length === 0) {
13
- return { ...values };
14
- }
15
-
16
- const result: Record<string, unknown> = {};
17
- for (const source of sources) {
18
- const value = getByPath(values, source);
19
- if (value !== undefined) {
20
- setByPath(result, source, value);
21
- }
22
- }
23
- return result;
24
- }