@etsoo/toolpad 1.0.28 → 1.0.30

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.
@@ -27,7 +27,8 @@ export interface NavigationPageItem {
27
27
  pattern?: string;
28
28
  action?: React.ReactNode;
29
29
  children?: Navigation;
30
- subs?: string[];
30
+ hidden?: boolean;
31
+ pageHeader?: React.ReactNode;
31
32
  }
32
33
  export interface NavigationSubheaderItem {
33
34
  kind: "header";
@@ -131,6 +131,12 @@ const AppProviderComponent_1 = require("../AppProvider/AppProviderComponent");
131
131
  segment: "traffic",
132
132
  title: "Traffic",
133
133
  icon: (0, jsx_runtime_1.jsx)(Description_1.default, {})
134
+ },
135
+ {
136
+ segment: "hidden",
137
+ title: "Hidden",
138
+ icon: (0, jsx_runtime_1.jsx)(Description_1.default, {}),
139
+ hidden: true
134
140
  }
135
141
  ]
136
142
  }
@@ -146,6 +152,7 @@ const AppProviderComponent_1 = require("../AppProvider/AppProviderComponent");
146
152
  });
147
153
  (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByText("Sales")).toBeTruthy();
148
154
  (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByText("Traffic")).toBeTruthy();
155
+ (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).queryByText("Hidden")).toBeNull();
149
156
  });
150
157
  (0, vitest_1.test)("shows correct selected page item", () => {
151
158
  const NAVIGATION = [
@@ -157,7 +164,14 @@ const AppProviderComponent_1 = require("../AppProvider/AppProviderComponent");
157
164
  {
158
165
  title: "Orders",
159
166
  segment: "orders",
160
- icon: (0, jsx_runtime_1.jsx)(ShoppingCart_1.default, {})
167
+ icon: (0, jsx_runtime_1.jsx)(ShoppingCart_1.default, {}),
168
+ children: [
169
+ {
170
+ segment: "nested",
171
+ title: "Nested",
172
+ hidden: true
173
+ }
174
+ ]
161
175
  },
162
176
  {
163
177
  segment: "dynamic",
@@ -197,6 +211,8 @@ const AppProviderComponent_1 = require("../AppProvider/AppProviderComponent");
197
211
  rerender((0, jsx_runtime_1.jsx)(AppWithPathname, { pathname: "/orders" }));
198
212
  (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByRole("link", { name: "Dashboard" })).not.toHaveClass("Mui-selected");
199
213
  (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByRole("link", { name: "Orders" })).toHaveClass("Mui-selected");
214
+ rerender((0, jsx_runtime_1.jsx)(AppWithPathname, { pathname: "/orders/nested" }));
215
+ (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByRole("link", { name: "Orders" })).toHaveClass("Mui-selected");
200
216
  rerender((0, jsx_runtime_1.jsx)(AppWithPathname, { pathname: "/dynamic" }));
201
217
  (0, vitest_1.expect)((0, react_1.within)(desktopNavigation).getByRole("link", { name: "Dynamic" })).not.toHaveClass("Mui-selected");
202
218
  rerender((0, jsx_runtime_1.jsx)(AppWithPathname, { pathname: "/dynamic/123" }));
@@ -53,10 +53,10 @@ const Tooltip_1 = __importDefault(require("@mui/material/Tooltip"));
53
53
  const ExpandLess_1 = __importDefault(require("@mui/icons-material/ExpandLess"));
54
54
  const ExpandMore_1 = __importDefault(require("@mui/icons-material/ExpandMore"));
55
55
  const Link_1 = require("../shared/Link");
56
- const context_1 = require("../shared/context");
57
56
  const navigation_1 = require("../shared/navigation");
58
57
  const utils_1 = require("./utils");
59
58
  const styles_1 = require("@mui/material/styles");
59
+ const useActivePage_1 = require("../useActivePage/useActivePage");
60
60
  const NavigationListItemButton = (0, styles_1.styled)(ListItemButton_1.default)(({ theme }) => ({
61
61
  borderRadius: 8,
62
62
  "&.Mui-selected": {
@@ -87,8 +87,8 @@ const NavigationListItemButton = (0, styles_1.styled)(ListItemButton_1.default)(
87
87
  * @ignore - internal component.
88
88
  */
89
89
  function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0, onLinkClick, isMini = false, isFullyExpanded = true, hasDrawerTransitions = false, selectedItemId }) {
90
- const routerContext = React.useContext(context_1.RouterContext);
91
- const pathname = routerContext?.pathname ?? "/";
90
+ const activePage = (0, useActivePage_1.useActivePage)();
91
+ const pathname = activePage?.sourcePath ?? "/";
92
92
  const initialExpandedSidebarItemIds = React.useMemo(() => subNavigation
93
93
  .map((navigationItem, navigationItemIndex) => ({
94
94
  navigationItem,
@@ -130,6 +130,13 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
130
130
  : {})
131
131
  } }, `divider-${depth}-${navigationItemIndex}`));
132
132
  }
133
+ if (navigationItem.hidden) {
134
+ return null;
135
+ }
136
+ let children = navigationItem.children?.filter((child) => child.kind === "divider" || child.kind === "header" || !child.hidden);
137
+ if (children && children.length === 0) {
138
+ children = undefined;
139
+ }
133
140
  const navigationItemFullPath = (0, navigation_1.getPageItemFullPath)(basePath, navigationItem);
134
141
  const navigationItemId = `${depth}-${navigationItemIndex}`;
135
142
  const navigationItemTitle = (0, navigation_1.getItemTitle)(navigationItem);
@@ -139,7 +146,10 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
139
146
  // If the item is selected, we don't want to select more
140
147
  const isSelected = selectedItemId
141
148
  ? false
142
- : (0, navigation_1.isPageItemSelected)(navigationItem, basePath, pathname);
149
+ : (0, navigation_1.isPageItemSelected)(navigationItem, basePath, pathname) ||
150
+ navigationItem.children?.some((child) => (child.kind === "page" || child.kind == null) &&
151
+ child.hidden &&
152
+ (0, navigation_1.isPageItemSelected)(child, navigationItemFullPath, pathname));
143
153
  if (isSelected && !selectedItemId) {
144
154
  selectedItemId = navigationItemId;
145
155
  }
@@ -147,10 +157,10 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
147
157
  py: 0,
148
158
  px: 1,
149
159
  overflowX: "hidden"
150
- }, children: (0, jsx_runtime_1.jsxs)(NavigationListItemButton, { selected: isSelected && (!navigationItem.children || isMini), sx: {
160
+ }, children: (0, jsx_runtime_1.jsxs)(NavigationListItemButton, { selected: isSelected && (!children || isMini), sx: {
151
161
  px: 1.4,
152
162
  height: 48
153
- }, ...(navigationItem.children && !isMini
163
+ }, ...(children && !isMini
154
164
  ? {
155
165
  onClick: handleOpenFolderClick(navigationItemId)
156
166
  }
@@ -177,9 +187,9 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
177
187
  }
178
188
  } }), navigationItem.action && !isMini && isFullyExpanded
179
189
  ? navigationItem.action
180
- : null, navigationItem.children && !isMini && isFullyExpanded
190
+ : null, children && !isMini && isFullyExpanded
181
191
  ? nestedNavigationCollapseIcon
182
192
  : null] }) }));
