diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js
new file mode 100644
index 00000000000..91d4e58d0bc
--- /dev/null
+++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.js
@@ -0,0 +1,295 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import ListItemText from '@mui/material/ListItemText';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import Avatar from '@mui/material/Avatar';
+import Divider from '@mui/material/Divider';
+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 {
+ Account,
+ AccountPreview,
+ AccountPopoverFooter,
+ SignOutButton,
+} from '@toolpad/core/Account';
+
+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 AccountSidebarPreview(props) {
+ const { handleClick, open, mini } = props;
+ return (
+
+
+
+
+ );
+}
+
+AccountSidebarPreview.propTypes = {
+ /**
+ * The handler used when the preview is expanded
+ */
+ handleClick: PropTypes.func,
+ mini: PropTypes.bool.isRequired,
+ /**
+ * The state of the Account popover
+ * @default false
+ */
+ open: PropTypes.bool,
+};
+
+const accounts = [
+ {
+ id: 1,
+ name: 'Bharat Kashyap',
+ email: 'bharatkashyap@outlook.com',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ projects: [
+ {
+ id: 3,
+ title: 'Project X',
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Bharat MUI',
+ email: 'bharat@mui.com',
+ color: '#8B4513', // Brown color
+ projects: [{ id: 4, title: 'Project A' }],
+ },
+];
+
+function SidebarFooterAccountPopover() {
+ return (
+
+
+ Accounts
+
+
+ {accounts.map((account) => (
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+const createPreviewComponent = (mini) => {
+ function PreviewComponent(props) {
+ return ;
+ }
+ return PreviewComponent;
+};
+
+function SidebarFooterAccount({ mini }) {
+ const PreviewComponent = React.useMemo(() => createPreviewComponent(mini), [mini]);
+ return (
+
+ `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
+ mt: 1,
+ '&::before': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ bottom: 10,
+ left: 0,
+ width: 10,
+ height: 10,
+ bgcolor: 'background.paper',
+ transform: 'translate(-50%, -50%) rotate(45deg)',
+ zIndex: 0,
+ },
+ },
+ },
+ },
+ },
+ }}
+ />
+ );
+}
+
+SidebarFooterAccount.propTypes = {
+ mini: PropTypes.bool.isRequired,
+};
+
+const demoSession = {
+ user: {
+ name: 'Bharat Kashyap',
+ email: 'bharatkashyap@outlook.com',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ },
+};
+
+function DashboardLayoutAccountSidebar(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;
+
+ const [session, setSession] = React.useState(demoSession);
+ const authentication = React.useMemo(() => {
+ return {
+ signIn: () => {
+ setSession(demoSession);
+ },
+ signOut: () => {
+ setSession(null);
+ },
+ };
+ }, []);
+
+ return (
+
+ null, sidebarFooter: SidebarFooterAccount }}
+ >
+
+
+
+ );
+}
+
+DashboardLayoutAccountSidebar.propTypes = {
+ /**
+ * Injected by the documentation to work in an iframe.
+ * Remove this when copying and pasting into your project.
+ */
+ window: PropTypes.func,
+};
+
+export default DashboardLayoutAccountSidebar;
diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx
new file mode 100644
index 00000000000..d88c885b3a4
--- /dev/null
+++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx
@@ -0,0 +1,272 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import ListItemText from '@mui/material/ListItemText';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import Avatar from '@mui/material/Avatar';
+import Divider from '@mui/material/Divider';
+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 {
+ Account,
+ AccountPreview,
+ AccountPopoverFooter,
+ SignOutButton,
+ AccountPreviewProps,
+} from '@toolpad/core/Account';
+import type { Navigation, Router, Session } from '@toolpad/core/AppProvider';
+
+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 AccountSidebarPreview(props: AccountPreviewProps & { mini: boolean }) {
+ const { handleClick, open, mini } = props;
+ return (
+
+
+
+
+ );
+}
+
+const accounts = [
+ {
+ id: 1,
+ name: 'Bharat Kashyap',
+ email: 'bharatkashyap@outlook.com',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ projects: [
+ {
+ id: 3,
+ title: 'Project X',
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Bharat MUI',
+ email: 'bharat@mui.com',
+ color: '#8B4513', // Brown color
+ projects: [{ id: 4, title: 'Project A' }],
+ },
+];
+
+function SidebarFooterAccountPopover() {
+ return (
+
+
+ Accounts
+
+
+ {accounts.map((account) => (
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+const createPreviewComponent = (mini: boolean) => {
+ function PreviewComponent(props: AccountPreviewProps) {
+ return ;
+ }
+ return PreviewComponent;
+};
+
+function SidebarFooterAccount({ mini }: SidebarFooterProps) {
+ const PreviewComponent = React.useMemo(() => createPreviewComponent(mini), [mini]);
+ return (
+
+ `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
+ mt: 1,
+ '&::before': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ bottom: 10,
+ left: 0,
+ width: 10,
+ height: 10,
+ bgcolor: 'background.paper',
+ transform: 'translate(-50%, -50%) rotate(45deg)',
+ zIndex: 0,
+ },
+ },
+ },
+ },
+ },
+ }}
+ />
+ );
+}
+
+interface DemoProps {
+ /**
+ * Injected by the documentation to work in an iframe.
+ * Remove this when copying and pasting into your project.
+ */
+ window?: () => Window;
+}
+
+const demoSession = {
+ user: {
+ name: 'Bharat Kashyap',
+ email: 'bharatkashyap@outlook.com',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ },
+};
+
+export default function DashboardLayoutAccountSidebar(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;
+
+ const [session, setSession] = React.useState(demoSession);
+ const authentication = React.useMemo(() => {
+ return {
+ signIn: () => {
+ setSession(demoSession);
+ },
+ signOut: () => {
+ setSession(null);
+ },
+ };
+ }, []);
+
+ return (
+
+ null, 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
new file mode 100644
index 00000000000..f567d5793e9
--- /dev/null
+++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutAccountSidebar.tsx.preview
@@ -0,0 +1,5 @@
+ null, sidebarFooter: SidebarFooterAccount }}
+>
+
+
\ 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 92196943efb..91572f19ed6 100644
--- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
+++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md
@@ -146,3 +146,7 @@ This allows you to add, for example:
- 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:
+
+{{"demo": "DashboardLayoutAccountSidebar.js", "height": 400, "iframe": true}}
diff --git a/docs/pages/toolpad/core/api/account-preview.json b/docs/pages/toolpad/core/api/account-preview.json
index 8608aa72476..bca8fcd7acb 100644
--- a/docs/pages/toolpad/core/api/account-preview.json
+++ b/docs/pages/toolpad/core/api/account-preview.json
@@ -9,7 +9,10 @@
}
},
"slots": {
- "type": { "name": "shape", "description": "{ avatar?: elementType }" },
+ "type": {
+ "name": "shape",
+ "description": "{ avatar?: elementType, avatarIconButton?: elementType, moreIconButton?: elementType }"
+ },
"additionalInfo": { "slotsApi": true }
},
"variant": {
diff --git a/packages/toolpad-core/src/Account/Account.test.tsx b/packages/toolpad-core/src/Account/Account.test.tsx
index ab7df9e0b0a..a94e4e4c4d2 100644
--- a/packages/toolpad-core/src/Account/Account.test.tsx
+++ b/packages/toolpad-core/src/Account/Account.test.tsx
@@ -51,7 +51,7 @@ describe('AppProvider', () => {
await userEvent.click(userButton);
- expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('John Doe', { selector: 'p' })).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
const signOutButton = screen.getByRole('button', { name: 'Sign Out' });
diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx
index 0c6a105f5e1..c4b0876ad8a 100644
--- a/packages/toolpad-core/src/Account/Account.tsx
+++ b/packages/toolpad-core/src/Account/Account.tsx
@@ -1,9 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
-import Button from '@mui/material/Button';
-import Popover from '@mui/material/Popover';
+import Button, { ButtonProps } from '@mui/material/Button';
+import Popover, { PopoverProps } from '@mui/material/Popover';
import Divider from '@mui/material/Divider';
-import Stack from '@mui/material/Stack';
+import Stack, { StackProps } from '@mui/material/Stack';
import { SignInButton } from './SignInButton';
import { SignOutButton } from './SignOutButton';
import { AccountPreview, AccountPreviewProps } from './AccountPreview';
@@ -17,27 +17,27 @@ export interface AccountSlots {
* The component used for the account preview
* @default AccountPreview
*/
- preview?: React.ElementType;
+ preview?: React.JSXElementConstructor;
/**
* The component used for the account popover menu
* @default Popover
*/
- popover?: React.ElementType;
+ popover?: React.JSXElementConstructor;
/**
* The component used for the content of account popover
* @default Stack
*/
- popoverContent?: React.ElementType;
+ popoverContent?: React.JSXElementConstructor;
/**
* The component used for the sign in button.
* @default Button
*/
- signInButton?: React.ElementType;
+ signInButton?: React.JSXElementConstructor;
/**
* The component used for the sign out button.
* @default Button
*/
- signOutButton?: React.ElementType;
+ signOutButton?: React.JSXElementConstructor;
}
export interface AccountProps {
@@ -50,7 +50,7 @@ export interface AccountProps {
*/
slotProps?: {
preview?: AccountPreviewProps;
- popover?: React.ComponentProps;
+ popover?: Omit, 'open'>;
popoverContent?: React.ComponentProps;
signInButton?: React.ComponentProps;
signOutButton?: React.ComponentProps;
@@ -118,7 +118,12 @@ function Account(props: AccountProps) {
/>
)}
{slots?.popover ? (
-
+
) : (
+ `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
mt: 1,
'&::before': {
content: '""',
diff --git a/packages/toolpad-core/src/Account/AccountPreview.tsx b/packages/toolpad-core/src/Account/AccountPreview.tsx
index dc4455269c5..a165e771e40 100644
--- a/packages/toolpad-core/src/Account/AccountPreview.tsx
+++ b/packages/toolpad-core/src/Account/AccountPreview.tsx
@@ -93,15 +93,17 @@ function AccountPreview(props: AccountPreviewProps) {
if (variant === 'expanded') {
return (
-
- {avatarContent}
-
-
- {session.user?.name}
-
-
- {session.user?.email}
-
+
+
+ {avatarContent}
+
+
+ {session.user?.name}
+
+
+ {session.user?.email}
+
+
{handleClick &&
(slots?.moreIconButton ? (
@@ -111,7 +113,7 @@ function AccountPreview(props: AccountPreviewProps) {
size="small"
onClick={handleClick}
{...slotProps?.moreIconButton}
- sx={{ alignSelf: 'flex-start', ...slotProps?.moreIconButton?.sx }}
+ sx={{ alignSelf: 'center', ...slotProps?.moreIconButton?.sx }}
>
@@ -129,6 +131,7 @@ function AccountPreview(props: AccountPreviewProps) {
onClick={handleClick}
aria-label={localeText.iconButtonAriaLabel || 'Current User'}
size="small"
+ sx={{ width: 'fit-content', margin: '0 auto' }}
aria-controls={open ? 'account-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
@@ -168,6 +171,8 @@ AccountPreview.propTypes /* remove-proptypes */ = {
*/
slots: PropTypes.shape({
avatar: PropTypes.elementType,
+ avatarIconButton: PropTypes.elementType,
+ moreIconButton: PropTypes.elementType,
}),
/**
* The type of account details to display.
diff --git a/playground/nextjs/src/app/(dashboard)/SidebarFooterAccount.tsx b/playground/nextjs/src/app/(dashboard)/SidebarFooterAccount.tsx
deleted file mode 100644
index aceb55524f4..00000000000
--- a/playground/nextjs/src/app/(dashboard)/SidebarFooterAccount.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-'use client';
-
-import * as React from 'react';
-
-import Typography from '@mui/material/Typography';
-import Stack from '@mui/material/Stack';
-import MenuList from '@mui/material/MenuList';
-import MenuItem from '@mui/material/MenuItem';
-import ListItemText from '@mui/material/ListItemText';
-import ListItemIcon from '@mui/material/ListItemIcon';
-import Avatar from '@mui/material/Avatar';
-import Divider from '@mui/material/Divider';
-
-import {
- AccountPreview,
- AccountPopoverFooter,
- SignOutButton,
- AccountPreviewProps,
-} from '@toolpad/core/Account';
-
-export function AccountSidebarPreview(props: AccountPreviewProps) {
- const { handleClick, open } = props;
- return (
-
-
-
-
- );
-}
-
-const accounts = [
- {
- id: 1,
- name: 'Bharat Kashyap',
- email: 'bharatkashyap@outlook.com',
- image: 'https://avatars.githubusercontent.com/u/19550456',
- projects: [
- {
- id: 3,
- title: 'Project X',
- },
- ],
- },
- {
- id: 2,
- name: 'Bharat MUI',
- email: 'bharat@mui.com',
- color: '#8B4513', // Brown color
- projects: [{ id: 4, title: 'Project A' }],
- },
-];
-
-export function SidebarFooterAccountPopover() {
- return (
-
-
- Accounts
-
-
- {accounts.map((account) => (
-
- ))}
-
-
-
-
-
- );
-}
diff --git a/playground/nextjs/src/app/(dashboard)/layout.tsx b/playground/nextjs/src/app/(dashboard)/layout.tsx
index e481f628f1c..e4bb596b791 100644
--- a/playground/nextjs/src/app/(dashboard)/layout.tsx
+++ b/playground/nextjs/src/app/(dashboard)/layout.tsx
@@ -1,10 +1,164 @@
+'use client';
import * as React from 'react';
-import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import MenuList from '@mui/material/MenuList';
+import MenuItem from '@mui/material/MenuItem';
+import ListItemText from '@mui/material/ListItemText';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import Avatar from '@mui/material/Avatar';
+import Divider from '@mui/material/Divider';
+import {
+ Account,
+ AccountPreview,
+ AccountPopoverFooter,
+ SignOutButton,
+ AccountPreviewProps,
+} from '@toolpad/core/Account';
+import { DashboardLayout, SidebarFooterProps } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
+const accounts = [
+ {
+ id: 1,
+ name: 'Bharat Kashyap',
+ email: 'bharatkashyap@outlook.com',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ projects: [
+ {
+ id: 3,
+ title: 'Project X',
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Bharat MUI',
+ email: 'bharat@mui.com',
+ color: '#8B4513', // Brown color
+ projects: [{ id: 4, title: 'Project A' }],
+ },
+];
+
+function AccountSidebarPreview(props: AccountPreviewProps & { mini: boolean }) {
+ const { handleClick, open, mini } = props;
+ return (
+
+
+
+
+ );
+}
+
+function SidebarFooterAccountPopover() {
+ return (
+
+
+ Accounts
+
+
+ {accounts.map((account) => (
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+const createPreviewComponent = (mini: boolean) => {
+ function PreviewComponent(props: AccountPreviewProps) {
+ return ;
+ }
+ return PreviewComponent;
+};
+
+function SidebarFooterAccount({ mini }: SidebarFooterProps) {
+ const PreviewComponent = React.useMemo(() => createPreviewComponent(mini), [mini]);
+ return (
+
+ `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
+ mt: 1,
+ '&::before': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ bottom: 10,
+ left: 0,
+ width: 10,
+ height: 10,
+ bgcolor: 'background.paper',
+ transform: 'translate(-50%, -50%) rotate(45deg)',
+ zIndex: 0,
+ },
+ },
+ },
+ },
+ },
+ }}
+ />
+ );
+}
+
export default function DashboardPagesLayout(props: { children: React.ReactNode }) {
return (
-
+ null }}>
{props.children}
);