diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js index 66e996bc4cc..a0b9bc5b826 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js @@ -13,7 +13,7 @@ import { createTheme } from '@mui/material/styles'; import DashboardIcon from '@mui/icons-material/Dashboard'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import { AppProvider } from '@toolpad/core/AppProvider'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; import { Account, AccountPreview, @@ -56,6 +56,14 @@ const demoTheme = createTheme({ }, }); +function CustomToolbarActions() { + return ( + + + + ); +} + function DemoPageContent({ pathname }) { return ( {/* preview-start */} null, sidebarFooter: SidebarFooterAccount }} + slots={{ + toolbarActions: CustomToolbarActions, + sidebarFooter: SidebarFooterAccount, + }} > diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx index 78ab570494d..9f954224c1a 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx @@ -12,7 +12,11 @@ import { createTheme } from '@mui/material/styles'; import DashboardIcon from '@mui/icons-material/Dashboard'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import { AppProvider } from '@toolpad/core/AppProvider'; -import { DashboardLayout, SidebarFooterProps } from '@toolpad/core/DashboardLayout'; +import { + DashboardLayout, + SidebarFooterProps, + ThemeSwitcher, +} from '@toolpad/core/DashboardLayout'; import { Account, AccountPreview, @@ -56,6 +60,14 @@ const demoTheme = createTheme({ }, }); +function CustomToolbarActions() { + return ( + + + + ); +} + function DemoPageContent({ pathname }: { pathname: string }) { return ( {/* preview-start */} null, sidebarFooter: SidebarFooterAccount }} + slots={{ + toolbarActions: CustomToolbarActions, + sidebarFooter: SidebarFooterAccount, + }} > diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx.preview b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx.preview index f567d5793e9..d86d5248eda 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx.preview +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx.preview @@ -1,5 +1,8 @@ null, sidebarFooter: SidebarFooterAccount }} + slots={{ + toolbarActions: CustomToolbarActions, + sidebarFooter: SidebarFooterAccount, + }} > \ No newline at end of file diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js index 149bf92fd22..58308648af4 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js @@ -8,6 +8,7 @@ import IconButton from '@mui/material/IconButton'; import Popover from '@mui/material/Popover'; import Radio from '@mui/material/Radio'; import RadioGroup from '@mui/material/RadioGroup'; +import Stack from '@mui/material/Stack'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import { createTheme, useColorScheme } from '@mui/material/styles'; @@ -16,6 +17,7 @@ import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import SettingsIcon from '@mui/icons-material/Settings'; import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { Account } from '@toolpad/core/Account'; import { DemoProvider, useDemoRouter } from '@toolpad/core/internal'; const NAVIGATION = [ @@ -131,6 +133,15 @@ function CustomThemeSwitcher() { ); } +function CustomToolbarActions() { + return ( + + + + + ); +} + function DashboardLayoutCustomThemeSwitcher(props) { const { window } = props; @@ -151,7 +162,7 @@ function DashboardLayoutCustomThemeSwitcher(props) { {/* preview-start */} diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx index 65e0136126e..d244844b39d 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx @@ -7,6 +7,7 @@ import IconButton from '@mui/material/IconButton'; import Popover from '@mui/material/Popover'; import Radio from '@mui/material/Radio'; import RadioGroup from '@mui/material/RadioGroup'; +import Stack from '@mui/material/Stack'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import { createTheme, useColorScheme } from '@mui/material/styles'; @@ -15,6 +16,7 @@ import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import SettingsIcon from '@mui/icons-material/Settings'; import { AppProvider, type Navigation } from '@toolpad/core/AppProvider'; import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { Account } from '@toolpad/core/Account'; import { DemoProvider, useDemoRouter } from '@toolpad/core/internal'; const NAVIGATION: Navigation = [ @@ -126,6 +128,15 @@ function CustomThemeSwitcher() { ); } +function CustomToolbarActions() { + return ( + + + + + ); +} + interface DemoProps { /** * Injected by the documentation to work in an iframe. @@ -154,7 +165,7 @@ export default function DashboardLayoutCustomThemeSwitcher(props: DemoProps) { {/* preview-start */} diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview index c6e4f10b119..6c09aae96ab 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview @@ -1,6 +1,6 @@ diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js index cc71dc1308b..0bd4e14746f 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js @@ -15,6 +15,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import SearchIcon from '@mui/icons-material/Search'; import { AppProvider } from '@toolpad/core/AppProvider'; import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; +import { Account } from '@toolpad/core/Account'; import { DemoProvider, useDemoRouter } from '@toolpad/core/internal'; const NAVIGATION = [ @@ -103,6 +104,7 @@ function ToolbarActionsSearch() { sx={{ display: { xs: 'none', md: 'inline-block' }, mr: 1 }} /> + ); } diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx index ad0789f864e..fa7f4581b5a 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx @@ -18,6 +18,7 @@ import { ThemeSwitcher, type SidebarFooterProps, } from '@toolpad/core/DashboardLayout'; +import { Account } from '@toolpad/core/Account'; import { DemoProvider, useDemoRouter } from '@toolpad/core/internal'; const NAVIGATION: Navigation = [ @@ -102,6 +103,7 @@ function ToolbarActionsSearch() { sx={{ display: { xs: 'none', md: 'inline-block' }, mr: 1 }} /> + ); } 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 7b3fc2fa65d..06e75d36773 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md +++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md @@ -1,7 +1,7 @@ --- productId: toolpad-core title: Dashboard Layout -components: AppProvider, DashboardLayout, ToolbarActions, ThemeSwitcher, Account, DashboardSidebarPageItem +components: AppProvider, DashboardLayout, DashboardHeader, ToolbarActions, ThemeSwitcher, Account, DashboardSidebarPageItem --- # Dashboard Layout @@ -164,9 +164,11 @@ Some possibly useful slots: - `appTitle`: allows you to customize the app title section in the layout header. -- `toolbarActions`: allows you to add new items to the toolbar in the header, such as a search bar or button. The default `ThemeSwitcher` component can be imported and used if you wish to do so, as shown in the example below. +- `toolbarActions`: allows you to add new items to the toolbar in the header, such as a search bar or button. The default `ThemeSwitcher` and `Account` components can be imported and used if you wish to do so, as shown in the example below. -- `sidebarFooter`: allows you to add footer content in the sidebar. +- `header`: allows you to fully replace and customize the layout header. + +- `sidebarFooter`: allows you to add a footer to the sidebar. {{"demo": "DashboardLayoutSlots.js", "height": 400, "iframe": true}} diff --git a/docs/data/toolpad/core/pagesApi.js b/docs/data/toolpad/core/pagesApi.js index b90368dc772..955852032b0 100644 --- a/docs/data/toolpad/core/pagesApi.js +++ b/docs/data/toolpad/core/pagesApi.js @@ -8,6 +8,7 @@ export default [ { pathname: '/toolpad/core/api/crud' }, { pathname: '/toolpad/core/api/crud-form' }, { pathname: '/toolpad/core/api/crud-provider' }, + { pathname: '/toolpad/core/api/dashboard-header' }, { pathname: '/toolpad/core/api/dashboard-layout' }, { pathname: '/toolpad/core/api/dashboard-sidebar-page-item' }, { pathname: '/toolpad/core/api/dialogs-provider' }, diff --git a/docs/pages/toolpad/core/api/dashboard-header.js b/docs/pages/toolpad/core/api/dashboard-header.js new file mode 100644 index 00000000000..50c578cd38a --- /dev/null +++ b/docs/pages/toolpad/core/api/dashboard-header.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import ApiPage from 'docs/src/modules/components/ApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './dashboard-header.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs-toolpad/translations/api-docs/dashboard-header', + false, + /\.\/dashboard-header.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/toolpad/core/api/dashboard-header.json b/docs/pages/toolpad/core/api/dashboard-header.json new file mode 100644 index 00000000000..b3f83293739 --- /dev/null +++ b/docs/pages/toolpad/core/api/dashboard-header.json @@ -0,0 +1,57 @@ +{ + "props": { + "menuOpen": { "type": { "name": "bool" }, "required": true }, + "onToggleMenu": { "type": { "name": "func" }, "required": true }, + "branding": { + "type": { + "name": "shape", + "description": "{ homeUrl?: string, logo?: node, title?: string }" + }, + "default": "null" + }, + "hideMenuButton": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, toolbarAccount?: { localeText?: object, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ appTitle?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }" + }, + "default": "{}", + "additionalInfo": { "slotsApi": true } + } + }, + "name": "DashboardHeader", + "imports": ["import { DashboardHeader } from '@toolpad/core/DashboardLayout';"], + "slots": [ + { + "name": "appTitle", + "description": "The component used for the app title section.", + "default": "Link", + "class": null + }, + { + "name": "toolbarActions", + "description": "The toolbar actions component to be used.", + "default": "ToolbarActions", + "class": null + }, + { + "name": "toolbarAccount", + "description": "The toolbar account component to be used.", + "default": "Account", + "class": null + } + ], + "classes": [], + "muiName": "DashboardHeader", + "filename": "/packages/toolpad-core/src/DashboardLayout/DashboardHeader.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index ffcba22fbc3..4b5c0c7b181 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -34,14 +34,14 @@ "slotProps": { "type": { "name": "shape", - "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: object, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" + "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, header?: { branding?: { homeUrl?: string, logo?: node, title?: string }, hideMenuButton?: bool, menuOpen: bool, onToggleMenu: func, slotProps?: { appTitle?: object, toolbarAccount?: object, toolbarActions?: object }, slots?: { appTitle?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: object, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" }, "default": "{}" }, "slots": { "type": { "name": "shape", - "description": "{ appTitle?: elementType, sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }" + "description": "{ appTitle?: elementType, header?: elementType, sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }" }, "default": "{}", "additionalInfo": { "slotsApi": true } @@ -78,6 +78,12 @@ "default": "Account", "class": null }, + { + "name": "header", + "description": "The component used for the layout header.", + "default": "DashboardHeader", + "class": null + }, { "name": "sidebarFooter", "description": "Optional footer component used in the layout sidebar.", diff --git a/docs/translations/api-docs/dashboard-header/dashboard-header.json b/docs/translations/api-docs/dashboard-header/dashboard-header.json new file mode 100644 index 00000000000..cd8affb7bd3 --- /dev/null +++ b/docs/translations/api-docs/dashboard-header/dashboard-header.json @@ -0,0 +1,19 @@ +{ + "componentDescription": "", + "propDescriptions": { + "branding": { "description": "Branding options for the header." }, + "hideMenuButton": { "description": "Whether the menu icon should always be hidden." }, + "menuOpen": { + "description": "If true, show menu button as if menu is expanded, otherwise show it as if menu is collapsed." + }, + "onToggleMenu": { "description": "Callback fired when the menu button is clicked." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." } + }, + "classDescriptions": {}, + "slotDescriptions": { + "appTitle": "The component used for the app title section.", + "toolbarAccount": "The toolbar account component to be used.", + "toolbarActions": "The toolbar actions component to be used." + } +} diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index ea0ad31cdc5..cd12927ad92 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -26,6 +26,7 @@ "classDescriptions": {}, "slotDescriptions": { "appTitle": "The component used for the app title section in the layout header.", + "header": "The component used for the layout header.", "sidebarFooter": "Optional footer component used in the layout sidebar.", "toolbarAccount": "The toolbar account component used in the layout header.", "toolbarActions": "The toolbar actions component used in the layout header." diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx index 2bce076a6e8..a89b93527dd 100644 --- a/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx +++ b/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx @@ -1,11 +1,21 @@ 'use client'; import * as React from 'react'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import Stack from '@mui/material/Stack'; +import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; import { usePathname, useParams } from 'next/navigation'; import { PageContainer } from '@toolpad/core/PageContainer'; import Copyright from '../components/Copyright'; import SidebarFooterAccount, { ToolbarAccountOverride } from './SidebarFooterAccount'; +function CustomActions() { + return ( + + + + + ); +} + export default function Layout(props: { children: React.ReactNode }) { const pathname = usePathname(); const params = useParams(); @@ -27,7 +37,7 @@ export default function Layout(props: { children: React.ReactNode }) { return ( diff --git a/examples/core/firebase-vite/src/layouts/dashboard.tsx b/examples/core/firebase-vite/src/layouts/dashboard.tsx index 07e4183641b..b9b473b85cf 100644 --- a/examples/core/firebase-vite/src/layouts/dashboard.tsx +++ b/examples/core/firebase-vite/src/layouts/dashboard.tsx @@ -1,19 +1,23 @@ import * as React from 'react'; import LinearProgress from '@mui/material/LinearProgress'; +import Stack from '@mui/material/Stack'; import { Outlet, Navigate, useLocation } from 'react-router'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; import { PageContainer } from '@toolpad/core/PageContainer'; import { Account } from '@toolpad/core/Account'; import { useSession } from '../SessionContext'; -function CustomAccount() { +function CustomActions() { return ( - + + + + ); } @@ -37,7 +41,7 @@ export default function Layout() { } return ( - + diff --git a/packages/create-toolpad-app/src/templates/vite/dashboardLayout.ts b/packages/create-toolpad-app/src/templates/vite/dashboardLayout.ts index ecd6cd15846..e726dddec55 100644 --- a/packages/create-toolpad-app/src/templates/vite/dashboardLayout.ts +++ b/packages/create-toolpad-app/src/templates/vite/dashboardLayout.ts @@ -3,9 +3,14 @@ import { Template } from '../../types'; const dashboardTemplate: Template = (options) => { const { auth } = options; - return `import * as React from 'react';${auth ? `\nimport LinearProgress from '@mui/material/LinearProgress';` : ``} + return `import * as React from 'react';${ + auth + ? `\nimport LinearProgress from '@mui/material/LinearProgress';\nimport Stack from '@mui/material/Stack'; +` + : `` + } import { Outlet, useLocation, useParams, matchPath } from 'react-router'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { DashboardLayout${auth ? ', ThemeSwitcher' : ''} } from '@toolpad/core/DashboardLayout'; import { PageContainer } from '@toolpad/core/PageContainer'; ${ auth @@ -13,13 +18,16 @@ ${ import { useSession } from '../SessionContext'; -function CustomAccount() { +function CustomActions() { return ( - + + + + ); }` : '' @@ -63,7 +71,7 @@ export default function Layout() { } return ( - + diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardHeader.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardHeader.tsx new file mode 100644 index 00000000000..b56997d6196 --- /dev/null +++ b/packages/toolpad-core/src/DashboardLayout/DashboardHeader.tsx @@ -0,0 +1,263 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import MuiAppBar from '@mui/material/AppBar'; +import IconButton from '@mui/material/IconButton'; +import Toolbar from '@mui/material/Toolbar'; +import Tooltip from '@mui/material/Tooltip'; +import MenuIcon from '@mui/icons-material/Menu'; +import MenuOpenIcon from '@mui/icons-material/MenuOpen'; +import Stack from '@mui/material/Stack'; +import { BrandingContext } from '../shared/context'; +import type { Branding } from '../AppProvider'; +import type { AccountProps } from '../Account'; +import { AppTitle, type AppTitleProps } from './AppTitle'; +import { ToolbarActions } from './ToolbarActions'; + +const AppBar = styled(MuiAppBar)(({ theme }) => ({ + borderWidth: 0, + borderBottomWidth: 1, + borderStyle: 'solid', + borderColor: (theme.vars ?? theme).palette.divider, + boxShadow: 'none', + zIndex: theme.zIndex.drawer + 1, +})); + +export interface DashboardHeaderSlotProps { + appTitle?: AppTitleProps; + toolbarActions?: {}; + toolbarAccount?: AccountProps; +} + +export interface DashboardHeaderSlots { + /** + * The component used for the app title section. + * @default Link + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) + */ + appTitle?: React.ElementType; + /** + * The toolbar actions component to be used. + * @default ToolbarActions + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) + */ + toolbarActions?: React.JSXElementConstructor<{}>; + /** + * The toolbar account component to be used. + * @default Account + * @deprecated Place your custom component on the right in the `toolbarActions` slot instead. + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) + */ + toolbarAccount?: React.JSXElementConstructor; +} + +export interface DashboardHeaderProps { + /** + * Branding options for the header. + * @default null + */ + branding?: Branding | null; + /** + * If `true`, show menu button as if menu is expanded, otherwise show it as if menu is collapsed. + */ + menuOpen: boolean; + /** + * Callback fired when the menu button is clicked. + */ + onToggleMenu: (open: boolean) => void; + /** + * Whether the menu icon should always be hidden. + * @default false + */ + hideMenuButton?: boolean; + /** + * The components used for each slot inside. + * @default {} + */ + slots?: DashboardHeaderSlots; + /** + * The props used for each slot inside. + * @default {} + */ + slotProps?: DashboardHeaderSlotProps; +} +/** + * + * Demos: + * + * - [Dashboard Layout](https://mui.com/toolpad/core/react-dashboard-layout/) + * + * API: + * + * - [DashboardHeader API](https://mui.com/toolpad/core/api/dashboard-header) + */ +function DashboardHeader(props: DashboardHeaderProps) { + const { + branding: brandingProp, + menuOpen, + onToggleMenu, + hideMenuButton, + slots, + slotProps, + } = props; + + const brandingContext = React.useContext(BrandingContext); + + const branding = { ...brandingContext, ...brandingProp }; + + const handleMenuOpen = React.useCallback(() => { + onToggleMenu(!menuOpen); + }, [menuOpen, onToggleMenu]); + + const getMenuIcon = React.useCallback( + (isExpanded: boolean) => { + const expandMenuActionText = 'Expand'; + const collapseMenuActionText = 'Collapse'; + + return ( + +
+ + {isExpanded ? : } + +
+
+ ); + }, + [handleMenuOpen], + ); + + const ToolbarActionsSlot = slots?.toolbarActions ?? ToolbarActions; + const ToolbarAccountSlot = slots?.toolbarAccount ?? (() => null); + + return ( + + + + + {!hideMenuButton ? ( + + + {getMenuIcon(menuOpen)} + + + {getMenuIcon(menuOpen)} + + + ) : null} + {slots?.appTitle ? ( + + ) : ( + /* Hierarchy of application of `branding` + * 1. Branding prop passed in the `slotProps.appTitle` + * 2. Branding prop passed to the `DashboardLayout` + * 3. Branding prop passed to the `AppProvider` + */ + + )} + + + + + + + + + ); +} + +DashboardHeader.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * Branding options for the header. + * @default null + */ + branding: PropTypes.shape({ + homeUrl: PropTypes.string, + logo: PropTypes.node, + title: PropTypes.string, + }), + /** + * Whether the menu icon should always be hidden. + * @default false + */ + hideMenuButton: PropTypes.bool, + /** + * If `true`, show menu button as if menu is expanded, otherwise show it as if menu is collapsed. + */ + menuOpen: PropTypes.bool.isRequired, + /** + * Callback fired when the menu button is clicked. + */ + onToggleMenu: PropTypes.func.isRequired, + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + appTitle: PropTypes.shape({ + branding: PropTypes.shape({ + homeUrl: PropTypes.string, + logo: PropTypes.node, + title: PropTypes.string, + }), + }), + toolbarAccount: PropTypes.shape({ + localeText: PropTypes.object, + slotProps: PropTypes.shape({ + popover: PropTypes.object, + popoverContent: PropTypes.object, + preview: PropTypes.object, + signInButton: PropTypes.object, + signOutButton: PropTypes.object, + }), + slots: PropTypes.shape({ + popover: PropTypes.elementType, + popoverContent: PropTypes.elementType, + preview: PropTypes.elementType, + signInButton: PropTypes.elementType, + signOutButton: PropTypes.elementType, + }), + }), + toolbarActions: PropTypes.object, + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + appTitle: PropTypes.elementType, + toolbarAccount: PropTypes.elementType, + toolbarActions: PropTypes.elementType, + }), +} as any; + +export { DashboardHeader }; diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index c6c10253a96..199d38cef0e 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -1,64 +1,64 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { styled, useTheme, SxProps } from '@mui/material'; -import MuiAppBar from '@mui/material/AppBar'; +import { useTheme, SxProps } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import type {} from '@mui/material/themeCssVarsAugmentation'; import Box from '@mui/material/Box'; import Drawer from '@mui/material/Drawer'; -import IconButton from '@mui/material/IconButton'; -import Stack from '@mui/material/Stack'; import Toolbar from '@mui/material/Toolbar'; -import Tooltip from '@mui/material/Tooltip'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import type {} from '@mui/material/themeCssVarsAugmentation'; -import MenuIcon from '@mui/icons-material/Menu'; -import MenuOpenIcon from '@mui/icons-material/MenuOpen'; import warnOnce from '@toolpad/utils/warnOnce'; -import { BrandingContext, NavigationContext, WindowContext } from '../shared/context'; -import { Account, type AccountProps } from '../Account'; +import { NavigationContext, WindowContext } from '../shared/context'; +import type { Branding, Navigation, NavigationPageItem } from '../AppProvider'; +import type { AccountProps } from '../Account'; +import { + DashboardHeader, + type DashboardHeaderProps, + type DashboardHeaderSlotProps, +} from './DashboardHeader'; import { DashboardSidebarSubNavigation } from './DashboardSidebarSubNavigation'; -import { ToolbarActions } from './ToolbarActions'; -import { AppTitle, AppTitleProps } from './AppTitle'; import { getDrawerSxTransitionMixin, getDrawerWidthTransitionMixin } from './utils'; import { MINI_DRAWER_WIDTH } from './shared'; -import type { Branding, Navigation, NavigationPageItem } from '../AppProvider'; - -const AppBar = styled(MuiAppBar)(({ theme }) => ({ - borderWidth: 0, - borderBottomWidth: 1, - borderStyle: 'solid', - borderColor: (theme.vars ?? theme).palette.divider, - boxShadow: 'none', - zIndex: theme.zIndex.drawer + 1, -})); export interface SidebarFooterProps { mini: boolean; } export interface DashboardLayoutSlotProps { - appTitle?: AppTitleProps; - toolbarActions?: {}; - toolbarAccount?: AccountProps; + appTitle?: DashboardHeaderSlotProps['appTitle']; + toolbarActions?: DashboardHeaderSlotProps['toolbarActions']; + toolbarAccount?: DashboardHeaderSlotProps['toolbarAccount']; sidebarFooter?: SidebarFooterProps; + header?: DashboardHeaderProps; } +// @TODO: Deprecate `appTitle` and `toolbarActions` slots so that they must be used in DashboardHeader component only. Update docs accordingly. + export interface DashboardLayoutSlots { /** * The component used for the app title section in the layout header. * @default Link + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) */ appTitle?: React.ElementType; /** * The toolbar actions component used in the layout header. * @default ToolbarActions + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) */ toolbarActions?: React.JSXElementConstructor<{}>; /** * The toolbar account component used in the layout header. * @default Account + * @deprecated Place your custom component on the right in the `toolbarActions` slot instead. + * @see [DashboardLayout#slots](https://mui.com/toolpad/core/react-dashboard-layout/#slots) */ toolbarAccount?: React.JSXElementConstructor; + /** + * The component used for the layout header. + * @default DashboardHeader + */ + header?: React.JSXElementConstructor; /** * Optional footer component used in the layout sidebar. * @default null @@ -140,7 +140,7 @@ export interface DashboardLayoutProps { function DashboardLayout(props: DashboardLayoutProps) { const { children, - branding: brandingProp, + branding, navigation: navigationProp, defaultSidebarCollapsed = false, disableCollapsibleSidebar = false, @@ -160,11 +160,9 @@ function DashboardLayout(props: DashboardLayoutProps) { const theme = useTheme(); - const brandingContext = React.useContext(BrandingContext); const navigationContext = React.useContext(NavigationContext); const appWindowContext = React.useContext(WindowContext); - const branding = { ...brandingContext, ...brandingProp }; const navigation = navigationProp ?? navigationContext; const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] = @@ -239,9 +237,12 @@ function DashboardLayout(props: DashboardLayoutProps) { [setIsNavigationExpanded], ); - const toggleNavigationExpanded = React.useCallback(() => { - setIsNavigationExpanded(!isNavigationExpanded); - }, [isNavigationExpanded, setIsNavigationExpanded]); + const handleToggleHeaderMenu = React.useCallback( + (isExpanded: boolean) => { + setIsNavigationExpanded(isExpanded); + }, + [setIsNavigationExpanded], + ); const handleNavigationLinkClick = React.useCallback(() => { setIsMobileNavigationExpanded(false); @@ -250,35 +251,40 @@ function DashboardLayout(props: DashboardLayoutProps) { const isDesktopMini = !disableCollapsibleSidebar && !isDesktopNavigationExpanded; const isMobileMini = !disableCollapsibleSidebar && !isMobileNavigationExpanded; - const getMenuIcon = React.useCallback( - (isExpanded: boolean) => { - const expandMenuActionText = 'Expand'; - const collapseMenuActionText = 'Collapse'; - - return ( - -
- - {isExpanded ? : } - -
-
- ); - }, - [toggleNavigationExpanded], - ); - const hasDrawerTransitions = isOverSmViewport && (!disableCollapsibleSidebar || isOverMdViewport); - const ToolbarActionsSlot = slots?.toolbarActions ?? ToolbarActions; - const ToolbarAccountSlot = slots?.toolbarAccount ?? Account; const SidebarFooterSlot = slots?.sidebarFooter ?? null; + const HeaderSlot = slots?.header ?? DashboardHeader; + + const headerSlotProps: DashboardHeaderProps = React.useMemo( + () => ({ + branding, + menuOpen: isNavigationExpanded, + onToggleMenu: handleToggleHeaderMenu, + hideMenuButton: hideNavigation || (isOverMdViewport && disableCollapsibleSidebar), + slots: { + appTitle: slots?.appTitle, + toolbarActions: slots?.toolbarActions, + toolbarAccount: slots?.toolbarAccount, + }, + slotProps: { + appTitle: slotProps?.appTitle, + toolbarActions: slotProps?.toolbarActions, + toolbarAccount: slotProps?.toolbarAccount, + }, + ...slotProps?.header, + }), + [ + branding, + isNavigationExpanded, + handleToggleHeaderMenu, + hideNavigation, + isOverMdViewport, + disableCollapsibleSidebar, + slotProps, + slots, + ], + ); const getDrawerContent = React.useCallback( (isMini: boolean, viewport: 'phone' | 'tablet' | 'desktop') => ( @@ -363,57 +369,7 @@ function DashboardLayout(props: DashboardLayoutProps) { ...sx, }} > - - - - - {!hideNavigation ? ( - - - {getMenuIcon(isMobileNavigationExpanded)} - - - {getMenuIcon(isDesktopNavigationExpanded)} - - - ) : null} - {slots?.appTitle ? ( - - ) : ( - /* Hierarchy of application of `branding` - * 1. Branding prop passed in the `slotProps.appTitle` - * 2. Branding prop passed to the `DashboardLayout` - * 3. Branding prop passed to the `AppProvider` - */ - - )} - - - - - - - - - + {!hideNavigation ? ( + ); } diff --git a/packages/toolpad-core/src/DashboardLayout/index.ts b/packages/toolpad-core/src/DashboardLayout/index.ts index d60a4e03f93..c2cc9697ec7 100644 --- a/packages/toolpad-core/src/DashboardLayout/index.ts +++ b/packages/toolpad-core/src/DashboardLayout/index.ts @@ -1,4 +1,5 @@ export * from './DashboardLayout'; -export * from './ToolbarActions'; -export * from './ThemeSwitcher'; +export * from './DashboardHeader'; export * from './DashboardSidebarPageItem'; +export * from './ThemeSwitcher'; +export * from './ToolbarActions'; diff --git a/playground/nextjs/src/app/(dashboard)/layout.tsx b/playground/nextjs/src/app/(dashboard)/layout.tsx index 52b3ac44e6b..493cff95f43 100644 --- a/playground/nextjs/src/app/(dashboard)/layout.tsx +++ b/playground/nextjs/src/app/(dashboard)/layout.tsx @@ -16,9 +16,17 @@ import { SignOutButton, AccountPreviewProps, } from '@toolpad/core/Account'; -import { DashboardLayout, SidebarFooterProps } from '@toolpad/core/DashboardLayout'; +import { DashboardLayout, SidebarFooterProps, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; import { PageContainer } from '@toolpad/core/PageContainer'; +function ToolbarActions() { + return ( + + + + ); +} + const accounts = [ { id: 1, @@ -176,7 +184,12 @@ export default function DashboardPagesLayout(props: { children: React.ReactNode }, [orderId, pathname]); return ( - null }}> + {props.children} );