@etsoo/toolpad 1.0.28 → 1.0.29
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.
- package/build/cjs/AppProvider/AppProvider.d.ts +1 -1
- package/build/cjs/DashboardLayout/DashboardLayout.test.js +17 -1
- package/build/cjs/DashboardLayout/DashboardSidebarSubNavigation.js +18 -8
- package/build/cjs/PageContainer/PageContainer.js +2 -2
- package/build/cjs/shared/navigation.js +4 -11
- package/build/cjs/useActivePage/useActivePage.js +21 -12
- package/build/mjs/AppProvider/AppProvider.d.ts +1 -1
- package/build/mjs/DashboardLayout/DashboardLayout.test.js +17 -1
- package/build/mjs/DashboardLayout/DashboardSidebarSubNavigation.js +18 -8
- package/build/mjs/PageContainer/PageContainer.js +2 -2
- package/build/mjs/shared/navigation.js +4 -11
- package/build/mjs/useActivePage/useActivePage.js +21 -12
- package/package.json +1 -1
- package/src/AppProvider/AppProvider.tsx +1 -1
- package/src/DashboardLayout/DashboardLayout.test.tsx +20 -1
- package/src/DashboardLayout/DashboardSidebarSubNavigation.tsx +27 -9
- package/src/PageContainer/PageContainer.tsx +2 -2
- package/src/shared/navigation.tsx +5 -10
- package/src/useActivePage/useActivePage.ts +26 -13
|
@@ -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
|
|
91
|
-
const 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 && (!
|
|
160
|
+
}, children: (0, jsx_runtime_1.jsxs)(NavigationListItemButton, { selected: isSelected && (!children || isMini), sx: {
|
|
151
161
|
px: 1.4,
|
|
152
162
|
height: 48
|
|
153
|
-
}, ...(
|
|
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,
|
|
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),
|
|
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
|
}
|
|
@@ -90,10 +90,10 @@ function PageDataContextProvider(props) {
|
|
|
90
90
|
}
|
|
91
91
|
function PageContainerBar(props) {
|
|
92
92
|
const { defaultTitle, slots, slotProps } = props;
|
|
93
|
-
const { state
|
|
93
|
+
const { state } = React.useContext(exports.PageDataContext);
|
|
94
94
|
const activePage = (0, useActivePage_1.useActivePage)();
|
|
95
95
|
React.useLayoutEffect(() => {
|
|
96
|
-
// Reset the state
|
|
96
|
+
// Reset the state without rerendering
|
|
97
97
|
state.breadcrumbs = undefined;
|
|
98
98
|
state.noBreadcrumbs = undefined;
|
|
99
99
|
state.noPageHeader = undefined;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
return item;
|
|
130
|
+
if (key instanceof RegExp && key.test(path)) {
|
|
131
|
+
return item;
|
|
139
132
|
}
|
|
140
133
|
}
|
|
141
134
|
return null;
|
|
@@ -41,13 +41,14 @@ const navigation_1 = require("../shared/navigation");
|
|
|
41
41
|
function useActivePage() {
|
|
42
42
|
const navigationContext = React.useContext(context_1.NavigationContext);
|
|
43
43
|
const routerContext = React.useContext(context_1.RouterContext);
|
|
44
|
-
const
|
|
44
|
+
const pageRef = React.useRef(null);
|
|
45
|
+
let pathname = routerContext?.pathname ?? "/";
|
|
45
46
|
const activeItem = (0, navigation_1.matchPath)(navigationContext, pathname);
|
|
46
47
|
const rootItem = (0, navigation_1.matchPath)(navigationContext, "/");
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (!activeItem) {
|
|
49
|
+
pageRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
51
52
|
const breadcrumbs = [];
|
|
52
53
|
if (rootItem) {
|
|
53
54
|
breadcrumbs.push({
|
|
@@ -73,11 +74,19 @@ function useActivePage() {
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
const title = (0, navigation_1.getItemTitle)(activeItem);
|
|
78
|
+
const path = (0, navigation_1.getItemPath)(navigationContext, activeItem);
|
|
79
|
+
if (pageRef.current == null ||
|
|
80
|
+
pageRef.current.title !== title ||
|
|
81
|
+
pageRef.current.path !== path ||
|
|
82
|
+
pageRef.current.sourcePath !== pathname) {
|
|
83
|
+
pageRef.current = {
|
|
84
|
+
title,
|
|
85
|
+
path,
|
|
86
|
+
sourcePath: pathname,
|
|
87
|
+
breadcrumbs
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return pageRef.current;
|
|
83
92
|
}
|
|
@@ -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
|
|
52
|
-
const 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 && (!
|
|
121
|
+
}, children: _jsxs(NavigationListItemButton, { selected: isSelected && (!children || isMini), sx: {
|
|
112
122
|
px: 1.4,
|
|
113
123
|
height: 48
|
|
114
|
-
}, ...(
|
|
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,
|
|
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),
|
|
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 };
|
|
@@ -49,10 +49,10 @@ export function PageDataContextProvider(props) {
|
|
|
49
49
|
}
|
|
50
50
|
function PageContainerBar(props) {
|
|
51
51
|
const { defaultTitle, slots, slotProps } = props;
|
|
52
|
-
const { state
|
|
52
|
+
const { state } = React.useContext(PageDataContext);
|
|
53
53
|
const activePage = useActivePage();
|
|
54
54
|
React.useLayoutEffect(() => {
|
|
55
|
-
// Reset the state
|
|
55
|
+
// Reset the state without rerendering
|
|
56
56
|
state.breadcrumbs = undefined;
|
|
57
57
|
state.noBreadcrumbs = undefined;
|
|
58
58
|
state.noPageHeader = undefined;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
return item;
|
|
116
|
+
if (key instanceof RegExp && key.test(path)) {
|
|
117
|
+
return item;
|
|
125
118
|
}
|
|
126
119
|
}
|
|
127
120
|
return null;
|
|
@@ -5,13 +5,14 @@ import { getItemPath, getItemTitle, matchPath } from "../shared/navigation";
|
|
|
5
5
|
export function useActivePage() {
|
|
6
6
|
const navigationContext = React.useContext(NavigationContext);
|
|
7
7
|
const routerContext = React.useContext(RouterContext);
|
|
8
|
-
const
|
|
8
|
+
const pageRef = React.useRef(null);
|
|
9
|
+
let pathname = routerContext?.pathname ?? "/";
|
|
9
10
|
const activeItem = matchPath(navigationContext, pathname);
|
|
10
11
|
const rootItem = matchPath(navigationContext, "/");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!activeItem) {
|
|
13
|
+
pageRef.current = null;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
15
16
|
const breadcrumbs = [];
|
|
16
17
|
if (rootItem) {
|
|
17
18
|
breadcrumbs.push({
|
|
@@ -37,11 +38,19 @@ export function useActivePage() {
|
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const title = getItemTitle(activeItem);
|
|
42
|
+
const path = getItemPath(navigationContext, activeItem);
|
|
43
|
+
if (pageRef.current == null ||
|
|
44
|
+
pageRef.current.title !== title ||
|
|
45
|
+
pageRef.current.path !== path ||
|
|
46
|
+
pageRef.current.sourcePath !== pathname) {
|
|
47
|
+
pageRef.current = {
|
|
48
|
+
title,
|
|
49
|
+
path,
|
|
50
|
+
sourcePath: pathname,
|
|
51
|
+
breadcrumbs
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return pageRef.current;
|
|
47
56
|
}
|
package/package.json
CHANGED
|
@@ -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
|
|
79
|
+
const activePage = useActivePage();
|
|
80
80
|
|
|
81
|
-
const 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 && (!
|
|
214
|
+
selected={isSelected && (!children || isMini)}
|
|
197
215
|
sx={{
|
|
198
216
|
px: 1.4,
|
|
199
217
|
height: 48
|
|
200
218
|
}}
|
|
201
|
-
{...(
|
|
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
|
-
{
|
|
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
|
-
{
|
|
286
|
+
{children && !isMini ? (
|
|
269
287
|
<Collapse
|
|
270
288
|
in={isNestedNavigationExpanded}
|
|
271
289
|
timeout="auto"
|
|
272
290
|
unmountOnExit
|
|
273
291
|
>
|
|
274
292
|
<DashboardSidebarSubNavigation
|
|
275
|
-
subNavigation={
|
|
293
|
+
subNavigation={children}
|
|
276
294
|
basePath={navigationItemFullPath}
|
|
277
295
|
depth={depth + 1}
|
|
278
296
|
onLinkClick={onLinkClick}
|
|
@@ -115,12 +115,12 @@ type PageContainerBarProps = {
|
|
|
115
115
|
function PageContainerBar(props: PageContainerBarProps) {
|
|
116
116
|
const { defaultTitle, slots, slotProps } = props;
|
|
117
117
|
|
|
118
|
-
const { state
|
|
118
|
+
const { state } = React.useContext(PageDataContext);
|
|
119
119
|
|
|
120
120
|
const activePage = useActivePage();
|
|
121
121
|
|
|
122
122
|
React.useLayoutEffect(() => {
|
|
123
|
-
// Reset the state
|
|
123
|
+
// Reset the state without rerendering
|
|
124
124
|
state.breadcrumbs = undefined;
|
|
125
125
|
state.noBreadcrumbs = undefined;
|
|
126
126
|
state.noPageHeader = undefined;
|
|
@@ -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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
|
@@ -14,16 +14,17 @@ export interface ActivePage {
|
|
|
14
14
|
export function useActivePage(): ActivePage | null {
|
|
15
15
|
const navigationContext = React.useContext(NavigationContext);
|
|
16
16
|
const routerContext = React.useContext(RouterContext);
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
const pageRef = React.useRef<ActivePage>(null);
|
|
19
|
+
|
|
20
|
+
let pathname = routerContext?.pathname ?? "/";
|
|
18
21
|
const activeItem = matchPath(navigationContext, pathname);
|
|
19
22
|
|
|
20
23
|
const rootItem = matchPath(navigationContext, "/");
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
25
|
+
if (!activeItem) {
|
|
26
|
+
pageRef.current = null;
|
|
27
|
+
} else {
|
|
27
28
|
const breadcrumbs: Breadcrumb[] = [];
|
|
28
29
|
|
|
29
30
|
if (rootItem) {
|
|
@@ -52,11 +53,23 @@ export function useActivePage(): ActivePage | null {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const title = getItemTitle(activeItem);
|
|
57
|
+
const path = getItemPath(navigationContext, activeItem);
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
pageRef.current == null ||
|
|
61
|
+
pageRef.current.title !== title ||
|
|
62
|
+
pageRef.current.path !== path ||
|
|
63
|
+
pageRef.current.sourcePath !== pathname
|
|
64
|
+
) {
|
|
65
|
+
pageRef.current = {
|
|
66
|
+
title,
|
|
67
|
+
path,
|
|
68
|
+
sourcePath: pathname,
|
|
69
|
+
breadcrumbs
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return pageRef.current;
|
|
62
75
|
}
|