From 5b51a00a61bc167d95d60227adc85f5c6819ea86 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:04:07 +0100 Subject: [PATCH 01/11] Add toolbarActions slot to DashboardLayout --- packages/toolpad-core/src/Account/Account.tsx | 2 +- .../src/AppProvider/AppProvider.tsx | 6 +- .../src/DashboardLayout/DashboardLayout.tsx | 90 +++++-------------- .../src/layout/ToolbarActions.tsx | 80 +++++++++++++++++ packages/toolpad-core/src/layout/index.ts | 1 + packages/toolpad-core/src/shared/context.ts | 6 +- pnpm-lock.yaml | 4 +- 7 files changed, 113 insertions(+), 76 deletions(-) create mode 100644 packages/toolpad-core/src/layout/ToolbarActions.tsx create mode 100644 packages/toolpad-core/src/layout/index.ts diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index e1b7991a33e..b4669b9e7aa 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -7,7 +7,7 @@ import Popover from '@mui/material/Popover'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import LogoutIcon from '@mui/icons-material/Logout'; -import { SessionContext, AuthenticationContext } from '../AppProvider/AppProvider'; +import { SessionContext, AuthenticationContext } from '../shared/context'; import { SessionAvatar } from './SessionAvatar'; export interface AccountProps { diff --git a/packages/toolpad-core/src/AppProvider/AppProvider.tsx b/packages/toolpad-core/src/AppProvider/AppProvider.tsx index f600a19ead1..855e1b8a3aa 100644 --- a/packages/toolpad-core/src/AppProvider/AppProvider.tsx +++ b/packages/toolpad-core/src/AppProvider/AppProvider.tsx @@ -8,6 +8,8 @@ import { BrandingContext, NavigationContext, RouterContext, + AuthenticationContext, + SessionContext, WindowContext, } from '../shared/context'; import { AppThemeProvider } from './AppThemeProvider'; @@ -70,10 +72,6 @@ export interface Authentication { signOut: () => void; } -export const SessionContext = React.createContext(null); - -export const AuthenticationContext = React.createContext(null); - export type AppTheme = Theme | { light: Theme; dark: Theme }; export interface AppProviderProps { diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 7c5285b6fdc..e6e8e1689b0 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 { styled } from '@mui/material'; import MuiAppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; @@ -19,24 +19,20 @@ import Toolbar from '@mui/material/Toolbar'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import type {} from '@mui/material/themeCssVarsAugmentation'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import MenuIcon from '@mui/icons-material/Menu'; import MenuOpenIcon from '@mui/icons-material/MenuOpen'; -import useSsr from '@toolpad/utils/hooks/useSsr'; -import { Account } from '../Account'; import { Link } from '../shared/Link'; import { BrandingContext, NavigationContext, - PaletteModeContext, RouterContext, WindowContext, } from '../shared/context'; import type { Navigation } from '../AppProvider'; import { ToolpadLogo } from './ToolpadLogo'; +import { ToolbarActions } from '../layout/ToolbarActions'; import { getItemTitle, getPageItemFullPath, @@ -86,65 +82,6 @@ const NavigationListItemButton = styled(ListItemButton)(({ theme }) => ({ }, })); -function ThemeSwitcher() { - const isSsr = useSsr(); - const theme = useTheme(); - - const { paletteMode, setPaletteMode, isDualTheme } = React.useContext(PaletteModeContext); - - const toggleMode = React.useCallback(() => { - setPaletteMode(paletteMode === 'dark' ? 'light' : 'dark'); - }, [paletteMode, setPaletteMode]); - - return isDualTheme ? ( - -
- - {theme.getColorSchemeSelector ? ( - - - - - ) : ( - - {isSsr || paletteMode !== 'dark' ? : } - - )} - -
-
- ) : null; -} - interface DashboardSidebarSubNavigationProps { subNavigation: Navigation; basePath?: string; @@ -318,6 +255,20 @@ export interface DashboardLayoutProps { * The content of the dashboard. */ children: React.ReactNode; + /** + * Overridable components. + * @default {} + */ + slots?: { + /** + * Toolbar actions component rendered in the layout header. + * @default ToolbarActions + */ + toolbarActions?: React.JSXElementConstructor<{}>; + }; + slotProps?: { + toolbarActions?: {}; + }; } /** @@ -331,7 +282,7 @@ export interface DashboardLayoutProps { * - [DashboardLayout API](https://mui.com/toolpad/core/api/dashboard-layout) */ function DashboardLayout(props: DashboardLayoutProps) { - const { children } = props; + const { children, slots, slotProps } = props; const branding = React.useContext(BrandingContext); const navigation = React.useContext(NavigationContext); @@ -428,8 +379,11 @@ function DashboardLayout(props: DashboardLayoutProps) { - - + {slots?.toolbarActions ? ( + + ) : ( + + )} { + setPaletteMode(paletteMode === 'dark' ? 'light' : 'dark'); + }, [paletteMode, setPaletteMode]); + + return isDualTheme ? ( + +
+ + {theme.getColorSchemeSelector ? ( + + + + + ) : ( + + {isSsr || paletteMode !== 'dark' ? : } + + )} + +
+
+ ) : null; +} + +function ToolbarActions() { + return ( + + + + + ); +} + +export { ToolbarActions }; diff --git a/packages/toolpad-core/src/layout/index.ts b/packages/toolpad-core/src/layout/index.ts new file mode 100644 index 00000000000..b2d75da1d33 --- /dev/null +++ b/packages/toolpad-core/src/layout/index.ts @@ -0,0 +1 @@ +export * from './ToolbarActions'; diff --git a/packages/toolpad-core/src/shared/context.ts b/packages/toolpad-core/src/shared/context.ts index ea351b29113..f31bb21ca44 100644 --- a/packages/toolpad-core/src/shared/context.ts +++ b/packages/toolpad-core/src/shared/context.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import type { PaletteMode } from '@mui/material'; -import type { Branding, Navigation, Router } from '../AppProvider'; +import type { Branding, Navigation, Router, Authentication, Session } from '../AppProvider'; export const BrandingContext = React.createContext(null); @@ -18,6 +18,10 @@ export const PaletteModeContext = React.createContext<{ export const RouterContext = React.createContext(null); +export const AuthenticationContext = React.createContext(null); + +export const SessionContext = React.createContext(null); + export const WindowContext = React.createContext(undefined); export const DocsContext = React.createContext(false); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0151d0a460..69d9ef3d67b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2061,7 +2061,7 @@ packages: resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.24.8 + '@babel/core': ^7.0.0-0 '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} @@ -2134,7 +2134,7 @@ packages: '@docsearch/react@3.6.0': resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} peerDependencies: - '@types/react': 18.3.3 + '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' react-dom: '>= 16.8.0 < 19.0.0' search-insights: '>= 1 < 3' From 67ea17a4ce812a9d853f1cdb63876096bdc2c6ae Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:10:00 +0100 Subject: [PATCH 02/11] Isolate ThemeSwitcher component --- .../toolpad-core/src/layout/ThemeSwitcher.tsx | 70 +++++++++++++++++++ .../src/layout/ToolbarActions.tsx | 67 +----------------- packages/toolpad-core/src/layout/index.ts | 1 - 3 files changed, 71 insertions(+), 67 deletions(-) create mode 100644 packages/toolpad-core/src/layout/ThemeSwitcher.tsx delete mode 100644 packages/toolpad-core/src/layout/index.ts diff --git a/packages/toolpad-core/src/layout/ThemeSwitcher.tsx b/packages/toolpad-core/src/layout/ThemeSwitcher.tsx new file mode 100644 index 00000000000..4eb40d5e461 --- /dev/null +++ b/packages/toolpad-core/src/layout/ThemeSwitcher.tsx @@ -0,0 +1,70 @@ +'use client'; +import * as React from 'react'; +import { useTheme } from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +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'; + +function ThemeSwitcher() { + const isSsr = useSsr(); + const theme = useTheme(); + + const { paletteMode, setPaletteMode, isDualTheme } = React.useContext(PaletteModeContext); + + const toggleMode = React.useCallback(() => { + setPaletteMode(paletteMode === 'dark' ? 'light' : 'dark'); + }, [paletteMode, setPaletteMode]); + + return isDualTheme ? ( + +
+ + {theme.getColorSchemeSelector ? ( + + + + + ) : ( + + {isSsr || paletteMode !== 'dark' ? : } + + )} + +
+
+ ) : null; +} + +export { ThemeSwitcher }; diff --git a/packages/toolpad-core/src/layout/ToolbarActions.tsx b/packages/toolpad-core/src/layout/ToolbarActions.tsx index 5177cc45075..e32242faa9c 100644 --- a/packages/toolpad-core/src/layout/ToolbarActions.tsx +++ b/packages/toolpad-core/src/layout/ToolbarActions.tsx @@ -1,72 +1,7 @@ 'use client'; import * as React from 'react'; -import { useTheme } from '@mui/material'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import useSsr from '@toolpad/utils/hooks/useSsr'; import { Account } from '../Account'; -import { PaletteModeContext } from '../shared/context'; - -function ThemeSwitcher() { - const isSsr = useSsr(); - const theme = useTheme(); - - const { paletteMode, setPaletteMode, isDualTheme } = React.useContext(PaletteModeContext); - - const toggleMode = React.useCallback(() => { - setPaletteMode(paletteMode === 'dark' ? 'light' : 'dark'); - }, [paletteMode, setPaletteMode]); - - return isDualTheme ? ( - -
- - {theme.getColorSchemeSelector ? ( - - - - - ) : ( - - {isSsr || paletteMode !== 'dark' ? : } - - )} - -
-
- ) : null; -} +import { ThemeSwitcher } from './ThemeSwitcher'; function ToolbarActions() { return ( diff --git a/packages/toolpad-core/src/layout/index.ts b/packages/toolpad-core/src/layout/index.ts deleted file mode 100644 index b2d75da1d33..00000000000 --- a/packages/toolpad-core/src/layout/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ToolbarActions'; From f1f828a6707cb908914e64f5511dc3775b70a4e8 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:19:49 +0100 Subject: [PATCH 03/11] dedupes --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69d9ef3d67b..5c9ca0ad06b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,7 +191,7 @@ importers: version: 7.35.0(eslint@8.57.0) eslint-plugin-react-compiler: specifier: latest - version: 0.0.0-experimental-8e3b87c-20240821(eslint@8.57.0) + version: 0.0.0-experimental-8e3b87c-20240822(eslint@8.57.0) eslint-plugin-react-hooks: specifier: 4.6.2 version: 4.6.2(eslint@8.57.0) @@ -5883,8 +5883,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-react-compiler@0.0.0-experimental-8e3b87c-20240821: - resolution: {integrity: sha512-VSJDGcBGwI3UeMwAa+BLdVyTwyKfPtGvhSlcfrzjnQe0v+dx4Yo+hmVvPSZ42RsJpq58HIjkjSTY6TwLjpI0QQ==} + eslint-plugin-react-compiler@0.0.0-experimental-8e3b87c-20240822: + resolution: {integrity: sha512-H0FthLqMLg07n22ritJlLHo749J0z2uaRKu9PgHOhHnBe4DTkzH8kHHMRQUdLkdEE/s/YcDGMXdPyzzGiHhIUg==} engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} peerDependencies: eslint: '>=7' @@ -15739,7 +15739,7 @@ snapshots: globals: 13.24.0 rambda: 7.5.0 - eslint-plugin-react-compiler@0.0.0-experimental-8e3b87c-20240821(eslint@8.57.0): + eslint-plugin-react-compiler@0.0.0-experimental-8e3b87c-20240822(eslint@8.57.0): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.3 From 722c8372cff9e8f9f32722d9d9bfed048b1b01d9 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:46:36 +0100 Subject: [PATCH 04/11] Fix slots in docs API pages --- .../toolpad/core/api/dashboard-layout.json | 21 ++++++- docs/pages/toolpad/core/api/sign-in-page.json | 32 +++++++++++ .../dashboard-layout/dashboard-layout.json | 11 +++- .../api-docs/sign-in-page/sign-in-page.json | 9 ++- .../src/DashboardLayout/DashboardLayout.tsx | 40 ++++++++++--- .../ThemeSwitcher.tsx | 3 + .../src/DashboardLayout/ToolbarActions.tsx | 12 ++++ .../src/SignInPage/SignInPage.tsx | 56 ++++++++++--------- .../src/layout/ToolbarActions.tsx | 15 ----- 9 files changed, 144 insertions(+), 55 deletions(-) rename packages/toolpad-core/src/{layout => DashboardLayout}/ThemeSwitcher.tsx (98%) create mode 100644 packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx delete mode 100644 packages/toolpad-core/src/layout/ToolbarActions.tsx diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index 87b9dec9b3b..167bc716f9d 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -1,10 +1,29 @@ { - "props": { "children": { "type": { "name": "node" }, "required": true } }, + "props": { + "children": { "type": { "name": "node" }, "required": true }, + "slotProps": { + "type": { "name": "shape", "description": "{ toolbarActions?: object }" }, + "default": "{}" + }, + "slots": { + "type": { "name": "shape", "description": "{ toolbarActions?: elementType }" }, + "default": "{}", + "additionalInfo": { "slotsApi": true } + } + }, "name": "DashboardLayout", "imports": [ "import { DashboardLayout } from '@toolpad/core/DashboardLayout';", "import { DashboardLayout } from '@toolpad/core';" ], + "slots": [ + { + "name": "toolbarActions", + "description": "The toolbar actions component used in the layout header.", + "default": "ToolbarActions", + "class": null + } + ], "classes": [], "spread": true, "themeDefaultProps": null, diff --git a/docs/pages/toolpad/core/api/sign-in-page.json b/docs/pages/toolpad/core/api/sign-in-page.json index 4621baecf70..606d4cd0994 100644 --- a/docs/pages/toolpad/core/api/sign-in-page.json +++ b/docs/pages/toolpad/core/api/sign-in-page.json @@ -33,6 +33,38 @@ "import { SignInPage } from '@toolpad/core/SignInPage';", "import { SignInPage } from '@toolpad/core';" ], + "slots": [ + { + "name": "emailField", + "description": "The custom email field component used in the credentials form.", + "default": "TextField", + "class": null + }, + { + "name": "passwordField", + "description": "The custom password field component used in the credentials form.", + "default": "TextField", + "class": null + }, + { + "name": "submitButton", + "description": "The custom submit button component used in the credentials form.", + "default": "LoadingButton", + "class": null + }, + { + "name": "forgotPasswordLink", + "description": "The custom forgot password link component used in the credentials form.", + "default": "Link", + "class": null + }, + { + "name": "signUpLink", + "description": "The custom sign up link component used in the credentials form.", + "default": "Link", + "class": null + } + ], "classes": [], "spread": true, "themeDefaultProps": false, diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index 8e461ca2aaa..ed236fb19c2 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -1,5 +1,12 @@ { "componentDescription": "", - "propDescriptions": { "children": { "description": "The content of the dashboard." } }, - "classDescriptions": {} + "propDescriptions": { + "children": { "description": "The content of the dashboard." }, + "slotProps": { "description": "The components used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." } + }, + "classDescriptions": {}, + "slotDescriptions": { + "toolbarActions": "The toolbar actions component used in the layout header." + } } diff --git a/docs/translations/api-docs/sign-in-page/sign-in-page.json b/docs/translations/api-docs/sign-in-page/sign-in-page.json index d27f08ae393..066ea0ed30d 100644 --- a/docs/translations/api-docs/sign-in-page/sign-in-page.json +++ b/docs/translations/api-docs/sign-in-page/sign-in-page.json @@ -13,5 +13,12 @@ "slotProps": { "description": "The props used for each slot inside." }, "slots": { "description": "The components used for each slot inside." } }, - "classDescriptions": {} + "classDescriptions": {}, + "slotDescriptions": { + "emailField": "The custom email field component used in the credentials form.", + "forgotPasswordLink": "The custom forgot password link component used in the credentials form.", + "passwordField": "The custom password field component used in the credentials form.", + "signUpLink": "The custom sign up link component used in the credentials form.", + "submitButton": "The custom submit button component used in the credentials form." + } } diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index e6e8e1689b0..55ff756e737 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -31,8 +31,9 @@ import { WindowContext, } from '../shared/context'; import type { Navigation } from '../AppProvider'; +import { ThemeSwitcher } from './ThemeSwitcher'; +import { ToolbarActions } from './ToolbarActions'; import { ToolpadLogo } from './ToolpadLogo'; -import { ToolbarActions } from '../layout/ToolbarActions'; import { getItemTitle, getPageItemFullPath, @@ -250,22 +251,28 @@ function DashboardSidebarSubNavigation({ ); } +export interface DashboardLayoutSlots { + /** + * The toolbar actions component used in the layout header. + * @default ToolbarActions + */ + toolbarActions?: React.JSXElementConstructor<{}>; +} + export interface DashboardLayoutProps { /** * The content of the dashboard. */ children: React.ReactNode; /** - * Overridable components. + * The components used for each slot inside. + * @default {} + */ + slots?: DashboardLayoutSlots; + /** + * The components used for each slot inside. * @default {} */ - slots?: { - /** - * Toolbar actions component rendered in the layout header. - * @default ToolbarActions - */ - toolbarActions?: React.JSXElementConstructor<{}>; - }; slotProps?: { toolbarActions?: {}; }; @@ -379,6 +386,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
+ {slots?.toolbarActions ? ( ) : ( @@ -447,6 +455,20 @@ DashboardLayout.propTypes /* remove-proptypes */ = { * The content of the dashboard. */ children: PropTypes.node, + /** + * The components used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + toolbarActions: PropTypes.object, + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + toolbarActions: PropTypes.elementType, + }), } as any; export { DashboardLayout }; diff --git a/packages/toolpad-core/src/layout/ThemeSwitcher.tsx b/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx similarity index 98% rename from packages/toolpad-core/src/layout/ThemeSwitcher.tsx rename to packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx index 4eb40d5e461..a0233dfbbae 100644 --- a/packages/toolpad-core/src/layout/ThemeSwitcher.tsx +++ b/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx @@ -8,6 +8,9 @@ import LightModeIcon from '@mui/icons-material/LightMode'; import useSsr from '@toolpad/utils/hooks/useSsr'; import { PaletteModeContext } from '../shared/context'; +/** + * @ignore - internal component. + */ function ThemeSwitcher() { const isSsr = useSsr(); const theme = useTheme(); diff --git a/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx b/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx new file mode 100644 index 00000000000..e93fc1dca3f --- /dev/null +++ b/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx @@ -0,0 +1,12 @@ +'use client'; +import * as React from 'react'; +import { Account } from '../Account'; + +/** + * @ignore - internal component. + */ +function ToolbarActions() { + return ; +} + +export { ToolbarActions }; diff --git a/packages/toolpad-core/src/SignInPage/SignInPage.tsx b/packages/toolpad-core/src/SignInPage/SignInPage.tsx index 718d6dc59b8..564f9989dda 100644 --- a/packages/toolpad-core/src/SignInPage/SignInPage.tsx +++ b/packages/toolpad-core/src/SignInPage/SignInPage.tsx @@ -79,6 +79,34 @@ export interface AuthResponse { type?: string; } +export interface SignInPageSlots { + /** + * The custom email field component used in the credentials form. + * @default TextField + */ + emailField?: React.JSXElementConstructor; + /** + * The custom password field component used in the credentials form. + * @default TextField + */ + passwordField?: React.JSXElementConstructor; + /** + * The custom submit button component used in the credentials form. + * @default LoadingButton + */ + submitButton?: React.JSXElementConstructor; + /** + * The custom forgot password link component used in the credentials form. + * @default Link + */ + forgotPasswordLink?: React.JSXElementConstructor; + /** + * The custom sign up link component used in the credentials form. + * @default Link + */ + signUpLink?: React.JSXElementConstructor; +} + export interface SignInPageProps { /** * The list of authentication providers to display. @@ -104,33 +132,7 @@ export interface SignInPageProps { * @example { forgotPasswordLink: Forgot password? } * @example { signUpLink: Sign up } */ - slots?: { - /** - * The custom email field component used in the credentials form. - * @default TextField - */ - emailField?: React.JSXElementConstructor; - /** - * The custom password field component used in the credentials form. - * @default TextField - */ - passwordField?: React.JSXElementConstructor; - /** - * The custom submit button component used in the credentials form. - * @default LoadingButton - */ - submitButton?: React.JSXElementConstructor; - /** - * The custom forgot password link component used in the credentials form. - * @default Link - */ - forgotPasswordLink?: React.JSXElementConstructor; - /** - * The custom sign up link component used in the credentials form. - * @default Link - */ - signUpLink?: React.JSXElementConstructor; - }; + slots?: SignInPageSlots; /** * The props used for each slot inside. * @default {} diff --git a/packages/toolpad-core/src/layout/ToolbarActions.tsx b/packages/toolpad-core/src/layout/ToolbarActions.tsx deleted file mode 100644 index e32242faa9c..00000000000 --- a/packages/toolpad-core/src/layout/ToolbarActions.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Account } from '../Account'; -import { ThemeSwitcher } from './ThemeSwitcher'; - -function ToolbarActions() { - return ( - - - - - ); -} - -export { ToolbarActions }; From 4160227749d0a1f055e97f5768db20898c0ad0fc Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:00:49 +0100 Subject: [PATCH 05/11] Export default slots components --- packages/toolpad-core/src/DashboardLayout/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/toolpad-core/src/DashboardLayout/index.ts b/packages/toolpad-core/src/DashboardLayout/index.ts index fc36c152c7f..8040efa4be9 100644 --- a/packages/toolpad-core/src/DashboardLayout/index.ts +++ b/packages/toolpad-core/src/DashboardLayout/index.ts @@ -1 +1,3 @@ export * from './DashboardLayout'; + +export * from './ToolbarActions'; From 11588ce7b95de122ebeb66a60bc96981b3914194 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:01:29 +0100 Subject: [PATCH 06/11] Add comment --- packages/toolpad-core/src/DashboardLayout/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolpad-core/src/DashboardLayout/index.ts b/packages/toolpad-core/src/DashboardLayout/index.ts index 8040efa4be9..f65e521b923 100644 --- a/packages/toolpad-core/src/DashboardLayout/index.ts +++ b/packages/toolpad-core/src/DashboardLayout/index.ts @@ -1,3 +1,4 @@ export * from './DashboardLayout'; +// Default slots components export * from './ToolbarActions'; From b2ea5c22768f2ad6cebe35f22d016a1f69ce567b Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:09:05 +0100 Subject: [PATCH 07/11] ugh --- packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 55ff756e737..56a7c9aac7e 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -270,7 +270,7 @@ export interface DashboardLayoutProps { */ slots?: DashboardLayoutSlots; /** - * The components used for each slot inside. + * The props used for each slot inside. * @default {} */ slotProps?: { From 91a04a999e4a8b76a218bd2c2b52b943572388c3 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:22:05 +0100 Subject: [PATCH 08/11] oh come on --- .../api-docs/dashboard-layout/dashboard-layout.json | 2 +- packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index ed236fb19c2..8113e35f5e0 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -2,7 +2,7 @@ "componentDescription": "", "propDescriptions": { "children": { "description": "The content of the dashboard." }, - "slotProps": { "description": "The components used for each slot inside." }, + "slotProps": { "description": "The props used for each slot inside." }, "slots": { "description": "The components used for each slot inside." } }, "classDescriptions": {}, diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 56a7c9aac7e..2a2e1a0990b 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -456,7 +456,7 @@ DashboardLayout.propTypes /* remove-proptypes */ = { */ children: PropTypes.node, /** - * The components used for each slot inside. + * The props used for each slot inside. * @default {} */ slotProps: PropTypes.shape({ From 2829cef144255ed0bed93d0e25ee93dd3ce15697 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:10:36 +0100 Subject: [PATCH 09/11] revert changes to context --- packages/toolpad-core/src/Account/Account.tsx | 2 +- packages/toolpad-core/src/AppProvider/AppProvider.tsx | 6 ++++-- packages/toolpad-core/src/shared/context.ts | 6 +----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index b4669b9e7aa..e1b7991a33e 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -7,7 +7,7 @@ import Popover from '@mui/material/Popover'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import LogoutIcon from '@mui/icons-material/Logout'; -import { SessionContext, AuthenticationContext } from '../shared/context'; +import { SessionContext, AuthenticationContext } from '../AppProvider/AppProvider'; import { SessionAvatar } from './SessionAvatar'; export interface AccountProps { diff --git a/packages/toolpad-core/src/AppProvider/AppProvider.tsx b/packages/toolpad-core/src/AppProvider/AppProvider.tsx index 855e1b8a3aa..d1732a3211a 100644 --- a/packages/toolpad-core/src/AppProvider/AppProvider.tsx +++ b/packages/toolpad-core/src/AppProvider/AppProvider.tsx @@ -8,8 +8,6 @@ import { BrandingContext, NavigationContext, RouterContext, - AuthenticationContext, - SessionContext, WindowContext, } from '../shared/context'; import { AppThemeProvider } from './AppThemeProvider'; @@ -72,6 +70,10 @@ export interface Authentication { signOut: () => void; } +export const AuthenticationContext = React.createContext(null); + +export const SessionContext = React.createContext(null); + export type AppTheme = Theme | { light: Theme; dark: Theme }; export interface AppProviderProps { diff --git a/packages/toolpad-core/src/shared/context.ts b/packages/toolpad-core/src/shared/context.ts index f31bb21ca44..ea351b29113 100644 --- a/packages/toolpad-core/src/shared/context.ts +++ b/packages/toolpad-core/src/shared/context.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import type { PaletteMode } from '@mui/material'; -import type { Branding, Navigation, Router, Authentication, Session } from '../AppProvider'; +import type { Branding, Navigation, Router } from '../AppProvider'; export const BrandingContext = React.createContext(null); @@ -18,10 +18,6 @@ export const PaletteModeContext = React.createContext<{ export const RouterContext = React.createContext(null); -export const AuthenticationContext = React.createContext(null); - -export const SessionContext = React.createContext(null); - export const WindowContext = React.createContext(undefined); export const DocsContext = React.createContext(false); From 4d0bc462983b8ee846a789f301972c6b716f8c9e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:13:46 +0100 Subject: [PATCH 10/11] Add search bar demo, make it 2 new slots instead of 1 --- .../dashboard-layout/DashboardLayoutSlots.js | 127 ++++++++++++++++++ .../dashboard-layout/DashboardLayoutSlots.tsx | 120 +++++++++++++++++ .../DashboardLayoutSlots.tsx.preview | 3 + .../dashboard-layout/dashboard-layout.md | 7 + .../toolpad/core/api/dashboard-layout.json | 18 ++- .../dashboard-layout/dashboard-layout.json | 3 +- .../src/DashboardLayout/DashboardLayout.tsx | 36 +++-- .../src/DashboardLayout/ToolbarActions.tsx | 4 +- 8 files changed, 302 insertions(+), 16 deletions(-) create mode 100644 docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js create mode 100644 docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx create mode 100644 docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js new file mode 100644 index 00000000000..e6f1418c873 --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.js @@ -0,0 +1,127 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import { createTheme } from '@mui/material/styles'; +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'; + +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 SearchBar() { + return ( + + + + ), + sx: { pr: 0.5 }, + }, + }} + sx={{ mr: 1 }} + /> + ); +} + +function DashboardLayoutSlots(props) { + 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 ( + + + + + + ); +} + +DashboardLayoutSlots.propTypes = { + /** + * Injected by the documentation to work in an iframe. + * Remove this when copying and pasting into your project. + */ + window: PropTypes.func, +}; + +export default DashboardLayoutSlots; diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx new file mode 100644 index 00000000000..a237c269ad9 --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import { createTheme } from '@mui/material/styles'; +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'; + +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 SearchBar() { + return ( + + + + ), + sx: { pr: 0.5 }, + }, + }} + sx={{ mr: 1 }} + /> + ); +} + +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 DashboardLayoutSlots(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 ( + + + + + + ); +} diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview new file mode 100644 index 00000000000..a504a4b624d --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutSlots.tsx.preview @@ -0,0 +1,3 @@ + + + \ 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 13eb5a5746b..3b20296f08b 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md +++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md @@ -91,3 +91,10 @@ The use of an `iframe` may cause some spacing issues in the following demo. ::: {{"demo": "DashboardLayoutAccount.js", "height": 400, "iframe": true}} + +## Customization + +Some areas of the layout can be replaced with custom components by using the `slots` and `slotProps` props. +For example, this allows you to add new items to the toolbar in the header, such as a search bar. + +{{"demo": "DashboardLayoutSlots.js", "height": 400, "iframe": true}} diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index 167bc716f9d..b643f8833eb 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -2,11 +2,17 @@ "props": { "children": { "type": { "name": "node" }, "required": true }, "slotProps": { - "type": { "name": "shape", "description": "{ toolbarActions?: object }" }, + "type": { + "name": "shape", + "description": "{ toolbarAccount?: { signInLabel?: string, signOutLabel?: string, slotProps?: { avatar?: object, iconButton?: object, signInButton?: object, signOutButton?: object } }, toolbarActions?: object }" + }, "default": "{}" }, "slots": { - "type": { "name": "shape", "description": "{ toolbarActions?: elementType }" }, + "type": { + "name": "shape", + "description": "{ toolbarAccount?: elementType, toolbarActions?: elementType }" + }, "default": "{}", "additionalInfo": { "slotsApi": true } } @@ -19,9 +25,15 @@ "slots": [ { "name": "toolbarActions", - "description": "The toolbar actions component used in the layout header.", + "description": "The toolbar account component used in the layout header.", "default": "ToolbarActions", "class": null + }, + { + "name": "toolbarAccount", + "description": "The toolbar account component used in the layout header.", + "default": "Account", + "class": null } ], "classes": [], diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index 8113e35f5e0..973d0f3e221 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -7,6 +7,7 @@ }, "classDescriptions": {}, "slotDescriptions": { - "toolbarActions": "The toolbar actions component used in the layout header." + "toolbarAccount": "The toolbar account component used in the layout header.", + "toolbarActions": "The toolbar account component used in the layout header." } } diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 2a2e1a0990b..4223b92d9ae 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -31,15 +31,16 @@ import { WindowContext, } from '../shared/context'; import type { Navigation } from '../AppProvider'; -import { ThemeSwitcher } from './ThemeSwitcher'; -import { ToolbarActions } from './ToolbarActions'; -import { ToolpadLogo } from './ToolpadLogo'; +import { Account, type AccountProps } from '../Account'; import { getItemTitle, getPageItemFullPath, hasSelectedNavigationChildren, } from '../shared/navigation'; import { useApplicationTitle } from '../shared/branding'; +import { ToolbarActions } from './ToolbarActions'; +import { ThemeSwitcher } from './ThemeSwitcher'; +import { ToolpadLogo } from './ToolpadLogo'; const DRAWER_WIDTH = 320; // px @@ -253,10 +254,15 @@ function DashboardSidebarSubNavigation({ export interface DashboardLayoutSlots { /** - * The toolbar actions component used in the layout header. + * The toolbar account component used in the layout header. * @default ToolbarActions */ toolbarActions?: React.JSXElementConstructor<{}>; + /** + * The toolbar account component used in the layout header. + * @default Account + */ + toolbarAccount?: React.JSXElementConstructor; } export interface DashboardLayoutProps { @@ -275,6 +281,7 @@ export interface DashboardLayoutProps { */ slotProps?: { toolbarActions?: {}; + toolbarAccount?: AccountProps; }; } @@ -337,6 +344,9 @@ function DashboardLayout(props: DashboardLayoutProps) { ); + const ToolbarActionsSlot = slots?.toolbarActions ?? ToolbarActions; + const ToolbarAccountSlot = slots?.toolbarAccount ?? Account; + return ( @@ -386,12 +396,9 @@ function DashboardLayout(props: DashboardLayoutProps) { + - {slots?.toolbarActions ? ( - - ) : ( - - )} + ; + return null; } export { ToolbarActions }; From 58825f42a05b0e55dd25eeba29a1229d620e1987 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira <10789765+apedroferreira@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:51:39 +0100 Subject: [PATCH 11/11] Fix typo --- docs/pages/toolpad/core/api/dashboard-layout.json | 2 +- .../api-docs/dashboard-layout/dashboard-layout.json | 2 +- packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index b643f8833eb..c1d6e51523b 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -25,7 +25,7 @@ "slots": [ { "name": "toolbarActions", - "description": "The toolbar account component used in the layout header.", + "description": "The toolbar actions component used in the layout header.", "default": "ToolbarActions", "class": null }, diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json index 973d0f3e221..369522018ed 100644 --- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json +++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json @@ -8,6 +8,6 @@ "classDescriptions": {}, "slotDescriptions": { "toolbarAccount": "The toolbar account component used in the layout header.", - "toolbarActions": "The toolbar account component used in the layout header." + "toolbarActions": "The toolbar actions component used in the layout header." } } diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index 4223b92d9ae..4c8ecb9f184 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -254,7 +254,7 @@ function DashboardSidebarSubNavigation({ export interface DashboardLayoutSlots { /** - * The toolbar account component used in the layout header. + * The toolbar actions component used in the layout header. * @default ToolbarActions */ toolbarActions?: React.JSXElementConstructor<{}>;