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..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,6 +37,8 @@ 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` 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). ::: @@ -46,6 +48,10 @@ 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` prop passed to the `AppProvider`. +::: + ### 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