183
- return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [isMini ? ((0, jsx_runtime_1.jsx)(Tooltip_1.default, { title: navigationItemTitle, placement: "right", children: listItem })) : (listItem), navigationItem.children && !isMini ? ((0, jsx_runtime_1.jsx)(Collapse_1.default, { in: isNestedNavigationExpanded, timeout: "auto", unmountOnExit: true, children: (0, jsx_runtime_1.jsx)(DashboardSidebarSubNavigation, { subNavigation: navigationItem.children, basePath: navigationItemFullPath, depth: depth + 1, onLinkClick: onLinkClick, selectedItemId: selectedItemId }) })) : null] }, navigationItemId));
193
+ return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [isMini ? ((0, jsx_runtime_1.jsx)(Tooltip_1.default, { title: navigationItemTitle, placement: "right", children: listItem })) : (listItem), children && !isMini ? ((0, jsx_runtime_1.jsx)(Collapse_1.default, { in: isNestedNavigationExpanded, timeout: "auto", unmountOnExit: true, children: (0, jsx_runtime_1.jsx)(DashboardSidebarSubNavigation, { subNavigation: children, basePath: navigationItemFullPath, depth: depth + 1, onLinkClick: onLinkClick, selectedItemId: selectedItemId }) })) : null] }, navigationItemId));
184
194
  }) }));
185
195
  }
@@ -25,8 +25,7 @@ export type PageData = {
25
25
  title?: string;
26
26
  page?: string;
27
27
  breadcrumbs?: Breadcrumb[];
28
- noBreadcrumbs?: boolean;
29
- noPageHeader?: boolean;
28
+ pageHeader?: React.ReactNode;
30
29
  };
31
30
  type PageDataAction = PageData;
32
31
  export declare const PageDataContext: React.Context<{
@@ -35,10 +34,6 @@ export declare const PageDataContext: React.Context<{
35
34
  }>;
36
35
  export declare function PageDataContextProvider(props: React.PropsWithChildren<PageData>): import("react/jsx-runtime").JSX.Element;
37
36
  type PageContainerBarProps = {
38
- /**
39
- * The default title of the page.
40
- */
41
- defaultTitle?: string;
42
37
  /**
43
38
  * The components used for each slot inside.
44
39
  */
@@ -64,7 +64,7 @@ function reducer(state, action) {
64
64
  let key;
65
65
  let isSame = true;
66
66
  for (key in action) {
67
- if (action[key] !== state[key]) {
67
+ if (action[key] != state[key]) {
68
68
  isSame = false;
69
69
  break;
70
70
  }
@@ -76,38 +76,30 @@ function reducer(state, action) {
76
76
  }
77
77
  function PageDataContextProvider(props) {
78
78
  // Destruct
79
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } = props;
79
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
80
80
  // useReducer hook to manage state with our reducer function and initial state
81
81
  const [state, dispatch] = React.useReducer(reducer, {
82
82
  title,
83
83
  page,
84
84
  breadcrumbs,
85
- noBreadcrumbs,
86
- noPageHeader
85
+ pageHeader
87
86
  });
88
87
  // Provide the state and dispatch function to the context value
89
88
  return (0, jsx_runtime_1.jsx)(exports.PageDataContext.Provider, { value: { state, dispatch }, ...rest });
90
89
  }
91
90
  function PageContainerBar(props) {
92
- const { defaultTitle, slots, slotProps } = props;
93
- const { state, dispatch } = React.useContext(exports.PageDataContext);
91
+ const { slots, slotProps } = props;
92
+ const { state } = React.useContext(exports.PageDataContext);
94
93
  const activePage = (0, useActivePage_1.useActivePage)();
95
- React.useLayoutEffect(() => {
96
- // Reset the state when the active page changes without rerendering
97
- state.breadcrumbs = undefined;
98
- state.noBreadcrumbs = undefined;
99
- state.noPageHeader = undefined;
100
- state.page = undefined;
101
- state.title = undefined;
94
+ React.useEffect(() => {
95
+ return () => {
96
+ // Reset the state when the component unmounts
97
+ state.breadcrumbs = undefined;
98
+ state.page = undefined;
99
+ state.pageHeader = undefined;
100
+ state.title = undefined;
101
+ };
102
102
  }, [activePage?.sourcePath]);
103
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
104
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
105
- if (state.page) {
106
- resolvedBreadcrumbs = [
107
- ...resolvedBreadcrumbs,
108
- { title: state.page, path: "#" }
109
- ];
110
- }
111
103
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar_1.PageContainerToolbar;
112
104
  const toolbarSlotProps = (0, useSlotProps_1.default)({
113
105
  elementType: ToolbarComponent,
@@ -115,11 +107,19 @@ function PageContainerBar(props) {
115
107
  externalSlotProps: slotProps?.toolbar,
116
108
  additionalProps: {}
117
109
  });
118
- return state.noPageHeader !== true ? ((0, jsx_runtime_1.jsxs)(Stack_1.default, { children: [state.noBreadcrumbs !== true && ((0, jsx_runtime_1.jsx)(Breadcrumbs_1.default, { "aria-label": "breadcrumb", children: resolvedBreadcrumbs
119
- ? resolvedBreadcrumbs.map((item, index) => {
120
- return index < resolvedBreadcrumbs.length - 1 ? ((0, jsx_runtime_1.jsx)(Link_1.default, { component: Link_2.Link, underline: "hover", color: "inherit", href: item.path, children: (0, navigation_1.getItemTitle)(item) }, item.path)) : ((0, jsx_runtime_1.jsx)(Typography_1.default, { color: "text.primary", children: (0, navigation_1.getItemTitle)(item) }, item.path));
121
- })
122
- : null })), (0, jsx_runtime_1.jsxs)(PageContentHeader, { children: [title ? (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "h4", children: title }) : null, (0, jsx_runtime_1.jsx)(ToolbarComponent, { ...toolbarSlotProps })] })] })) : undefined;
110
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
111
+ const title = state.title ?? activePage?.title ?? "";
112
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
113
+ if (pageHeader === false)
114
+ return undefined;
115
+ if (pageHeader != null)
116
+ return pageHeader;
117
+ if (state.page) {
118
+ breadcrumbs.push({ title: state.page, path: "#" });
119
+ }
120
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { children: [breadcrumbs && ((0, jsx_runtime_1.jsx)(Breadcrumbs_1.default, { "aria-label": "breadcrumb", children: breadcrumbs.map((item, index) => {
121
+ return index < breadcrumbs.length - 1 ? ((0, jsx_runtime_1.jsx)(Link_1.default, { component: Link_2.Link, underline: "hover", color: "inherit", href: item.path, children: (0, navigation_1.getItemTitle)(item) }, item.path)) : ((0, jsx_runtime_1.jsx)(Typography_1.default, { color: "text.primary", children: (0, navigation_1.getItemTitle)(item) }, item.path));
122
+ }) })), (0, jsx_runtime_1.jsxs)(PageContentHeader, { children: [title ? (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "h4", children: title }) : null, (0, jsx_runtime_1.jsx)(ToolbarComponent, { ...toolbarSlotProps })] })] }));
123
123
  }
124
124
  /**
125
125
  * A container component to provide a title and breadcrumbs for your pages.
@@ -133,6 +133,6 @@ function PageContainerBar(props) {
133
133
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
134
134
  */
135
135
  function PageContainer(props) {
136
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
137
- return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [(0, jsx_runtime_1.jsx)(PageContainerBar, { defaultTitle: defaultTitle, slots: slots, slotProps: slotProps }), children] }));
136
+ const { children, slots, slotProps, ...rest } = props;
137
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [(0, jsx_runtime_1.jsx)(PageContainerBar, { slots: slots, slotProps: slotProps }), children] }));
138
138
  }
