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