Skip to content

Commit

Permalink
[DashboardLayout] Add sidebarFooter slot (#4236)
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira authored Oct 11, 2024
1 parent 4e95f60 commit 48502b4
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ function Search() {
);
}

function SidebarFooter({ mini }) {
return (
<Typography
variant="caption"
sx={{ m: 1, whiteSpace: 'nowrap', overflow: 'hidden' }}
>
{mini ? '© MUI' : ${new Date().getFullYear()} Made with love by MUI`}
</Typography>
);
}

SidebarFooter.propTypes = {
mini: PropTypes.bool.isRequired,
};

function DashboardLayoutSlots(props) {
const { window } = props;

Expand All @@ -124,7 +139,9 @@ function DashboardLayoutSlots(props) {
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout slots={{ toolbarActions: Search }}>
<DashboardLayout
slots={{ toolbarActions: Search, sidebarFooter: SidebarFooter }}
>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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 type { Navigation, Router } from '@toolpad/core';
import type { Navigation, Router, SidebarFooterProps } from '@toolpad/core';

const NAVIGATION: Navigation = [
{
Expand Down Expand Up @@ -97,6 +97,17 @@ function Search() {
);
}

function SidebarFooter({ mini }: SidebarFooterProps) {
return (
<Typography
variant="caption"
sx={{ m: 1, whiteSpace: 'nowrap', overflow: 'hidden' }}
>
{mini ? '© MUI' : ${new Date().getFullYear()} Made with love by MUI`}
</Typography>
);
}

interface DemoProps {
/**
* Injected by the documentation to work in an iframe.
Expand Down Expand Up @@ -128,7 +139,9 @@ export default function DashboardLayoutSlots(props: DemoProps) {
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout slots={{ toolbarActions: Search }}>
<DashboardLayout
slots={{ toolbarActions: Search, sidebarFooter: SidebarFooter }}
>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<DashboardLayout slots={{ toolbarActions: Search }}>
<DashboardLayout
slots={{ toolbarActions: Search, sidebarFooter: SidebarFooter }}
>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ 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.
For example, this allows you to add new items to the toolbar in the header, such as a search bar or a button.
This allows you to add, for example:

- new items to the toolbar in the header, such as a search bar or button;
- footer content in the sidebar.

{{"demo": "DashboardLayoutSlots.js", "height": 400, "iframe": true}}
10 changes: 8 additions & 2 deletions docs/pages/toolpad/core/api/dashboard-layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"slotProps": {
"type": {
"name": "shape",
"description": "{ toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
"description": "{ sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
"description": "{ toolbarAccount?: elementType, toolbarActions?: elementType }"
"description": "{ sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }"
},
"default": "{}",
"additionalInfo": { "slotsApi": true }
Expand Down Expand Up @@ -43,6 +43,12 @@
"description": "The toolbar account component used in the layout header.",
"default": "Account",
"class": null
},
{
"name": "sidebarFooter",
"description": "Optional footer component used in the layout sidebar.",
"default": "null",
"class": null
}
],
"classes": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"classDescriptions": {},
"slotDescriptions": {
"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."
}
Expand Down
16 changes: 16 additions & 0 deletions packages/toolpad-core/src/DashboardLayout/DashboardLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,22 @@ describe('DashboardLayout', () => {
expect(within(desktopNavigation).getByText('Action 2')).toBeTruthy();
});

test('renders sidebar footer slot content', async () => {
function SidebarFooter() {
return <div>I am footer</div>;
}

render(
<AppProvider>
<DashboardLayout slots={{ sidebarFooter: SidebarFooter }}>Hello world</DashboardLayout>
</AppProvider>,
);

const desktopNavigation = screen.getByRole('navigation', { name: 'Desktop' });

expect(within(desktopNavigation).getByText('I am footer')).toBeTruthy();
});

test('renders without the navigation and toggle button', async () => {
const NAVIGATION: Navigation = [
{
Expand Down
72 changes: 46 additions & 26 deletions packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ const AppBar = styled(MuiAppBar)(({ theme }) => ({
borderStyle: 'solid',
borderColor: (theme.vars ?? theme).palette.divider,
boxShadow: 'none',
// TODO: Temporary fix to issue reported in https://github.com/mui/material-ui/issues/43244
left: 0,
zIndex: theme.zIndex.drawer + 1,
}));

Expand Down Expand Up @@ -179,6 +177,7 @@ function DashboardSidebarSubNavigation({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
zIndex: 2,
}}
>
{getItemTitle(navigationItem)}
Expand Down Expand Up @@ -324,6 +323,16 @@ function DashboardSidebarSubNavigation({
);
}

export interface SidebarFooterProps {
mini: boolean;
}

export interface DashboardLayoutSlotProps {
toolbarActions?: {};
toolbarAccount?: AccountProps;
sidebarFooter?: SidebarFooterProps;
}

export interface DashboardLayoutSlots {
/**
* The toolbar actions component used in the layout header.
Expand All @@ -335,6 +344,11 @@ export interface DashboardLayoutSlots {
* @default Account
*/
toolbarAccount?: React.JSXElementConstructor<AccountProps>;
/**
* Optional footer component used in the layout sidebar.
* @default null
*/
sidebarFooter?: React.JSXElementConstructor<SidebarFooterProps>;
}

