ding-react-admin 2.0.8 → 2.1.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 (81) hide show
  1. package/README.md +1 -1
  2. package/dist/index.css +1 -1
  3. package/dist/index.js +1604 -1119
  4. package/dist/src/components/AppHub.d.ts +8 -0
  5. package/dist/src/components/AppHub.d.ts.map +1 -0
  6. package/dist/src/components/AppLauncherButton.d.ts +7 -0
  7. package/dist/src/components/AppLauncherButton.d.ts.map +1 -0
  8. package/dist/src/components/index.d.ts +2 -0
  9. package/dist/src/components/index.d.ts.map +1 -1
  10. package/dist/src/crud/InlineFormSet.d.ts +6 -2
  11. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  12. package/dist/src/crud/ResourceList.d.ts +6 -1
  13. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  14. package/dist/src/crud/context/FilterContext.d.ts +4 -1
  15. package/dist/src/crud/context/FilterContext.d.ts.map +1 -1
  16. package/dist/src/crud/fields/BooleanField.d.ts +2 -1
  17. package/dist/src/crud/fields/BooleanField.d.ts.map +1 -1
  18. package/dist/src/crud/fields/DateField.d.ts.map +1 -1
  19. package/dist/src/crud/fields/DateTimeField.d.ts +5 -0
  20. package/dist/src/crud/fields/DateTimeField.d.ts.map +1 -0
  21. package/dist/src/crud/fields/ReferenceField.d.ts +3 -1
  22. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  23. package/dist/src/crud/fields/ReferenceManyField.d.ts +4 -1
  24. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  25. package/dist/src/crud/fields/TextAreaField.d.ts +19 -0
  26. package/dist/src/crud/fields/TextAreaField.d.ts.map +1 -0
  27. package/dist/src/crud/fields/TimeField.d.ts +11 -0
  28. package/dist/src/crud/fields/TimeField.d.ts.map +1 -0
  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/filters/TextFilter.d.ts +9 -1
  32. package/dist/src/crud/filters/TextFilter.d.ts.map +1 -1
  33. package/dist/src/crud/index.d.ts +6 -1
  34. package/dist/src/crud/index.d.ts.map +1 -1
  35. package/dist/src/crud/types.d.ts +9 -0
  36. package/dist/src/crud/types.d.ts.map +1 -1
  37. package/dist/src/crud/utils/parseDayjsValue.d.ts +3 -0
  38. package/dist/src/crud/utils/parseDayjsValue.d.ts.map +1 -0
  39. package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts +11 -0
  40. package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts.map +1 -0
  41. package/dist/src/crud/utils/useDebouncedValue.d.ts +3 -0
  42. package/dist/src/crud/utils/useDebouncedValue.d.ts.map +1 -0
  43. package/dist/src/index.d.ts +4 -4
  44. package/dist/src/index.d.ts.map +1 -1
  45. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  46. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  47. package/dist/src/layouts/navMenuItems.d.ts +3 -0
  48. package/dist/src/layouts/navMenuItems.d.ts.map +1 -1
  49. package/dist/src/router/createAdminRouter.d.ts.map +1 -1
  50. package/dist/src/types.d.ts +23 -0
  51. package/dist/src/types.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/components/AppHub.tsx +47 -0
  54. package/src/components/AppLauncherButton.tsx +33 -0
  55. package/src/components/NavMenuLabel.tsx +1 -1
  56. package/src/components/index.ts +2 -0
  57. package/src/components/navMenu.css +107 -0
  58. package/src/context/AppThemeProvider.tsx +1 -1
  59. package/src/crud/InlineFormSet.tsx +32 -14
  60. package/src/crud/ResourceList.tsx +211 -35
  61. package/src/crud/context/FilterContext.tsx +7 -1
  62. package/src/crud/fields/BooleanField.tsx +3 -1
  63. package/src/crud/fields/DateField.tsx +6 -4
  64. package/src/crud/fields/DateTimeField.tsx +9 -0
  65. package/src/crud/fields/ReferenceField.tsx +10 -0
  66. package/src/crud/fields/ReferenceManyField.tsx +13 -1
  67. package/src/crud/fields/TextAreaField.tsx +58 -0
  68. package/src/crud/fields/TimeField.tsx +48 -0
  69. package/src/crud/filters/ReferenceFilter.tsx +10 -0
  70. package/src/crud/filters/TextFilter.tsx +80 -9
  71. package/src/crud/index.ts +9 -1
  72. package/src/crud/resourceListTable.css +113 -0
  73. package/src/crud/types.ts +9 -0
  74. package/src/crud/utils/parseDayjsValue.ts +14 -0
  75. package/src/crud/utils/referenceSelectDropdownProps.ts +30 -0
  76. package/src/crud/utils/useDebouncedValue.ts +17 -0
  77. package/src/index.ts +11 -0
  78. package/src/layouts/AdminLayout.tsx +47 -9
  79. package/src/layouts/navMenuItems.tsx +11 -2
  80. package/src/router/createAdminRouter.tsx +3 -1
  81. package/src/types.ts +24 -0
