diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js new file mode 100644 index 00000000000..bc46d147d7d --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.js @@ -0,0 +1,168 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Box from '@mui/material/Box'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +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 Tooltip from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import { createTheme, useColorScheme } from '@mui/material/styles'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +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 { useDemoRouter } from '@toolpad/core/internal'; + +const NAVIGATION = [ + { + kind: 'header', + title: 'Main items', + }, + { + segment: 'dashboard', + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const demoTheme = createTheme({ + cssVariables: { + colorSchemeSelector: 'data-toolpad-color-scheme', + }, + colorSchemes: { light: true, dark: true }, + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 600, + lg: 1200, + xl: 1536, + }, + }, +}); + +function DemoPageContent({ pathname }) { + return ( + + Dashboard content for {pathname} + + ); +} + +DemoPageContent.propTypes = { + pathname: PropTypes.string.isRequired, +}; + +function CustomThemeSwitcher() { + const { setMode } = useColorScheme(); + + const handleThemeChange = React.useCallback( + (event) => { + setMode(event.target.value); + }, + [setMode], + ); + + const [isMenuOpen, setIsMenuOpen] = React.useState(false); + const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); + + const toggleMenu = React.useCallback( + (event) => { + setMenuAnchorEl(isMenuOpen ? null : event.currentTarget); + setIsMenuOpen((previousIsMenuOpen) => !previousIsMenuOpen); + }, + [isMenuOpen], + ); + + return ( + + +
+ + + +
+
+ + + + Theme + + } label="Light" /> + } label="System" /> + } label="Dark" /> + + + + +
+ ); +} + +function DashboardLayoutCustomThemeSwitcher(props) { + const { window } = props; + + const router = useDemoRouter('/dashboard'); + + // Remove this const when copying and pasting into your project. + const demoWindow = window !== undefined ? window() : undefined; + + return ( + + + + + + ); +} + +DashboardLayoutCustomThemeSwitcher.propTypes = { + /** + * Injected by the documentation to work in an iframe. + * Remove this when copying and pasting into your project. + */ + window: PropTypes.func, +}; + +export default DashboardLayoutCustomThemeSwitcher; diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx new file mode 100644 index 00000000000..567db04cced --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx @@ -0,0 +1,161 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +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 Tooltip from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import { createTheme, useColorScheme } from '@mui/material/styles'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +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 { useDemoRouter } from '@toolpad/core/internal'; + +const NAVIGATION: Navigation = [ + { + kind: 'header', + title: 'Main items', + }, + { + segment: 'dashboard', + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const demoTheme = createTheme({ + cssVariables: { + colorSchemeSelector: 'data-toolpad-color-scheme', + }, + colorSchemes: { light: true, dark: true }, + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 600, + lg: 1200, + xl: 1536, + }, + }, +}); + +function DemoPageContent({ pathname }: { pathname: string }) { + return ( + + Dashboard content for {pathname} + + ); +} + +function CustomThemeSwitcher() { + const { setMode } = useColorScheme(); + + const handleThemeChange = React.useCallback( + (event: React.ChangeEvent) => { + setMode(event.target.value as 'light' | 'dark' | 'system'); + }, + [setMode], + ); + + const [isMenuOpen, setIsMenuOpen] = React.useState(false); + const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); + + const toggleMenu = React.useCallback( + (event: React.MouseEvent) => { + setMenuAnchorEl(isMenuOpen ? null : event.currentTarget); + setIsMenuOpen((previousIsMenuOpen) => !previousIsMenuOpen); + }, + [isMenuOpen], + ); + + return ( + + +
+ + + +
+
+ + + + Theme + + } label="Light" /> + } label="System" /> + } label="Dark" /> + + + + +
+ ); +} + +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 DashboardLayoutCustomThemeSwitcher(props: DemoProps) { + const { window } = props; + + const router = useDemoRouter('/dashboard'); + + // Remove this const when copying and pasting into your project. + const demoWindow = window !== undefined ? window() : undefined; + + return ( + + + + + + ); +} diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview new file mode 100644 index 00000000000..c6e4f10b119 --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutCustomThemeSwitcher.tsx.preview @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js index b8c9169d49b..186a5ac3c6d 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; +import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; @@ -10,7 +11,7 @@ import DashboardIcon from '@mui/icons-material/Dashboard'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import SearchIcon from '@mui/icons-material/Search'; import { AppProvider } from '@toolpad/core/AppProvider'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout'; import { useDemoRouter } from '@toolpad/core/internal'; const NAVIGATION = [ @@ -66,9 +67,9 @@ DemoPageContent.propTypes = { pathname: PropTypes.string.isRequired, }; -function Search() { +function ToolbarActionsSearch() { return ( - +
- + + ); } @@ -133,7 +135,10 @@ function DashboardLayoutSlots(props) { window={demoWindow} > diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx index 89afce5d4ce..a18eb8d496b 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; +import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; @@ -11,6 +12,7 @@ import SearchIcon from '@mui/icons-material/Search'; import { AppProvider, type Navigation } from '@toolpad/core/AppProvider'; import { DashboardLayout, + ThemeSwitcher, type SidebarFooterProps, } from '@toolpad/core/DashboardLayout'; import { useDemoRouter } from '@toolpad/core/internal'; @@ -64,9 +66,9 @@ function DemoPageContent({ pathname }: { pathname: string }) { ); } -function Search() { +function ToolbarActionsSearch() { return ( - +
- + + ); } @@ -135,7 +138,10 @@ export default function DashboardLayoutSlots(props: DemoProps) { window={demoWindow} > diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview index 3320e7fe140..19bf54bf3e7 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview @@ -1,5 +1,8 @@ \ No newline at end of file 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 91572f19ed6..a8819a625fd 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, Account +components: AppProvider, DashboardLayout, ToolbarActions, ThemeSwitcher, Account --- # Dashboard Layout @@ -93,7 +93,8 @@ Navigation links have an optional `action` prop to render any content on the rig ### Navigation pattern matching Navigation links have an optional `pattern` prop to define a pattern to be matched for the item to be marked as selected. -This feature is built on top of the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) library. Some examples: +This feature is built on top of the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) library. +Some examples: - Constant path: `orders` - Dynamic segment: `orders/:segment` @@ -139,14 +140,25 @@ The use of an `iframe` may cause some spacing issues in the following demo. ## Customization -Some areas of the layout can be replaced with custom components by using the `slots` and `slotProps` props. -This allows you to add, for example: +### Slots -- new items to the toolbar in the header, such as a search bar or button; -- footer content in the sidebar. +Certain areas of the layout can be replaced with custom components by using the `slots` and `slotProps` props. +Some possibly useful slots: + +- `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. + +- `sidebarFooter`: allows you to add footer content in the sidebar. {{"demo": "DashboardLayoutSlots.js", "height": 400, "iframe": true}} -Through this, you can also modify the position of the `` component and use it inside the sidebar: +### Examples + +#### User account in layout sidebar {{"demo": "DashboardLayoutAccountSidebar.js", "height": 400, "iframe": true}} + +#### Settings menu with custom theme switcher + +The `useColorScheme` hook can be used to create a custom theme switcher. + +{{"demo": "DashboardLayoutCustomThemeSwitcher.js", "height": 400, "iframe": true}} diff --git a/docs/data/toolpad/core/pagesApi.js b/docs/data/toolpad/core/pagesApi.js index ea749c0aff6..8a3bc6d2b60 100644 --- a/docs/data/toolpad/core/pagesApi.js +++ b/docs/data/toolpad/core/pagesApi.js @@ -12,4 +12,6 @@ module.exports = [ { pathname: '/toolpad/core/api/sign-in-button' }, { pathname: '/toolpad/core/api/sign-in-page' }, { pathname: '/toolpad/core/api/sign-out-button' }, + { pathname: '/toolpad/core/api/theme-switcher' }, + { pathname: '/toolpad/core/api/toolbar-actions' }, ]; diff --git a/docs/pages/toolpad/core/api/theme-switcher.js b/docs/pages/toolpad/core/api/theme-switcher.js new file mode 100644 index 00000000000..61c6592d7c7 --- /dev/null +++ b/docs/pages/toolpad/core/api/theme-switcher.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 './theme-switcher.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs-toolpad/translations/api-docs/theme-switcher', + false, + /\.\/theme-switcher.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/toolpad/core/api/theme-switcher.json b/docs/pages/toolpad/core/api/theme-switcher.json new file mode 100644 index 00000000000..6869b76640d --- /dev/null +++ b/docs/pages/toolpad/core/api/theme-switcher.json @@ -0,0 +1,11 @@ +{ + "props": {}, + "name": "ThemeSwitcher", + "imports": ["import { ThemeSwitcher } from '@toolpad/core/DashboardLayout';"], + "classes": [], + "muiName": "ThemeSwitcher", + "filename": "/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/toolpad/core/api/toolbar-actions.js b/docs/pages/toolpad/core/api/toolbar-actions.js new file mode 100644 index 00000000000..91cfb0d3c0d --- /dev/null +++ b/docs/pages/toolpad/core/api/toolbar-actions.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 './toolbar-actions.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs-toolpad/translations/api-docs/toolbar-actions', + false, + /\.\/toolbar-actions.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/toolpad/core/api/toolbar-actions.json b/docs/pages/toolpad/core/api/toolbar-actions.json new file mode 100644 index 00000000000..b5b794315c2 --- /dev/null +++ b/docs/pages/toolpad/core/api/toolbar-actions.json @@ -0,0 +1,11 @@ +{ + "props": {}, + "name": "ToolbarActions", + "imports": ["import { ToolbarActions } from '@toolpad/core/DashboardLayout';"], + "classes": [], + "muiName": "ToolbarActions", + "filename": "/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/translations/api-docs/theme-switcher/theme-switcher.json b/docs/translations/api-docs/theme-switcher/theme-switcher.json new file mode 100644 index 00000000000..f93d4cbd8c7 --- /dev/null +++ b/docs/translations/api-docs/theme-switcher/theme-switcher.json @@ -0,0 +1 @@ +{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} } diff --git a/docs/translations/api-docs/toolbar-actions/toolbar-actions.json b/docs/translations/api-docs/toolbar-actions/toolbar-actions.json new file mode 100644 index 00000000000..f93d4cbd8c7 --- /dev/null +++ b/docs/translations/api-docs/toolbar-actions/toolbar-actions.json @@ -0,0 +1 @@ +{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} } diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 4891f42f7d2..2d921608aba 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -20,7 +20,6 @@ import { Account, type AccountProps } from '../Account'; import { useApplicationTitle } from '../shared/branding'; import { DashboardSidebarSubNavigation } from './DashboardSidebarSubNavigation'; import { ToolbarActions } from './ToolbarActions'; -import { ThemeSwitcher } from './ThemeSwitcher'; import { ToolpadLogo } from './ToolpadLogo'; import { getDrawerSxTransitionMixin, getDrawerWidthTransitionMixin } from './utils'; @@ -376,7 +375,6 @@ function DashboardLayout(props: DashboardLayoutProps) { - diff --git a/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx b/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx index 36aa02d3d6b..23fa68d0fd7 100644 --- a/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx +++ b/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx @@ -7,10 +7,15 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import useSsr from '@toolpad/utils/hooks/useSsr'; import { PaletteModeContext } from '../shared/context'; - -// TODO: When we use this component as the default for a slot, make it non-internal /** - * @ignore - internal component. + * + * Demos: + * + * - [Dashboard Layout](https://mui.com/toolpad/core/react-dashboard-layout/) + * + * API: + * + * - [ThemeSwitcher API](https://mui.com/toolpad/core/api/theme-switcher) */ function ThemeSwitcher() { const isSsr = useSsr(); diff --git a/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx b/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx index 3292d10cc1b..a40cd6eca85 100644 --- a/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx +++ b/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx @@ -1,11 +1,23 @@ 'use client'; - -// TODO: When we add content to this component, make it non-internal +import * as React from 'react'; +import Stack from '@mui/material/Stack'; +import { ThemeSwitcher } from './ThemeSwitcher'; /** - * @ignore - internal component. + * + * Demos: + * + * - [Dashboard Layout](https://mui.com/toolpad/core/react-dashboard-layout/) + * + * API: + * + * - [ToolbarActions API](https://mui.com/toolpad/core/api/toolbar-actions) */ function ToolbarActions() { - return null; + return ( + + + + ); } export { ToolbarActions }; diff --git a/packages/toolpad-core/src/DashboardLayout/index.ts b/packages/toolpad-core/src/DashboardLayout/index.ts index f65e521b923..31b6ce8a976 100644 --- a/packages/toolpad-core/src/DashboardLayout/index.ts +++ b/packages/toolpad-core/src/DashboardLayout/index.ts @@ -1,4 +1,3 @@ export * from './DashboardLayout'; - -// Default slots components export * from './ToolbarActions'; +export * from './ThemeSwitcher';