@@ -26,9 +26,6 @@ function isPageItemSelected(navigationItem, basePath, pathname) {
26
26
  if (navigationItem.pattern) {
27
27
  return (0, path_to_regexp_1.pathToRegexp)(`${basePath}/${navigationItem.pattern}`).test(pathname);
28
28
  }
29
- if (navigationItem.subs) {
30
- return navigationItem.subs.some((sub) => new RegExp(sub).test(pathname));
31
- }
32
29
  return getPageItemFullPath(basePath, navigationItem) === pathname;
33
30
  }
34
31
  function hasSelectedNavigationChildren(navigationItem, basePath, pathname) {
@@ -127,15 +124,11 @@ function getItemLookup(navigation) {
127
124
  function matchPath(navigation, path) {
128
125
  const lookup = getItemLookup(navigation);
129
126
  for (const [key, item] of lookup.entries()) {
130
- if (typeof key === "string") {
131
- if (key === path)
132
- return item;
133
- else if (item.subs?.some((sub) => new RegExp(sub).test(path)))
134
- return item;
127
+ if (typeof key === "string" && key === path) {
128
+ return item;
135
129
  }
136
- else if (key instanceof RegExp) {
137
- if (key.test(path))
138
- return item;
130
+ if (key instanceof RegExp && key.test(path)) {
131
+ return item;
139
132
  }
140
133
  }
141
134
  return null;
@@ -1,8 +1,10 @@
1
+ import * as React from "react";
1
2
  import type { Breadcrumb } from "../PageContainer";
2
3
  export interface ActivePage {
3
4
  title: string;
4
5
  path: string;
5
6
  sourcePath: string;
6
7
  breadcrumbs: Breadcrumb[];
8
+ pageHeader?: React.ReactNode;
7
9
  }
8
10
  export declare function useActivePage(): ActivePage | null;
@@ -77,7 +77,8 @@ function useActivePage() {
77
77
  title: (0, navigation_1.getItemTitle)(activeItem),
78
78
  path: (0, navigation_1.getItemPath)(navigationContext, activeItem),
79
79
  sourcePath: pathname,
80
- breadcrumbs
80
+ breadcrumbs,
81
+ pageHeader: activeItem.pageHeader
81
82
  };
82
83
  }, [activeItem, rootItem, pathname, navigationContext]);
83
84
  }
@@ -27,7 +27,8 @@ export interface NavigationPageItem {
27
27
  pattern?: string;
28
28
  action?: React.ReactNode;
29
29
  children?: Navigation;
30
- subs?: string[];
30
+ hidden?: boolean;
31
+ pageHeader?: React.ReactNode;
31
32
  }
32
33
  export interface NavigationSubheaderItem {
33
34
  kind: "header";
@@ -126,6 +126,12 @@ describe("DashboardLayout", () => {
126
126
  segment: "traffic",
127
127
  title: "Traffic",
128
128
  icon: _jsx(DescriptionIcon, {})
129
+ },
130
+ {
131
+ segment: "hidden",
132
+ title: "Hidden",
133
+ icon: _jsx(DescriptionIcon, {}),
134
+ hidden: true
129
135
  }
130
136
  ]
131
137
  }
@@ -141,6 +147,7 @@ describe("DashboardLayout", () => {
141
147
  });
142
148
  expect(within(desktopNavigation).getByText("Sales")).toBeTruthy();
143
149
  expect(within(desktopNavigation).getByText("Traffic")).toBeTruthy();
150
+ expect(within(desktopNavigation).queryByText("Hidden")).toBeNull();
144
151
  });