export interface DashboardLayoutProps {
Expand All @@ -361,10 +375,7 @@ export interface DashboardLayoutProps {
* The props used for each slot inside.
* @default {}
*/
slotProps?: {
toolbarActions?: {};
toolbarAccount?: AccountProps;
};
slotProps?: DashboardLayoutSlotProps;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down Expand Up @@ -501,14 +512,22 @@ function DashboardLayout(props: DashboardLayoutProps) {
const hasDrawerTransitions =
isOverSmViewport && (disableCollapsibleSidebar || !isUnderMdViewport);

const ToolbarActionsSlot = slots?.toolbarActions ?? ToolbarActions;
const ToolbarAccountSlot = slots?.toolbarAccount ?? Account;
const SidebarFooterSlot = slots?.sidebarFooter ?? null;

const getDrawerContent = React.useCallback(
(isMini: boolean, ariaLabel: string) => (
(isMini: boolean, viewport: 'phone' | 'tablet' | 'desktop') => (
<React.Fragment>
<Toolbar />
<Box
component="nav"
aria-label={ariaLabel}
aria-label={`${viewport.charAt(0).toUpperCase()}${viewport.slice(1)}`}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
overflow: 'auto',
pt: navigation[0]?.kind === 'header' && !isMini ? 0 : 2,
...(hasDrawerTransitions
Expand All @@ -524,10 +543,20 @@ function DashboardLayout(props: DashboardLayoutProps) {
hasDrawerTransitions={hasDrawerTransitions}
selectedItemId={selectedItemIdRef.current}
/>
{SidebarFooterSlot ? (
<SidebarFooterSlot mini={isMini} {...slotProps?.sidebarFooter} />
) : null}
</Box>
</React.Fragment>
),
[handleNavigationLinkClick, hasDrawerTransitions, isNavigationFullyExpanded, navigation],
[
SidebarFooterSlot,
handleNavigationLinkClick,
hasDrawerTransitions,
isNavigationFullyExpanded,
navigation,
slotProps?.sidebarFooter,
],
);

const getDrawerSharedSx = React.useCallback(
Expand All @@ -551,9 +580,6 @@ function DashboardLayout(props: DashboardLayoutProps) {
[isNavigationExpanded],
);

const ToolbarActionsSlot = slots?.toolbarActions ?? ToolbarActions;
const ToolbarAccountSlot = slots?.toolbarAccount ?? Account;

const layoutRef = React.useRef<Element | null>(null);

return (
Expand All @@ -569,12 +595,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
}}
>
<AppBar color="inherit" position="absolute">
{
// TODO: (minWidth: 100vw) Temporary fix to issue reported in https://github.com/mui/material-ui/issues/43244
}
<Toolbar
sx={{ backgroundColor: 'inherit', minWidth: '100vw', mx: { xs: -0.75, sm: -1.5 } }}
>
<Toolbar sx={{ backgroundColor: 'inherit', mx: { xs: -0.75, sm: -1.5 } }}>
{!hideNavigation ? (
<React.Fragment>
<Box
Expand Down Expand Up @@ -648,7 +669,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
...getDrawerSharedSx(false, true),
}}
>
{getDrawerContent(false, 'Phone')}
{getDrawerContent(false, 'phone')}
</Drawer>
<Drawer
variant="permanent"
Expand All @@ -661,7 +682,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
...getDrawerSharedSx(isMobileMini, false),
}}
>
{getDrawerContent(isMobileMini, 'Tablet')}
{getDrawerContent(isMobileMini, 'tablet')}
</Drawer>
<Drawer
variant="permanent"
Expand All @@ -670,7 +691,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
...getDrawerSharedSx(isDesktopMini, false),
}}
>
{getDrawerContent(isDesktopMini, 'Desktop')}
{getDrawerContent(isDesktopMini, 'desktop')}
</Drawer>
</React.Fragment>
) : null}
Expand All @@ -680,11 +701,6 @@ function DashboardLayout(props: DashboardLayoutProps) {
display: 'flex',
flexDirection: 'column',
flex: 1,
// TODO: Temporary fix to issue reported in https://github.com/mui/material-ui/issues/43244
minWidth: {
xs: disableCollapsibleSidebar && isNavigationExpanded ? '100vw' : 'auto',
md: 'auto',
},
}}
>
<Toolbar />
Expand Down Expand Up @@ -728,6 +744,9 @@ DashboardLayout.propTypes /* remove-proptypes */ = {
* @default {}
*/
slotProps: PropTypes.shape({
sidebarFooter: PropTypes.shape({
mini: PropTypes.bool.isRequired,
}),
toolbarAccount: PropTypes.shape({
localeText: PropTypes.shape({
signInLabel: PropTypes.string.isRequired,
Expand All @@ -751,6 +770,7 @@ DashboardLayout.propTypes /* remove-proptypes */ = {
* @default {}
*/
slots: PropTypes.shape({
sidebarFooter: PropTypes.elementType,
toolbarAccount: PropTypes.elementType,
toolbarActions: PropTypes.elementType,
}),
Expand Down

0 comments on commit 48502b4

Please sign in to comment.