diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBasic.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBasic.tsx index fd2b310d2cc..a70fed83e8b 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBasic.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBasic.tsx @@ -7,9 +7,9 @@ import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import BarChartIcon from '@mui/icons-material/BarChart'; import DescriptionIcon from '@mui/icons-material/Description'; import LayersIcon from '@mui/icons-material/Layers'; -import { AppProvider, Router } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import type { Navigation } from '@toolpad/core'; +import type { Navigation, Router } from '@toolpad/core'; const NAVIGATION: Navigation = [ { diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBranding.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBranding.tsx index d246ee44077..9ad3d2dbba2 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBranding.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutBranding.tsx @@ -4,9 +4,9 @@ import Typography from '@mui/material/Typography'; import { extendTheme } from '@mui/material/styles'; import DashboardIcon from '@mui/icons-material/Dashboard'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; -import { AppProvider, Router } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import type { Navigation } from '@toolpad/core'; +import type { Navigation, Router } from '@toolpad/core'; const NAVIGATION: Navigation = [ { diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationActions.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationActions.tsx index e5623b8c2fc..d890368e9fb 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationActions.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationActions.tsx @@ -11,8 +11,9 @@ import CallIcon from '@mui/icons-material/Call'; import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import CallMadeIcon from '@mui/icons-material/CallMade'; import CallReceivedIcon from '@mui/icons-material/CallReceived'; -import { AppProvider, Router } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import type { Router } from '@toolpad/core'; const demoTheme = extendTheme({ breakpoints: { diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationItems.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationItems.tsx index 7597bb58ef0..25524047823 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationItems.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutNavigationItems.tsx @@ -4,8 +4,9 @@ import Typography from '@mui/material/Typography'; import { extendTheme } from '@mui/material/styles'; import DescriptionIcon from '@mui/icons-material/Description'; import FolderIcon from '@mui/icons-material/Folder'; -import { AppProvider, Router } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import type { Router } from '@toolpad/core'; const demoTheme = extendTheme({ breakpoints: { diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutStandalone.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutStandalone.tsx new file mode 100644 index 00000000000..d662c562142 --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutStandalone.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { CssVarsProvider, extendTheme } from '@mui/material/styles'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import type { Navigation, Router } from '@toolpad/core'; + +const NAVIGATION: Navigation = [ + { + segment: 'dashboard', + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const demoTheme = extendTheme({ + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 600, + lg: 1200, + xl: 1536, + }, + }, +}); + +function DemoPageContent({ pathname }: { pathname: string }) { + return ( + + Dashboard content for {pathname} + + ); +} + +interface DemoProps { + /** + * Injected by the documentation to work in an iframe. + * Remove this when copying and pasting into your project. + */ + window?: () => Window; +} + +export default function DashboardLayoutStandalone(props: DemoProps) { + const { window } = props; + + const [pathname, setPathname] = React.useState('dashboard'); + + const router = React.useMemo(() => { + return { + pathname, + searchParams: new URLSearchParams(), + navigate: (path) => setPathname(String(path)), + }; + }, [pathname]); + + // Remove this const when copying and pasting into your project. + const demoWindow = window !== undefined ? window() : undefined; + + return ( + + , + title: 'MUI', + }} + colorScheme={} + onColorSchemeChange={} + router={router} + window={demoWindow} + > + + + + ); +} 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 44f31f12474..9775ad83694 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md +++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md @@ -10,7 +10,8 @@ components: AppProvider, DashboardLayout The `DashboardLayout` component is a quick, easy way to provide a standard full-screen layout with a header and sidebar to any dashboard page, as well as ready-to-use and easy to customize navigation and branding. -Many features of this component are configurable through the [AppProvider](https://mui.com/toolpad/core/react-app-provider/) component that should wrap it. +Many features of this component are configurable through the [AppProvider](https://mui.com/toolpad/core/react-app-provider/) component that should wrap it, which is the recommended approach for a Toolpad app. +However, it is also possible to use the `DashboardLayout` as a standalone component by setting those configurations with the component props themselves. ## Demo @@ -50,3 +51,9 @@ The main navigation items that can be used are: Navigation links have an optional `action` prop that can be used to render any content on the right-side of the respective list item, such as badges with numbers, or buttons to toggle a popover menu. {{"demo": "DashboardLayoutNavigationActions.js", "height": 500, "iframe": true}} + +## Standalone Usage + +The component branding, navigation, theme switching and routing can also be set via component props themselves instead of indirectly through an `AppProvider`. + +{{"demo": "DashboardLayoutStandalone.js", "height": 500, "iframe": true}} diff --git a/docs/data/toolpad/core/introduction/TutorialDefault.tsx b/docs/data/toolpad/core/introduction/TutorialDefault.tsx index 671c1574944..8eca598433d 100644 --- a/docs/data/toolpad/core/introduction/TutorialDefault.tsx +++ b/docs/data/toolpad/core/introduction/TutorialDefault.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { extendTheme } from '@mui/material/styles'; import DashboardIcon from '@mui/icons-material/Dashboard'; -import { AppProvider, Navigation } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; import { PageContainer } from '@toolpad/core/PageContainer'; import { useDemoRouter } from '@toolpad/core/internals/demo'; +import type { Navigation } from '@toolpad/core'; const NAVIGATION: Navigation = [ { diff --git a/docs/data/toolpad/core/introduction/TutorialPages.tsx b/docs/data/toolpad/core/introduction/TutorialPages.tsx index ab1e1daa7f8..090c4d2e0d6 100644 --- a/docs/data/toolpad/core/introduction/TutorialPages.tsx +++ b/docs/data/toolpad/core/introduction/TutorialPages.tsx @@ -2,10 +2,11 @@ import * as React from 'react'; import { extendTheme } from '@mui/material/styles'; import DashboardIcon from '@mui/icons-material/Dashboard'; import TimelineIcon from '@mui/icons-material/Timeline'; -import { AppProvider, Navigation } from '@toolpad/core/AppProvider'; +import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; import { useDemoRouter } from '@toolpad/core/internals/demo'; import { PageContainer } from '@toolpad/core/PageContainer'; +import type { Navigation } from '@toolpad/core'; import { Typography } from '@mui/material'; const NAVIGATION: Navigation = [ diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index af4c4e66fda..f0e36989a02 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -1,5 +1,21 @@ { - "props": { "children": { "type": { "name": "node" }, "required": true } }, + "props": { + "children": { "type": { "name": "node" }, "required": true }, + "branding": { + "type": { "name": "shape", "description": "{ logo?: node, title?: string }" }, + "default": "null" + }, + "colorScheme": { "type": { "name": "enum", "description": "'dark'
| 'light'" } }, + "navigation": { + "type": { + "name": "arrayOf", + "description": "Array<{ action?: node, children?: Array<object
| { kind: 'header', title: string }
| { kind: 'divider' }>, icon?: node, kind?: 'page', segment: string, title?: string }
| { kind: 'header', title: string }
| { kind: 'divider' }>" + }, + "default": "[]" + }, + "onColorSchemeChange": { "type": { "name": "func" } }, + "window": { "type": { "name": "object" }, "default": "window" } + }, "name": "DashboardLayout", "imports": [ "import { DashboardLayout } from '@toolpad-core/DashboardLayout';", diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index 8e461ca2aaa..fa5e039d84a 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -1,5 +1,16 @@ { "componentDescription": "", - "propDescriptions": { "children": { "description": "The content of the dashboard." } }, + "propDescriptions": { + "branding": { "description": "Branding options for the layout." }, + "children": { "description": "The content of the dashboard." }, + "colorScheme": { "description": "Active color scheme in theme." }, + "navigation": { "description": "Navigation definition for the layout." }, + "onColorSchemeChange": { + "description": "Callback to run when the theme color scheme is changed." + }, + "window": { + "description": "The window where the layout is rendered. This is needed when rendering the layout inside an iframe, for example." + } + }, "classDescriptions": {} } diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 61d1d032ead..820fd03d3fb 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { styled, useTheme } from '@mui/material'; +import { PaletteMode, styled, useTheme } from '@mui/material'; import MuiAppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; @@ -34,7 +34,7 @@ import { RouterContext, WindowContext, } from '../shared/context'; -import type { Navigation, NavigationPageItem } from '../AppProvider'; +import type { AppProviderProps, Navigation, NavigationPageItem } from '../AppProvider'; import { ToolpadLogo } from './ToolpadLogo'; import { getItemTitle, isPageItem } from '../shared/navigation'; import { useApplicationTitle } from '../shared/branding'; @@ -79,11 +79,20 @@ const NavigationListItemButton = styled(ListItemButton)(({ theme }) => ({ }, })); -function ThemeSwitcher() { +interface ThemeSwitcherProps { + colorScheme?: PaletteMode; + onColorSchemeChange?: (mode: PaletteMode) => void; +} + +function ThemeSwitcher({ colorScheme, onColorSchemeChange }: ThemeSwitcherProps) { const isSsr = useSsr(); const theme = useTheme(); - const { paletteMode, setPaletteMode, isDualTheme } = React.useContext(PaletteModeContext); + const paletteModeContext = React.useContext(PaletteModeContext); + + const paletteMode = colorScheme ?? paletteModeContext.paletteMode; + const setPaletteMode = onColorSchemeChange ?? paletteModeContext.setPaletteMode; + const isDualTheme = !!onColorSchemeChange || paletteModeContext.isDualTheme; const toggleMode = React.useCallback(() => { setPaletteMode(paletteMode === 'dark' ? 'light' : 'dark'); @@ -303,6 +312,30 @@ export interface DashboardLayoutProps { * The content of the dashboard. */ children: React.ReactNode; + /** + * Branding options for the layout. + * @default null + */ + branding?: AppProviderProps['branding']; + /** + * Navigation definition for the layout. + * @default [] + */ + navigation?: AppProviderProps['navigation']; + /** + * Active color scheme in theme. + */ + colorScheme?: PaletteMode; + /** + * Callback to run when the theme color scheme is changed. + */ + onColorSchemeChange?: (theme: PaletteMode) => void; + /** + * The window where the layout is rendered. + * This is needed when rendering the layout inside an iframe, for example. + * @default window + */ + window?: AppProviderProps['window']; } /** @@ -316,13 +349,25 @@ export interface DashboardLayoutProps { * - [DashboardLayout API](https://mui.com/toolpad/core/api/dashboard-layout) */ function DashboardLayout(props: DashboardLayoutProps) { - const { children } = props; + const { + children, + colorScheme, + onColorSchemeChange, + branding: brandingProp, + navigation: navigationProp, + window: windowProp, + } = props; + + const brandingContext = React.useContext(BrandingContext); + const navigationContext = React.useContext(NavigationContext); + const windowContext = React.useContext(WindowContext); - const branding = React.useContext(BrandingContext); - const navigation = React.useContext(NavigationContext); - const appWindow = React.useContext(WindowContext); const applicationTitle = useApplicationTitle(); + const branding = brandingProp ?? brandingContext; + const navigation = navigationProp ?? navigationContext; + const appWindow = windowProp ?? windowContext; + const [isMobileNavigationOpen, setIsMobileNavigationOpen] = React.useState(false); const handleSetMobileNavigationOpen = React.useCallback( @@ -399,7 +444,7 @@ function DashboardLayout(props: DashboardLayoutProps) { - +