@@ -0,0 +1,30 @@
1
+ import type { SelectProps } from "antd";
2
+
3
+ export const REFERENCE_SELECT_POPUP_MIN_WIDTH = 360;
4
+
5
+ export type ReferenceSelectDropdownOptions = {
6
+ /** When false (default), the dropdown may be wider than the input. */
7
+ popupMatchSelectWidth?: boolean | number;
8
+ /** Minimum popup width in px when `popupMatchSelectWidth` is false. */
9
+ popupMinWidth?: number;
10
+ };
11
+
12
+ /** Ant Design Select props so reference dropdowns are not clipped to input width. */
13
+ export function referenceSelectDropdownProps(
14
+ options: ReferenceSelectDropdownOptions = {},
15
+ ): Pick<SelectProps, "popupMatchSelectWidth" | "styles"> {
16
+ const popupMatchSelectWidth = options.popupMatchSelectWidth ?? false;
17
+ if (popupMatchSelectWidth !== false) {
18
+ return { popupMatchSelectWidth };
19
+ }
20
+
21
+ const minWidth = options.popupMinWidth ?? REFERENCE_SELECT_POPUP_MIN_WIDTH;
22
+ return {
23
+ popupMatchSelectWidth: false,
24
+ styles: {
25
+ popup: {
26
+ root: { minWidth },
27
+ },
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ /** Returns `value` after it stays unchanged for `delayMs`. When `delayMs <= 0`, returns `value` immediately. */
4
+ export function useDebouncedValue<T>(value: T, delayMs: number): T {
5
+ const [debounced, setDebounced] = useState(value);
6
+
7
+ useEffect(() => {
8
+ if (delayMs <= 0) {
9
+ setDebounced(value);
10
+ return;
11
+ }
12
+ const timer = window.setTimeout(() => setDebounced(value), delayMs);
13
+ return () => window.clearTimeout(timer);
14
+ }, [value, delayMs]);
15
+
16
+ return delayMs <= 0 ? value : debounced;
17
+ }
package/src/index.ts CHANGED
@@ -89,6 +89,8 @@ export { LoginPage } from "./pages/LoginPage";
89
89
  export { PlaceholderPage } from "./pages/PlaceholderPage";
90
90
  export { AuthPageLayout } from "./layouts/AuthPageLayout";
91
91
  export {
92
+ AppHub,
93
+ AppLauncherButton,
92
94
  AuthAlternateLink,
93
95
  DensitySwitch,
94
96
  ThemeSwitch,
@@ -110,10 +112,13 @@ export type {
110
112
  LoginCredentials,
111
113
  AuthRedirects,
112
114
  CreateAdminRouterOptions,
115
+ AppHubProps,
116
+ AppLauncherButtonProps,
113
117
  AuthAlternateLinkProps,
114
118
  AuthPageLayoutProps,
115
119
  LoginPageProps,
116
120
  NavItem,
121
+ NavMenuItemDivider,
117
122
  RouteAccess,
118
123
  ThemeDensity,
119
124
  ThemeMode,
@@ -130,9 +135,12 @@ export {
130
135
  FormSteps,
131
136
  FormStep,
132
137
  TextField,
138
+ TextAreaField,
133
139
  NumberField,
134
140
  BooleanField,
135
141
  DateField,
142
+ DateTimeField,
143
+ TimeField,
136
144
  SelectField,
137
145
  PasswordField,
138
146
  ReferenceField,
@@ -149,6 +157,7 @@ export {
149
157
  ImageColumn,
150
158
  CustomColumn,
151
159
  TextFilter,
160
+ DEFAULT_TEXT_FILTER_DEBOUNCE_MS,
152
161
  NumberFilter,
153
162
  BooleanFilter,
154
163
  DateFilter,
@@ -182,6 +191,7 @@ export type {
182
191
  FormStepProps,
183
192
  InlineFormSetLayout,
184
193
  ResourceListProps,
194
+ TextFilterProps,
185
195
  BaseSourceProps,
186
196
  ChoiceOption,
187
197
  ChoicesLoader,
@@ -196,6 +206,7 @@ export type {
196
206
  InlineFormSetBaseProps,
197
207
  InlineRowContext,
198
208
  InlineFieldRegistration,
209
+ TextAreaFieldProps,
199
210
  FieldWrapperProps,
200
211
  ImageFieldProps,
201
212
  FileFieldProps,
@@ -29,7 +29,7 @@ import {
29
29
  } from "./navFilter";
30
30
  import { navItemsToAntdItems } from "./navMenuItems";
31
31
  import { filterNavByPermission } from "../permissions/resourcePermissions";
32
- import type { AdminLayoutProps, NavItem } from "../types";
32
+ import type { AdminLayoutProps, NavItem, NavMenuItemDivider } from "../types";
33
33
  import "../components/navMenu.css";
34
34
 
35
35
  const LIGHT_SIDEBAR_BG = "#001529";
@@ -87,15 +87,36 @@ type NavMenuProps = {
87
87
  openKeys?: string[];
88
88
  onOpenChange?: (keys: string[]) => void;
89
89
  onNavigate: (key: string) => void;
90
+ wrapLabels?: boolean;
91
+ itemDivider?: NavMenuItemDivider;
90
92
  };
91
93
 
92
- type SiderNavPanelProps = NavMenuProps & {
94
+ function navMenuClassName({
95
+ wrapLabels,
96
+ itemDivider = "none",
97
+ }: {
98
+ wrapLabels?: boolean;
99
+ itemDivider?: NavMenuItemDivider;
100
+ }): string {
101
+ const classes = ["ding-admin-nav-menu"];
102
+ if (wrapLabels) classes.push("ding-admin-nav-menu--wrap-labels");
103
+ if (itemDivider === "full") {
104
+ classes.push("ding-admin-nav-menu--item-divider-full");
105
+ } else if (itemDivider === "inset") {
106
+ classes.push("ding-admin-nav-menu--item-divider-inset");
107
+ }
108
+ return classes.join(" ");
109
+ }
110
+
111
+ type SiderNavPanelProps = Omit<NavMenuProps, "wrapLabels" | "itemDivider"> & {
93
112
  navQuery: string;
94
113
  onNavQueryChange: (value: string) => void;
95
114
  showNavSearch: boolean;
96
115
  navSearchPlaceholder?: string;
97
116
  scrollVariant: "default" | "on-dark";
98
117
  searchVariant: "on-dark" | "app";
118
+ wrapLabels?: boolean;
119
+ itemDivider?: NavMenuItemDivider;
99
120
  };
100
121
 
101
122
  function SiderNavPanel({
@@ -111,6 +132,8 @@ function SiderNavPanel({
111
132
  navSearchPlaceholder,
112
133
  scrollVariant,
113
134
  searchVariant,
135
+ wrapLabels,
136
+ itemDivider,
114
137
  }: SiderNavPanelProps) {
115
138
  return (
116
139
  <>
@@ -138,6 +161,8 @@ function SiderNavPanel({
138
161
  openKeys={openKeys}
139
162
  onOpenChange={onOpenChange}
140
163
  onNavigate={onNavigate}
164
+ wrapLabels={wrapLabels}
165
+ itemDivider={itemDivider}
141
166
  />
142
167
  </ScrollableArea>
143
168
  </>
@@ -151,15 +176,17 @@ function NavMenu({
151
176
  openKeys,
152
177
  onOpenChange,
153
178
  onNavigate,
179
+ wrapLabels,
180
+ itemDivider,
154
181
  }: NavMenuProps) {
155
182
  return (
156
183
  <Menu
157
- className="ding-admin-nav-menu"
184
+ className={navMenuClassName({ wrapLabels, itemDivider })}
158
185
  mode="inline"
159
186
  theme="dark"
160
187
  inlineCollapsed={inlineCollapsed}
161
188
  selectedKeys={selectedKeys}
162
- tooltip={{ placement: "right", mouseEnterDelay: 1 }}
189
+ tooltip={{ placement: "right", mouseEnterDelay: 0 }}
163
190
  {...(!inlineCollapsed && openKeys !== undefined && onOpenChange
164
191
  ? { openKeys, onOpenChange }
165
192
  : {})}
@@ -181,6 +208,8 @@ export function AdminLayout({
181
208
  loginPath = "/login",
182
209
  siderCollapsedStorageKey = DEFAULT_SIDER_KEY,
183
210
  navSearch = true,
211
+ navMenu,
212
+ hideSider = false,
184
213
  }: AdminLayoutProps) {
185
214
  const navigate = useNavigate();
186
215
  const location = useLocation();
@@ -200,6 +229,9 @@ export function AdminLayout({
200
229
  const navSearchEnabled = navSearch !== false;
201
230
  const navSearchPlaceholder =
202
231
  typeof navSearch === "object" ? navSearch.placeholder : undefined;
232
+ const navMenuWrapLabels = navMenu?.wrapLabels !== false;
233
+ const navMenuItemDivider: NavMenuItemDivider =
234
+ navMenu?.itemDivider ?? "inset";
203
235
 
204
236
  const drawerTitle = mobileDrawerTitle ?? brand;
205
237
 
@@ -260,9 +292,11 @@ export function AdminLayout({
260
292
  const menuItems: MenuProps["items"] = useMemo(
261
293
  () =>
262
294
  navItemsToAntdItems(displayNavItems, {
263
- showLabelTooltip: !collapsed,
295
+ showLabelTooltip: !collapsed && !navMenuWrapLabels,
296
+ wrapLabels: navMenuWrapLabels && !collapsed,
297
+ collapsed,
264
298
  }),
265
- [displayNavItems, collapsed],
299
+ [displayNavItems, collapsed, navMenuWrapLabels],
266
300
  );
267
301
 
268
302
  const filteredOpenKeys = useMemo(
@@ -335,7 +369,7 @@ export function AdminLayout({
335
369
  background: token.colorBgLayout,
336
370
  }}
337
371
  >
338
- {!isMobile && (
372
+ {!hideSider && !isMobile && (
339
373
  <Layout.Sider
340
374
  collapsible
341
375
  collapsed={collapsed}
@@ -388,11 +422,13 @@ export function AdminLayout({
388
422
  navSearchPlaceholder={navSearchPlaceholder}
389
423
  scrollVariant={siderScrollVariant}
390
424
  searchVariant={siderSearchVariant}
425
+ wrapLabels={navMenuWrapLabels}
426
+ itemDivider={navMenuItemDivider}
391
427
  />
392
428
  </div>
393
429
  </Layout.Sider>
394
430
  )}
395
- {isMobile && (
431
+ {!hideSider && isMobile && (
396
432
  <Drawer
397
433
  title={
398
434
  <Typography.Text
@@ -436,6 +472,8 @@ export function AdminLayout({
436
472
  navSearchPlaceholder={navSearchPlaceholder}
437
473
  scrollVariant={siderScrollVariant}
438
474
  searchVariant={siderSearchVariant}
475
+ wrapLabels={navMenuWrapLabels}
476
+ itemDivider={navMenuItemDivider}
439
477
  />
440
478
  </div>
441
479
  </Drawer>
@@ -461,7 +499,7 @@ export function AdminLayout({
461
499
  flexShrink: 0,
462
500
  }}
463
501
  >
464
- {isMobile && (
502
+ {!hideSider && isMobile && (
465
503
  <Button
466
504
  type="text"
467
505
  icon={<MenuOutlined />}
@@ -5,9 +5,16 @@ import { getNavItemLabel } from "./navFilter";
5
5
 
6
6
  export function navItemsToAntdItems(
7
7
  items: NavItem[],
8
- options?: { showLabelTooltip?: boolean },
8
+ options?: {
9
+ showLabelTooltip?: boolean;
10
+ wrapLabels?: boolean;
11
+ /** Collapsed sider: native `title` for icon tooltips only. */
12
+ collapsed?: boolean;
13
+ },
9
14
  ): NonNullable<MenuProps["items"]> {
10
15
  const showLabelTooltip = options?.showLabelTooltip !== false;
16
+ const wrapLabels = options?.wrapLabels === true;
17
+ const collapsed = options?.collapsed === true;
11
18
 
12
19
  return items.map((item) => {
13
20
  const IconComp = item.Icon;
@@ -16,6 +23,8 @@ export function navItemsToAntdItems(
16
23
  const label =
17
24
  title && showLabelTooltip ? (
18
25
  <NavMenuLabel label={item.label} title={title} />
26
+ ) : wrapLabels ? (
27
+ <NavMenuLabel label={item.label} title="" />
19
28
  ) : (
20
29
  item.label
21
30
  );
@@ -23,7 +32,7 @@ export function navItemsToAntdItems(
23
32
  // Expanded: NavMenuLabel tooltip only (omit `title` to avoid native DOM tooltip).
24
33
  // Collapsed: string title for the sider icon tooltip.
25
34
  const itemTitle: { title?: string } =
26
- !showLabelTooltip && title ? { title } : {};
35
+ collapsed && title ? { title } : {};
27
36
 
28
37
  if (item.children?.length) {
29
38
  return {
@@ -60,5 +60,7 @@ export function createAdminRouter({
60
60
  element: <Navigate to={homePath} replace />,
61
61
  });
62
62
 
63
- return createBrowserRouter(routerRoutes);
63
+ return createBrowserRouter(routerRoutes, {
64
+ basename: import.meta.env.BASE_URL.replace(/\/$/, "") || undefined,
65
+ });
64
66
  }
package/src/types.ts CHANGED
@@ -66,6 +66,8 @@ export type AppThemeProviderProps = {
66
66
  densityStorageKey?: string;
67
67
  };
68
68
 
69
+ export type NavMenuItemDivider = "none" | "full" | "inset";
70
+
69
71
  export type AdminLayoutProps = {
70
72
  navItems: NavItem[];
71
73
  /** Shown in sider header when expanded; drawer title on mobile. */
@@ -87,9 +89,31 @@ export type AdminLayoutProps = {
87
89
  * Use an object to customize the placeholder.
88
90
  */
89
91
  navSearch?: boolean | { placeholder?: string };
92
+ /**
93
+ * Sidebar menu presentation.
94
+ */
95
+ navMenu?: {
96
+ /**
97
+ * Wrap long labels onto multiple lines instead of truncating with ellipsis.
98
+ * Default **`true`**. Pass **`false`** for ellipsis + Ant Design hover tooltip.
99
+ */
100
+ wrapLabels?: boolean;
101
+ /**
102
+ * Divider between sibling rows at each submenu level.
103
+ * Default **`"inset"`**.
104
+ */
105
+ itemDivider?: NavMenuItemDivider;
106
+ };
107
+ /**
108
+ * Hide the sidebar and mobile nav drawer (e.g. on an Odoo-style app hub home).
109
+ * Default **`false`**.
110
+ */
111
+ hideSider?: boolean;
90
112
  };
91
113
 
92
114
  export type { AuthPageLayoutProps } from "./layouts/AuthPageLayout";
115
+ export type { AppHubProps } from "./components/AppHub";
116
+ export type { AppLauncherButtonProps } from "./components/AppLauncherButton";
93
117
  export type { AuthAlternateLinkProps } from "./components/AuthAlternateLink";
94
118
 
95
119
  export type LoginPageProps = {