From 6fdcf7ea90105ca2fcdbd9bf3939c7e0c7cf1181 Mon Sep 17 00:00:00 2001
From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com>
Date: Mon, 9 Dec 2024 09:33:59 +0000
Subject: [PATCH 1/3] Add navigation prop to DashboardLayout
---
.../dashboard-layout/dashboard-layout.md | 4 ++
.../toolpad/core/api/dashboard-layout.json | 7 +++
.../dashboard-layout/dashboard-layout.json | 1 +
.../src/DashboardLayout/DashboardLayout.tsx | 60 ++++++++++++++++---
pnpm-lock.yaml | 25 ++++++--
5 files changed, 83 insertions(+), 14 deletions(-)
diff --git a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
index dc46918b677..edd74eaf47c 100644
--- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
+++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
@@ -34,6 +34,8 @@ Some elements of the `DashboardLayout` can be configured to match your personali
This can be done via the `branding` prop in the [AppProvider](https://mui.com/toolpad/core/react-app-provider/), which allows for setting a custom `logo` image, a custom `title` text in the page header, and a custom `homeUrl` which the branding component leads to on click.
+Optionally, a specific `branding` can also be passed as a prop to the `DashboardLayout` component itself.
+
{{"demo": "DashboardLayoutBranding.js", "height": 400, "iframe": true}}
:::info
@@ -46,6 +48,8 @@ The `navigation` prop in the [AppProvider](https://mui.com/toolpad/core/react-ap
The flexibility in composing and ordering these different elements allows for a great variety of navigation structures to fit your use case.
+Optionally, a specific `navigation` can also be passed as a prop to the `DashboardLayout` component itself.
+
### Navigation links
Navigation links can be placed in the sidebar as items with the format:
diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json
index fb900ae907a..6cb95c85aa3 100644
--- a/docs/pages/toolpad/core/api/dashboard-layout.json
+++ b/docs/pages/toolpad/core/api/dashboard-layout.json
@@ -11,6 +11,13 @@
"defaultSidebarCollapsed": { "type": { "name": "bool" }, "default": "false" },
"disableCollapsibleSidebar": { "type": { "name": "bool" }, "default": "false" },
"hideNavigation": { "type": { "name": "bool" }, "default": "false" },
+ "navigation": {
+ "type": {
+ "name": "arrayOf",
+ "description": "Array<{ action?: node, children?: Array<object
| { kind: 'header', title: string }
| { kind: 'divider' }>, icon?: node, kind?: 'page', pattern?: string, segment?: string, title?: string }
| { kind: 'header', title: string }
| { kind: 'divider' }>"
+ },
+ "default": "[]"
+ },
"sidebarExpandedWidth": {
"type": { "name": "union", "description": "number
| string" },
"default": "320"
diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
index 3cd8c58bad6..a9668478927 100644
--- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
+++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
@@ -12,6 +12,7 @@
"hideNavigation": {
"description": "Whether the navigation bar and menu icon should be hidden"
},
+ "navigation": { "description": "Navigation definition for the dashboard." },
"sidebarExpandedWidth": { "description": "Width of the sidebar when expanded." },
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." },
diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx
index da545108989..f312b2c9c9b 100644
--- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx
+++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx
@@ -19,7 +19,7 @@ import { DashboardSidebarSubNavigation } from './DashboardSidebarSubNavigation';
import { ToolbarActions } from './ToolbarActions';
import { AppTitle, AppTitleProps } from './AppTitle';
import { getDrawerSxTransitionMixin, getDrawerWidthTransitionMixin } from './utils';
-import type { Branding } from '../AppProvider';
+import type { Branding, Navigation } from '../AppProvider';
const AppBar = styled(MuiAppBar)(({ theme }) => ({
borderWidth: 0,
@@ -74,6 +74,11 @@ export interface DashboardLayoutProps {
* @default null
*/
branding?: Branding | null;
+ /**
+ * Navigation definition for the dashboard.
+ * @default []
+ */
+ navigation?: Navigation;
/**
* Whether the sidebar should not be collapsible to a mini variant in desktop and tablet viewports.
* @default false
@@ -124,6 +129,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
const {
children,
branding: brandingProp,
+ navigation: navigationProp,
disableCollapsibleSidebar = false,
defaultSidebarCollapsed = false,
hideNavigation = false,
@@ -139,6 +145,9 @@ function DashboardLayout(props: DashboardLayoutProps) {
const navigationContext = React.useContext(NavigationContext);
const appWindowContext = React.useContext(WindowContext);
+ const branding = { ...brandingContext, ...brandingProp };
+ const navigation = navigationProp ?? navigationContext;
+
const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] =
React.useState(!defaultSidebarCollapsed);
const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] = React.useState(false);
@@ -208,10 +217,10 @@ function DashboardLayout(props: DashboardLayoutProps) {
// If useEffect was used, the reset would also happen on the client render after SSR which we don't need
React.useMemo(() => {
- if (navigationContext) {
+ if (navigation) {
selectedItemIdRef.current = '';
}
- }, [navigationContext]);
+ }, [navigation]);
const isDesktopMini = !disableCollapsibleSidebar && !isDesktopNavigationExpanded;
const isMobileMini = !disableCollapsibleSidebar && !isMobileNavigationExpanded;
@@ -247,8 +256,6 @@ function DashboardLayout(props: DashboardLayoutProps) {
const ToolbarAccountSlot = slots?.toolbarAccount ?? Account;
const SidebarFooterSlot = slots?.sidebarFooter ?? null;
- const appTitleBrandingProp = { ...brandingContext, ...brandingProp };
-
const getDrawerContent = React.useCallback(
(isMini: boolean, viewport: 'phone' | 'tablet' | 'desktop') => (
@@ -262,14 +269,14 @@ function DashboardLayout(props: DashboardLayoutProps) {
flexDirection: 'column',
justifyContent: 'space-between',
overflow: 'auto',
- pt: navigationContext[0]?.kind === 'header' && !isMini ? 0 : 2,
+ pt: navigation[0]?.kind === 'header' && !isMini ? 0 : 2,
...(hasDrawerTransitions
? getDrawerSxTransitionMixin(isNavigationFullyExpanded, 'padding')
: {}),
}}
>
+
)}
@@ -483,6 +490,41 @@ DashboardLayout.propTypes /* remove-proptypes */ = {
* @default false
*/
hideNavigation: PropTypes.bool,
+ /**
+ * Navigation definition for the dashboard.
+ * @default []
+ */
+ navigation: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.shape({
+ action: PropTypes.node,
+ children: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.shape({
+ kind: PropTypes.oneOf(['header']).isRequired,
+ title: PropTypes.string.isRequired,
+ }),
+ PropTypes.shape({
+ kind: PropTypes.oneOf(['divider']).isRequired,
+ }),
+ ]).isRequired,
+ ),
+ icon: PropTypes.node,
+ kind: PropTypes.oneOf(['page']),
+ pattern: PropTypes.string,
+ segment: PropTypes.string,
+ title: PropTypes.string,
+ }),
+ PropTypes.shape({
+ kind: PropTypes.oneOf(['header']).isRequired,
+ title: PropTypes.string.isRequired,
+ }),
+ PropTypes.shape({
+ kind: PropTypes.oneOf(['divider']).isRequired,
+ }),
+ ]).isRequired,
+ ),
/**
* Width of the sidebar when expanded.
* @default 320
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c91e1271ffc..005d9422053 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,7 +195,7 @@ importers:
version: 7.37.2(eslint@8.57.1)
eslint-plugin-react-compiler:
specifier: latest
- version: 19.0.0-beta-df7b47d-20241124(eslint@8.57.1)
+ version: 19.0.0-beta-37ed2a7-20241206(eslint@8.57.1)
eslint-plugin-react-hooks:
specifier: 5.0.0
version: 5.0.0(eslint@8.57.1)
@@ -1632,6 +1632,13 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
+ '@babel/plugin-proposal-private-methods@7.18.6':
+ resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
+ engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
engines: {node: '>=6.9.0'}
@@ -6057,8 +6064,8 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
- eslint-plugin-react-compiler@19.0.0-beta-df7b47d-20241124:
- resolution: {integrity: sha512-82PfnllC8jP/68KdLAbpWuYTcfmtGLzkqy2IW85WopKMTr+4rdQpp+lfliQ/QE79wWrv/dRoADrk3Pdhq25nTw==}
+ eslint-plugin-react-compiler@19.0.0-beta-37ed2a7-20241206:
+ resolution: {integrity: sha512-5Pex1fUCJwLwwqEJe6NkgTn45kUjjj9TZP6IrW4IcpWM/YaEe+QvcOeF60huDjBq0kz1svGeW2nw8WdY+qszAw==}
engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0}
peerDependencies:
eslint: '>=7'
@@ -10913,6 +10920,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.26.0)':
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
+ '@babel/helper-plugin-utils': 7.25.9
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
@@ -15900,11 +15915,11 @@ snapshots:
globals: 13.24.0
rambda: 7.5.0
- eslint-plugin-react-compiler@19.0.0-beta-df7b47d-20241124(eslint@8.57.1):
+ eslint-plugin-react-compiler@19.0.0-beta-37ed2a7-20241206(eslint@8.57.1):
dependencies:
'@babel/core': 7.26.0
'@babel/parser': 7.26.2
- '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0)
+ '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.0)
eslint: 8.57.1
hermes-parser: 0.25.1
zod: 3.23.8
From e5c132adca2a112b36c8d7793802288cef91e241 Mon Sep 17 00:00:00 2001
From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com>
Date: Wed, 11 Dec 2024 13:25:13 +0000
Subject: [PATCH 2/3] Adjust docs
---
.../core/components/dashboard-layout/dashboard-layout.md | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
index edd74eaf47c..401e8540a6f 100644
--- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
+++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
@@ -34,11 +34,11 @@ Some elements of the `DashboardLayout` can be configured to match your personali
This can be done via the `branding` prop in the [AppProvider](https://mui.com/toolpad/core/react-app-provider/), which allows for setting a custom `logo` image, a custom `title` text in the page header, and a custom `homeUrl` which the branding component leads to on click.
-Optionally, a specific `branding` can also be passed as a prop to the `DashboardLayout` component itself.
-
{{"demo": "DashboardLayoutBranding.js", "height": 400, "iframe": true}}
:::info
+Optionally, a specific `branding` can also be passed as a prop to the `DashboardLayout` component itself, which properties take precedence over any properties of the `branding` set in the `AppProvider`.
+
You may also override the default branding by passing in your own component to the `appTitle` slot, as shown in the [Slots section](#slots).
:::
@@ -48,7 +48,9 @@ The `navigation` prop in the [AppProvider](https://mui.com/toolpad/core/react-ap
The flexibility in composing and ordering these different elements allows for a great variety of navigation structures to fit your use case.
-Optionally, a specific `navigation` can also be passed as a prop to the `DashboardLayout` component itself.
+:::info
+Optionally, a specific `navigation` can also be passed as a prop to the `DashboardLayout` component itself, which takes complete precedence over any `navigation` set in the `AppProvider`.
+:::
### Navigation links
From e6f49b57bb7c25e4b67fc9479b308ee489f6c299 Mon Sep 17 00:00:00 2001
From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com>
Date: Wed, 11 Dec 2024 14:13:36 +0000
Subject: [PATCH 3/3] Copy adjustments
---
.../core/components/dashboard-layout/dashboard-layout.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
index 401e8540a6f..056add3b03c 100644
--- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
+++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
@@ -37,7 +37,7 @@ This can be done via the `branding` prop in the [AppProvider](https://mui.com/to
{{"demo": "DashboardLayoutBranding.js", "height": 400, "iframe": true}}
:::info
-Optionally, a specific `branding` can also be passed as a prop to the `DashboardLayout` component itself, which properties take precedence over any properties of the `branding` set in the `AppProvider`.
+Optionally, a specific `branding` can also be passed as a prop to the `DashboardLayout` component itself, which properties take precedence over any properties of the `branding` prop passed to the `AppProvider`.
You may also override the default branding by passing in your own component to the `appTitle` slot, as shown in the [Slots section](#slots).
:::
@@ -49,7 +49,7 @@ The `navigation` prop in the [AppProvider](https://mui.com/toolpad/core/react-ap
The flexibility in composing and ordering these different elements allows for a great variety of navigation structures to fit your use case.
:::info
-Optionally, a specific `navigation` can also be passed as a prop to the `DashboardLayout` component itself, which takes complete precedence over any `navigation` set in the `AppProvider`.
+Optionally, a specific `navigation` can also be passed as a prop to the `DashboardLayout` component itself, which takes complete precedence over any `navigation` prop passed to the `AppProvider`.
:::
### Navigation links