ding-react-admin 2.0.0 → 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 (169) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -195
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +1772 -1169
  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/ResourceForm.d.ts +1 -4
  18. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  20. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  22. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  23. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  24. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  25. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  26. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  27. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  28. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  29. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  30. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  31. package/dist/src/crud/index.d.ts +5 -0
  32. package/dist/src/crud/index.d.ts.map +1 -1
  33. package/dist/src/crud/types.d.ts +12 -0
  34. package/dist/src/crud/types.d.ts.map +1 -1
  35. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  36. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  37. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  38. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  39. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  40. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  41. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  42. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  43. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +1 -0
  44. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -1
  45. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  46. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  47. package/dist/src/crud/utils/toFormData.d.ts +1 -1
  48. package/dist/src/crud/utils/toFormData.d.ts.map +1 -1
  49. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  50. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  51. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  52. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  53. package/dist/src/crud/utils/useFormRecord.d.ts +9 -7
  54. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -1
  55. package/dist/src/data/parseFormErrorHelpers.d.ts +6 -27
  56. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  57. package/dist/src/index.d.ts +2 -2
  58. package/dist/src/index.d.ts.map +1 -1
  59. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  60. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  61. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  62. package/dist/src/layouts/navFilter.d.ts +5 -0
  63. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  64. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  65. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  66. package/dist/src/types.d.ts +12 -0
  67. package/dist/src/types.d.ts.map +1 -1
  68. package/package.json +79 -59
  69. package/src/app/AdminApp.tsx +34 -34
  70. package/src/components/AuthAlternateLink.tsx +23 -23
  71. package/src/components/DensitySwitch.tsx +31 -31
  72. package/src/components/NavMenuLabel.tsx +26 -0
  73. package/src/components/NavMenuSearch.tsx +76 -0
  74. package/src/components/ScrollableArea.tsx +46 -0
  75. package/src/components/ThemeSwitch.tsx +28 -28
  76. package/src/components/ThemeToolbar.tsx +34 -34
  77. package/src/components/index.ts +7 -4
  78. package/src/components/navMenu.css +21 -0
  79. package/src/components/scrollable.css +28 -0
  80. package/src/context/AppThemeProvider.tsx +128 -128
  81. package/src/context/AuthProvider.tsx +89 -77
  82. package/src/context/DataProvider.tsx +30 -30
  83. package/src/context/PermissionsProvider.tsx +57 -57
  84. package/src/crud/FilterBar.tsx +36 -36
  85. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  86. package/src/crud/FormSteps.tsx +135 -135
  87. package/src/crud/FormTabs.tsx +126 -126
  88. package/src/crud/InlineFormSet.tsx +162 -162
  89. package/src/crud/ListActionsBar.tsx +106 -106
  90. package/src/crud/ResourceForm.tsx +83 -160
  91. package/src/crud/ResourceFormModal.tsx +75 -133
  92. package/src/crud/ResourceRecordForm.tsx +166 -0
  93. package/src/crud/columns/BooleanColumn.tsx +32 -32
  94. package/src/crud/columns/CustomColumn.tsx +33 -33
  95. package/src/crud/columns/DateColumn.tsx +28 -28
  96. package/src/crud/columns/ImageColumn.tsx +58 -58
  97. package/src/crud/columns/NumberColumn.tsx +31 -31
  98. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  99. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  100. package/src/crud/columns/TextColumn.tsx +38 -38
  101. package/src/crud/context/FilterContext.tsx +71 -71
  102. package/src/crud/context/FormContext.tsx +39 -39
  103. package/src/crud/context/FormSectionContext.tsx +22 -22
  104. package/src/crud/context/InlineFieldsRegistry.tsx +61 -61
  105. package/src/crud/context/ListContext.tsx +93 -93
  106. package/src/crud/fields/BooleanField.tsx +38 -38
  107. package/src/crud/fields/DateField.tsx +53 -53
  108. package/src/crud/fields/FileField.tsx +123 -123
  109. package/src/crud/fields/ImageField.tsx +120 -120
  110. package/src/crud/fields/NumberField.tsx +52 -52
  111. package/src/crud/fields/PasswordField.tsx +99 -99
  112. package/src/crud/fields/ReferenceField.tsx +237 -96
  113. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  114. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  115. package/src/crud/fields/SelectField.tsx +48 -48
  116. package/src/crud/fields/TextField.tsx +46 -46
  117. package/src/crud/fields/uploadFieldUtils.ts +21 -21
  118. package/src/crud/fields/useUploadPreviewUrl.ts +20 -20
  119. package/src/crud/filters/BooleanFilter.tsx +38 -38
  120. package/src/crud/filters/DateFilter.tsx +35 -35
  121. package/src/crud/filters/NumberFilter.tsx +33 -33
  122. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  123. package/src/crud/filters/SelectFilter.tsx +44 -44
  124. package/src/crud/filters/TextFilter.tsx +36 -36
  125. package/src/crud/index.ts +99 -94
  126. package/src/crud/types.ts +146 -134
  127. package/src/crud/utils/buildFormPayload.ts +24 -24
  128. package/src/crud/utils/buildInlineRowsPayload.ts +37 -37
  129. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -29
  130. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  131. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  132. package/src/crud/utils/formErrors.test.ts +168 -0
  133. package/src/crud/utils/formErrors.ts +138 -42
  134. package/src/crud/utils/formSectionErrors.ts +85 -85
  135. package/src/crud/utils/getFormValue.ts +13 -13
  136. package/src/crud/utils/hasUploadValues.ts +9 -9
  137. package/src/crud/utils/nestedFieldPath.ts +8 -8
  138. package/src/crud/utils/prepareFormSubmitBody.ts +22 -17
  139. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  140. package/src/crud/utils/setFormValue.ts +18 -18
  141. package/src/crud/utils/sortQueryParam.ts +37 -37
  142. package/src/crud/utils/toFormData.ts +79 -81
  143. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  144. package/src/crud/utils/useAbortableEffect.ts +17 -17
  145. package/src/crud/utils/useChoices.ts +330 -182
  146. package/src/crud/utils/useFormRecord.ts +179 -156
  147. package/src/crud/utils/useListQueryState.ts +191 -191
  148. package/src/data/abortError.test.ts +19 -19
  149. package/src/data/abortError.ts +10 -10
  150. package/src/data/createMemoryResourceHandlers.ts +78 -78
  151. package/src/data/createRestResourceHandlers.ts +63 -63
  152. package/src/data/dataUtils.test.ts +228 -220
  153. package/src/data/inMemoryList.ts +97 -97
  154. package/src/data/parseFormErrorHelpers.test.ts +201 -138
  155. package/src/data/parseFormErrorHelpers.ts +376 -311
  156. package/src/data/sortHelpers.ts +48 -48
  157. package/src/index.ts +211 -207
  158. package/src/layouts/AdminLayout.tsx +530 -382
  159. package/src/layouts/AuthPageLayout.tsx +87 -81
  160. package/src/layouts/navFilter.ts +49 -0
  161. package/src/layouts/navMenuItems.tsx +44 -0
  162. package/src/pages/LoginPage.tsx +80 -80
  163. package/src/pages/PlaceholderPage.tsx +9 -9
  164. package/src/permissions/resourcePermissions.ts +47 -47
  165. package/src/router/createAdminRouter.tsx +64 -64
  166. package/src/router/guards.tsx +63 -63
  167. package/src/router/routeAccess.ts +89 -89
  168. package/src/types.ts +10 -0
  169. package/src/vite-env.d.ts +1 -1
@@ -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" />