145
152
  test("shows correct selected page item", () => {
146
153
  const NAVIGATION = [
@@ -152,7 +159,14 @@ describe("DashboardLayout", () => {
152
159
  {
153
160
  title: "Orders",
154
161
  segment: "orders",
155
- icon: _jsx(ShoppingCartIcon, {})
162
+ icon: _jsx(ShoppingCartIcon, {}),
163
+ children: [
164
+ {
165
+ segment: "nested",
166
+ title: "Nested",
167
+ hidden: true
168
+ }
169
+ ]
156
170
  },
157
171
  {
158
172
  segment: "dynamic",
@@ -192,6 +206,8 @@ describe("DashboardLayout", () => {
192
206
  rerender(_jsx(AppWithPathname, { pathname: "/orders" }));
193
207
  expect(within(desktopNavigation).getByRole("link", { name: "Dashboard" })).not.toHaveClass("Mui-selected");
194
208
  expect(within(desktopNavigation).getByRole("link", { name: "Orders" })).toHaveClass("Mui-selected");
209
+ rerender(_jsx(AppWithPathname, { pathname: "/orders/nested" }));
210
+ expect(within(desktopNavigation).getByRole("link", { name: "Orders" })).toHaveClass("Mui-selected");
195
211
  rerender(_jsx(AppWithPathname, { pathname: "/dynamic" }));
196
212
  expect(within(desktopNavigation).getByRole("link", { name: "Dynamic" })).not.toHaveClass("Mui-selected");
197
213
  rerender(_jsx(AppWithPathname, { pathname: "/dynamic/123" }));
@@ -14,10 +14,10 @@ import Tooltip from "@mui/material/Tooltip";
14
14
  import ExpandLessIcon from "@mui/icons-material/ExpandLess";
15
15
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
16
16
  import { Link } from "../shared/Link";
17
- import { RouterContext } from "../shared/context";
18
17
  import { getItemTitle, getPageItemFullPath, hasSelectedNavigationChildren, isPageItemSelected } from "../shared/navigation";
19
18
  import { getDrawerSxTransitionMixin } from "./utils";
20
19
  import { styled } from "@mui/material/styles";
20
+ import { useActivePage } from "../useActivePage/useActivePage";
21
21
  const NavigationListItemButton = styled(ListItemButton)(({ theme }) => ({
22
22
  borderRadius: 8,
23
23
  "&.Mui-selected": {
@@ -48,8 +48,8 @@ const NavigationListItemButton = styled(ListItemButton)(({ theme }) => ({
48
48
  * @ignore - internal component.
49
49
  */
50
50
  function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0, onLinkClick, isMini = false, isFullyExpanded = true, hasDrawerTransitions = false, selectedItemId }) {
51
- const routerContext = React.useContext(RouterContext);
52
- const pathname = routerContext?.pathname ?? "/";
51
+ const activePage = useActivePage();
52
+ const pathname = activePage?.sourcePath ?? "/";
53
53
  const initialExpandedSidebarItemIds = React.useMemo(() => subNavigation
54
54
  .map((navigationItem, navigationItemIndex) => ({
55
55
  navigationItem,
@@ -91,6 +91,13 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
91
91
  : {})
92
92
  } }, `divider-${depth}-${navigationItemIndex}`));
93
93
  }
94
+ if (navigationItem.hidden) {
95
+ return null;
96
+ }
97
+ let children = navigationItem.children?.filter((child) => child.kind === "divider" || child.kind === "header" || !child.hidden);
98
+ if (children && children.length === 0) {
99
+ children = undefined;
100
+ }
94
101
  const navigationItemFullPath = getPageItemFullPath(basePath, navigationItem);
95
102
  const navigationItemId = `${depth}-${navigationItemIndex}`;
96
103
  const navigationItemTitle = getItemTitle(navigationItem);
@@ -100,7 +107,10 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
100
107
  // If the item is selected, we don't want to select more
101
108
  const isSelected = selectedItemId
102
109
  ? false
103
- : isPageItemSelected(navigationItem, basePath, pathname);
110
+ : isPageItemSelected(navigationItem, basePath, pathname) ||
111
+ navigationItem.children?.some((child) => (child.kind === "page" || child.kind == null) &&
112
+ child.hidden &&
113
+ isPageItemSelected(child, navigationItemFullPath, pathname));
104
114
  if (isSelected && !selectedItemId) {
105
115
  selectedItemId = navigationItemId;
106
116
  }
@@ -108,10 +118,10 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
108
118
  py: 0,
109
119
  px: 1,
110
120
  overflowX: "hidden"
111
- }, children: _jsxs(NavigationListItemButton, { selected: isSelected && (!navigationItem.children || isMini), sx: {
121
+ }, children: _jsxs(NavigationListItemButton, { selected: isSelected && (!children || isMini), sx: {
112
122
  px: 1.4,
113
123
  height: 48
114
- }, ...(navigationItem.children && !isMini
124
+ }, ...(children && !isMini
115
125
  ? {
116
126
  onClick: handleOpenFolderClick(navigationItemId)
117
127
  }
@@ -138,10 +148,10 @@ function DashboardSidebarSubNavigation({ subNavigation, basePath = "", depth = 0
138
148
  }
139
149
  } }), navigationItem.action && !isMini && isFullyExpanded
140
150
  ? navigationItem.action
141
- : null, navigationItem.children && !isMini && isFullyExpanded
151
+ : null, children && !isMini && isFullyExpanded
142
152
  ? nestedNavigationCollapseIcon
143
153
  : null] }) }));
144
- return (_jsxs(React.Fragment, { children: [isMini ? (_jsx(Tooltip, { title: navigationItemTitle, placement: "right", children: listItem })) : (listItem), navigationItem.children && !isMini ? (_jsx(Collapse, { in: isNestedNavigationExpanded, timeout: "auto", unmountOnExit: true, children: _jsx(DashboardSidebarSubNavigation, { subNavigation: navigationItem.children, basePath: navigationItemFullPath, depth: depth + 1, onLinkClick: onLinkClick, selectedItemId: selectedItemId }) })) : null] }, navigationItemId));
154
+ return (_jsxs(React.Fragment, { children: [isMini ? (_jsx(Tooltip, { title: navigationItemTitle, placement: "right", children: listItem })) : (listItem), children && !isMini ? (_jsx(Collapse, { in: isNestedNavigationExpanded, timeout: "auto", unmountOnExit: true, children: _jsx(DashboardSidebarSubNavigation, { subNavigation: children, basePath: navigationItemFullPath, depth: depth + 1, onLinkClick: onLinkClick, selectedItemId: selectedItemId }) })) : null] }, navigationItemId));
145
155
  }) }));
146
156
  }
147
157
  export { DashboardSidebarSubNavigation };
@@ -25,8 +25,7 @@ export type PageData = {
25
25
  title?: string;
26
26
  page?: string;
27
27
  breadcrumbs?: Breadcrumb[];
28
- noBreadcrumbs?: boolean;
29
- noPageHeader?: boolean;
28
+ pageHeader?: React.ReactNode;
30
29
  };
31
30
  type PageDataAction = PageData;
32
31
  export declare const PageDataContext: React.Context<{
@@ -35,10 +34,6 @@ export declare const PageDataContext: React.Context<{
35
34
  }>;
36
35
  export declare function PageDataContextProvider(props: React.PropsWithChildren<PageData>): import("react/jsx-runtime").JSX.Element;
37
36
  type PageContainerBarProps = {
38
- /**
39
- * The default title of the page.
40
- */
41
- defaultTitle?: string;
42
37
  /**
43
38
  * The components used for each slot inside.
44
39
  */
@@ -23,7 +23,7 @@ function reducer(state, action) {
23
23
  let key;
24
24
  let isSame = true;
25
25
  for (key in action) {
26
- if (action[key] !== state[key]) {
26
+ if (action[key] != state[key]) {
27
27
  isSame = false;
28
28
  break;
29
29
  }
@@ -35,38 +35,30 @@ function reducer(state, action) {
35
35
  }
36
36
  export function PageDataContextProvider(props) {
37
37
  // Destruct
38
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } = props;
38
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
39
39
  // useReducer hook to manage state with our reducer function and initial state
40
40
  const [state, dispatch] = React.useReducer(reducer, {
41
41
  title,
42
42
  page,
43
43
  breadcrumbs,
44
- noBreadcrumbs,
45
- noPageHeader
44
+ pageHeader
46
45
  });
47
46
  // Provide the state and dispatch function to the context value
48
47
  return _jsx(PageDataContext.Provider, { value: { state, dispatch }, ...rest });
49
48
  }
50
49
  function PageContainerBar(props) {
51
- const { defaultTitle, slots, slotProps } = props;
52
- const { state, dispatch } = React.useContext(PageDataContext);
50
+ const { slots, slotProps } = props;
51
+ const { state } = React.useContext(PageDataContext);
53
52
  const activePage = useActivePage();
54
- React.useLayoutEffect(() => {
55
- // Reset the state when the active page changes without rerendering
56
- state.breadcrumbs = undefined;
57
- state.noBreadcrumbs = undefined;
58
- state.noPageHeader = undefined;
59
- state.page = undefined;
60
- state.title = undefined;
53
+ React.useEffect(() => {
54
+ return () => {
55
+ // Reset the state when the component unmounts
56
+ state.breadcrumbs = undefined;
57
+ state.page = undefined;
58
+ state.pageHeader = undefined;
59
+ state.title = undefined;
60
+ };
61
61
  }, [activePage?.sourcePath]);
62
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
63
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
64
- if (state.page) {
65
- resolvedBreadcrumbs = [
66
- ...resolvedBreadcrumbs,
67
- { title: state.page, path: "#" }
68
- ];
69
- }
70
62
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar;
71
63
  const toolbarSlotProps = useSlotProps({
72
64
  elementType: ToolbarComponent,
@@ -74,11 +66,19 @@ function PageContainerBar(props) {
74
66
  externalSlotProps: slotProps?.toolbar,
75
67
  additionalProps: {}
76
68
  });
77
- return state.noPageHeader !== true ? (_jsxs(Stack, { children: [state.noBreadcrumbs !== true && (_jsx(Breadcrumbs, { "aria-label": "breadcrumb", children: resolvedBreadcrumbs
78
- ? resolvedBreadcrumbs.map((item, index) => {
79
- return index < resolvedBreadcrumbs.length - 1 ? (_jsx(Link, { component: ToolpadLink, underline: "hover", color: "inherit", href: item.path, children: getItemTitle(item) }, item.path)) : (_jsx(Typography, { color: "text.primary", children: getItemTitle(item) }, item.path));
80
- })
81
- : null })), _jsxs(PageContentHeader, { children: [title ? _jsx(Typography, { variant: "h4", children: title }) : null, _jsx(ToolbarComponent, { ...toolbarSlotProps })] })] })) : undefined;
69
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
70
+ const title = state.title ?? activePage?.title ?? "";
71
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
72
+ if (pageHeader === false)
73
+ return undefined;
74
+ if (pageHeader != null)
75
+ return pageHeader;
76
+ if (state.page) {
77
+ breadcrumbs.push({ title: state.page, path: "#" });
78
+ }
79
+ return (_jsxs(Stack, { children: [breadcrumbs && (_jsx(Breadcrumbs, { "aria-label": "breadcrumb", children: breadcrumbs.map((item, index) => {
80
+ return index < breadcrumbs.length - 1 ? (_jsx(Link, { component: ToolpadLink, underline: "hover", color: "inherit", href: item.path, children: getItemTitle(item) }, item.path)) : (_jsx(Typography, { color: "text.primary", children: getItemTitle(item) }, item.path));
81
+ }) })), _jsxs(PageContentHeader, { children: [title ? _jsx(Typography, { variant: "h4", children: title }) : null, _jsx(ToolbarComponent, { ...toolbarSlotProps })] })] }));
82
82
  }
83
83
  /**
84
84
  * A container component to provide a title and breadcrumbs for your pages.
@@ -92,7 +92,7 @@ function PageContainerBar(props) {
92
92
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
93
93
  */
94
94
  function PageContainer(props) {
95
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
96
- return (_jsxs(Stack, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [_jsx(PageContainerBar, { defaultTitle: defaultTitle, slots: slots, slotProps: slotProps }), children] }));
95
+ const { children, slots, slotProps, ...rest } = props;
96
+ return (_jsxs(Stack, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [_jsx(PageContainerBar, { slots: slots, slotProps: slotProps }), children] }));
97
97
  }
98
98
  export { PageContainer };
@@ -12,9 +12,6 @@ export function isPageItemSelected(navigationItem, basePath, pathname) {
12
12
  if (navigationItem.pattern) {
13
13
  return pathToRegexp(`${basePath}/${navigationItem.pattern}`).test(pathname);
14
14
  }
15
- if (navigationItem.subs) {
16
- return navigationItem.subs.some((sub) => new RegExp(sub).test(pathname));
17
- }
18
15
  return getPageItemFullPath(basePath, navigationItem) === pathname;
19
16
  }
20
17
  export function hasSelectedNavigationChildren(navigationItem, basePath, pathname) {
@@ -113,15 +110,11 @@ function getItemLookup(navigation) {
113
110
  export function matchPath(navigation, path) {
114
111
  const lookup = getItemLookup(navigation);
115
112
  for (const [key, item] of lookup.entries()) {
116
- if (typeof key === "string") {
117
- if (key === path)
118
- return item;
119
- else if (item.subs?.some((sub) => new RegExp(sub).test(path)))
120
- return item;
113
+ if (typeof key === "string" && key === path) {
114
+ return item;
121
115
  }
122
- else if (key instanceof RegExp) {
123
- if (key.test(path))
124
- return item;
116
+ if (key instanceof RegExp && key.test(path)) {
117
+ return item;
125
118
  }
126
119
  }
127
120
  return null;
@@ -1,8 +1,10 @@
1
+ import * as React from "react";
1
2
  import type { Breadcrumb } from "../PageContainer";
2
3
  export interface ActivePage {
3
4
  title: string;
4
5
  path: string;
5
6
  sourcePath: string;
6
7
  breadcrumbs: Breadcrumb[];
8
+ pageHeader?: React.ReactNode;
7
9
  }
8
10
  export declare function useActivePage(): ActivePage | null;
@@ -41,7 +41,8 @@ export function useActivePage() {
41
41
  title: getItemTitle(activeItem),
42
42
  path: getItemPath(navigationContext, activeItem),
43
43
  sourcePath: pathname,
44
- breadcrumbs
44
+ breadcrumbs,
45
+ pageHeader: activeItem.pageHeader
45
46
  };
46
47
  }, [activeItem, rootItem, pathname, navigationContext]);
47
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/toolpad",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "author": "ETSOO",
5
5
  "description": "Dashboard framework extention based on Toolpad Core",
6
6
  "main": "build/cjs/index.js",
@@ -35,7 +35,8 @@ export interface NavigationPageItem {
35
35
  pattern?: string;
36
36
  action?: React.ReactNode;
37
37
  children?: Navigation;
38
- subs?: string[];
38
+ hidden?: boolean;
39
+ pageHeader?: React.ReactNode;
39
40
  }
40
41
 
41
42
  export interface NavigationSubheaderItem {
@@ -175,6 +175,12 @@ describe("DashboardLayout", () => {
175
175
  segment: "traffic",
176
176
  title: "Traffic",
177
177
  icon: <DescriptionIcon />
178
+ },
179
+ {
180
+ segment: "hidden",
181
+ title: "Hidden",
182
+ icon: <DescriptionIcon />,
183
+ hidden: true
178
184
  }
179
185
  ]
180
186
  }
@@ -198,6 +204,7 @@ describe("DashboardLayout", () => {
198
204
 
199
205
  expect(within(desktopNavigation).getByText("Sales")).toBeTruthy();
200
206
  expect(within(desktopNavigation).getByText("Traffic")).toBeTruthy();
207
+ expect(within(desktopNavigation).queryByText("Hidden")).toBeNull();
201
208
  });
202
209
 
203
210
  test("shows correct selected page item", () => {
@@ -210,7 +217,14 @@ describe("DashboardLayout", () => {
210
217
  {
211
218
  title: "Orders",
212
219
  segment: "orders",
213
- icon: <ShoppingCartIcon />
220
+ icon: <ShoppingCartIcon />,
221
+ children: [
222
+ {
223
+ segment: "nested",
224
+ title: "Nested",
225
+ hidden: true
226
+ }
227
+ ]
214
228
  },
215
229
  {
216
230
  segment: "dynamic",
@@ -268,6 +282,11 @@ describe("DashboardLayout", () => {
268
282
  within(desktopNavigation).getByRole("link", { name: "Orders" })
269
283
  ).toHaveClass("Mui-selected");
270
284
 
285
+ rerender(<AppWithPathname pathname="/orders/nested" />);
286
+ expect(
287
+ within(desktopNavigation).getByRole("link", { name: "Orders" })
288
+ ).toHaveClass("Mui-selected");
289
+
271
290
  rerender(<AppWithPathname pathname="/dynamic" />);
272
291
  expect(
273
292
  within(desktopNavigation).getByRole("link", { name: "Dynamic" })
@@ -14,7 +14,6 @@ import type {} from "@mui/material/themeCssVarsAugmentation";
14
14
  import ExpandLessIcon from "@mui/icons-material/ExpandLess";
15
15
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
16
16
  import { Link } from "../shared/Link";
17
- import { RouterContext } from "../shared/context";
18
17
  import type { Navigation } from "../AppProvider";
19
18
  import {
20
19
  getItemTitle,
@@ -24,6 +23,7 @@ import {
24
23
  } from "../shared/navigation";
25
24
  import { getDrawerSxTransitionMixin } from "./utils";
26
25
  import { styled } from "@mui/material/styles";
26
+ import { useActivePage } from "../useActivePage/useActivePage";
27
27
 
28
28
  const NavigationListItemButton = styled(ListItemButton)(({ theme }) => ({
29
29
  borderRadius: 8,
@@ -76,9 +76,9 @@ function DashboardSidebarSubNavigation({
76
76
  hasDrawerTransitions = false,
77
77
  selectedItemId
78
78
  }: DashboardSidebarSubNavigationProps) {
79
- const routerContext = React.useContext(RouterContext);
79
+ const activePage = useActivePage();
80
80
 
81
- const pathname = routerContext?.pathname ?? "/";
81
+ const pathname = activePage?.sourcePath ?? "/";
82
82
 
83
83
  const initialExpandedSidebarItemIds = React.useMemo(
84
84
  () =>
@@ -157,6 +157,18 @@ function DashboardSidebarSubNavigation({
157
157
  );
158
158
  }
159
159
 
160
+ if (navigationItem.hidden) {
161
+ return null;
162
+ }
163
+
164
+ let children = navigationItem.children?.filter(
165
+ (child) =>
166
+ child.kind === "divider" || child.kind === "header" || !child.hidden
167
+ );
168
+ if (children && children.length === 0) {
169
+ children = undefined;
170
+ }
171
+
160
172
  const navigationItemFullPath = getPageItemFullPath(
161
173
  basePath,
162
174
  navigationItem
@@ -178,7 +190,13 @@ function DashboardSidebarSubNavigation({
178
190
  // If the item is selected, we don't want to select more
179
191
  const isSelected = selectedItemId
180
192
  ? false
181
- : isPageItemSelected(navigationItem, basePath, pathname);
193
+ : isPageItemSelected(navigationItem, basePath, pathname) ||
194
+ navigationItem.children?.some(
195
+ (child) =>
196
+ (child.kind === "page" || child.kind == null) &&
197
+ child.hidden &&
198
+ isPageItemSelected(child, navigationItemFullPath, pathname)
199
+ );
182
200
 
183
201
  if (isSelected && !selectedItemId) {
184
202
  selectedItemId = navigationItemId;
@@ -193,12 +211,12 @@ function DashboardSidebarSubNavigation({
193
211
  }}
194
212
  >
195
213
  <NavigationListItemButton
196
- selected={isSelected && (!navigationItem.children || isMini)}
214
+ selected={isSelected && (!children || isMini)}
197
215
  sx={{
198
216
  px: 1.4,
199
217
  height: 48
200
218
  }}
201
- {...(navigationItem.children && !isMini
219
+ {...(children && !isMini
202
220
  ? {
203
221
  onClick: handleOpenFolderClick(navigationItemId)
204
222
  }
@@ -248,7 +266,7 @@ function DashboardSidebarSubNavigation({
248
266
  {navigationItem.action && !isMini && isFullyExpanded
249
267
  ? navigationItem.action
250
268
  : null}
251
- {navigationItem.children && !isMini && isFullyExpanded
269
+ {children && !isMini && isFullyExpanded
252
270
  ? nestedNavigationCollapseIcon
253
271
  : null}
254
272
  </NavigationListItemButton>
@@ -265,14 +283,14 @@ function DashboardSidebarSubNavigation({
265
283
  listItem
266
284
  )}
267
285
 
268
- {navigationItem.children && !isMini ? (
286
+ {children && !isMini ? (
269
287
  <Collapse
270
288
  in={isNestedNavigationExpanded}
271
289
  timeout="auto"
272
290
  unmountOnExit
273
291
  >
274
292
  <DashboardSidebarSubNavigation
275
- subNavigation={navigationItem.children}
293
+ subNavigation={children}
276
294
  basePath={navigationItemFullPath}
277
295
  depth={depth + 1}
278
296
  onLinkClick={onLinkClick}
@@ -48,8 +48,7 @@ export type PageData = {
48
48
  title?: string;
49
49
  page?: string;
50
50
  breadcrumbs?: Breadcrumb[];
51
- noBreadcrumbs?: boolean;
52
- noPageHeader?: boolean;
51
+ pageHeader?: React.ReactNode;
53
52
  };
54
53
 
55
54
  type PageDataAction = PageData;
@@ -64,7 +63,7 @@ function reducer(state: PageData, action: PageDataAction) {
64
63
  let key: keyof PageDataAction;
65
64
  let isSame = true;
66
65
  for (key in action) {
67
- if (action[key] !== state[key]) {
66
+ if (action[key] != state[key]) {
68
67
  isSame = false;
69
68
  break;
70
69
  }
@@ -81,16 +80,14 @@ export function PageDataContextProvider(
81
80
  props: React.PropsWithChildren<PageData>
82
81
  ) {
83
82
  // Destruct
84
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } =
85
- props;
83
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
86
84
 
87
85
  // useReducer hook to manage state with our reducer function and initial state
88
86
  const [state, dispatch] = React.useReducer(reducer, {
89
87
  title,
90
88
  page,
91
89
  breadcrumbs,
92
- noBreadcrumbs,
93
- noPageHeader
90
+ pageHeader
94
91
  });
95
92
 
96
93
  // Provide the state and dispatch function to the context value
@@ -98,10 +95,6 @@ export function PageDataContextProvider(
98
95
  }
99
96
 
100
97
  type PageContainerBarProps = {
101
- /**
102
- * The default title of the page.
103
- */
104
- defaultTitle?: string;
105
98
  /**
106
99
  * The components used for each slot inside.
107
100
  */
@@ -113,31 +106,22 @@ type PageContainerBarProps = {
113
106
  };
114
107
 
115
108
  function PageContainerBar(props: PageContainerBarProps) {
116
- const { defaultTitle, slots, slotProps } = props;
109
+ const { slots, slotProps } = props;
117
110
 
118
- const { state, dispatch } = React.useContext(PageDataContext);
111
+ const { state } = React.useContext(PageDataContext);
119
112
 
120
113
  const activePage = useActivePage();
121
114
 
122
- React.useLayoutEffect(() => {
123
- // Reset the state when the active page changes without rerendering
124
- state.breadcrumbs = undefined;
125
- state.noBreadcrumbs = undefined;
126
- state.noPageHeader = undefined;
127
- state.page = undefined;
128
- state.title = undefined;
115
+ React.useEffect(() => {
116
+ return () => {
117
+ // Reset the state when the component unmounts
118
+ state.breadcrumbs = undefined;
119
+ state.page = undefined;
120
+ state.pageHeader = undefined;
121
+ state.title = undefined;
122
+ };
129
123
  }, [activePage?.sourcePath]);
130
124
 
131
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
132
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
133
-
134
- if (state.page) {
135
- resolvedBreadcrumbs = [
136
- ...resolvedBreadcrumbs,
137
- { title: state.page, path: "#" }
138
- ];
139
- }
140
-
141
125
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar;
142
126
  const toolbarSlotProps = useSlotProps({
143
127
  elementType: ToolbarComponent,
@@ -146,29 +130,38 @@ function PageContainerBar(props: PageContainerBarProps) {
146
130
  additionalProps: {}
147
131
  });
148
132
 
149
- return state.noPageHeader !== true ? (
133
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
134
+ const title = state.title ?? activePage?.title ?? "";
135
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
136
+
137
+ if (pageHeader === false) return undefined;
138
+ if (pageHeader != null) return pageHeader;
139
+
140
+ if (state.page) {
141
+ breadcrumbs.push({ title: state.page, path: "#" });
142
+ }
143
+
144
+ return (
150
145
  <Stack>
151
- {state.noBreadcrumbs !== true && (
146
+ {breadcrumbs && (
152
147
  <Breadcrumbs aria-label="breadcrumb">
153
- {resolvedBreadcrumbs
154
- ? resolvedBreadcrumbs.map((item, index) => {
155
- return index < resolvedBreadcrumbs.length - 1 ? (
156
- <Link
157
- key={item.path}
158
- component={ToolpadLink}
159
- underline="hover"
160
- color="inherit"
161
- href={item.path}
162
- >
163
- {getItemTitle(item)}
164
- </Link>
165
- ) : (
166
- <Typography key={item.path} color="text.primary">
167
- {getItemTitle(item)}
168
- </Typography>
169
- );
170
- })
171
- : null}
148
+ {breadcrumbs.map((item, index) => {
149
+ return index < breadcrumbs.length - 1 ? (
150
+ <Link
151
+ key={item.path}
152
+ component={ToolpadLink}
153
+ underline="hover"
154
+ color="inherit"
155
+ href={item.path}
156
+ >
157
+ {getItemTitle(item)}
158
+ </Link>
159
+ ) : (
160
+ <Typography key={item.path} color="text.primary">
161
+ {getItemTitle(item)}
162
+ </Typography>
163
+ );
164
+ })}
172
165
  </Breadcrumbs>
173
166
  )}
174
167
  <PageContentHeader>
@@ -176,7 +169,7 @@ function PageContainerBar(props: PageContainerBarProps) {
176
169
  <ToolbarComponent {...toolbarSlotProps} />
177
170
  </PageContentHeader>
178
171
  </Stack>
179
- ) : undefined;
172
+ );
180
173
  }
181
174
 
182
175
  export type PageContainerProps = React.PropsWithChildren<
@@ -195,15 +188,11 @@ export type PageContainerProps = React.PropsWithChildren<
195
188
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
196
189
  */
197
190
  function PageContainer(props: PageContainerProps) {
198
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
191
+ const { children, slots, slotProps, ...rest } = props;
199
192
 
200
193
  return (
201
194
  <Stack sx={{ mx: 3, my: 2 }} spacing={2} {...rest}>
202
- <PageContainerBar
203
- defaultTitle={defaultTitle}
204
- slots={slots}
205
- slotProps={slotProps}
206
- />
195
+ <PageContainerBar slots={slots} slotProps={slotProps} />
207
196
  {children}
208
197
  </Stack>
209
198
  );
@@ -36,10 +36,6 @@ export function isPageItemSelected(
36
36
  return pathToRegexp(`${basePath}/${navigationItem.pattern}`).test(pathname);
37
37
  }
38
38
 
39
- if (navigationItem.subs) {
40
- return navigationItem.subs.some((sub) => new RegExp(sub).test(pathname));
41
- }
42
-
43
39
  return getPageItemFullPath(basePath, navigationItem) === pathname;
44
40
  }
45
41
 
@@ -178,12 +174,11 @@ export function matchPath(
178
174
  const lookup = getItemLookup(navigation);
179
175
 
180
176
  for (const [key, item] of lookup.entries()) {
181
- if (typeof key === "string") {
182
- if (key === path) return item;
183
- else if (item.subs?.some((sub) => new RegExp(sub).test(path)))
184
- return item;
185
- } else if (key instanceof RegExp) {
186
- if (key.test(path)) return item;
177
+ if (typeof key === "string" && key === path) {
178
+ return item;
179
+ }
180
+ if (key instanceof RegExp && key.test(path)) {
181
+ return item;
187
182
  }
188
183
  }
189
184
 
@@ -9,11 +9,13 @@ export interface ActivePage {
9
9
  path: string;
10
10
  sourcePath: string;
11
11
  breadcrumbs: Breadcrumb[];
12
+ pageHeader?: React.ReactNode;
12
13
  }
13
14
 
14
15
  export function useActivePage(): ActivePage | null {
15
16
  const navigationContext = React.useContext(NavigationContext);
16
17
  const routerContext = React.useContext(RouterContext);
18
+
17
19
  const pathname = routerContext?.pathname ?? "/";
18
20
  const activeItem = matchPath(navigationContext, pathname);
19
21
 
@@ -56,7 +58,8 @@ export function useActivePage(): ActivePage | null {
56
58
  title: getItemTitle(activeItem),
57
59
  path: getItemPath(navigationContext, activeItem),
58
60
  sourcePath: pathname,
59
- breadcrumbs
61
+ breadcrumbs,
62
+ pageHeader: activeItem.pageHeader
60
63
  };
61
64
  }, [activeItem, rootItem, pathname, navigationContext]);
62
65
  }