Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DashboardLayout] Support collapsed mini-drawer by default #4234

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
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 BarChartIcon from '@mui/icons-material/BarChart';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';

const NAVIGATION = [
{
segment: 'dashboard',
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
{
segment: 'reports',
title: 'Reports',
icon: <BarChartIcon />,
},
];

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 (
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<Typography>Dashboard content for {pathname}</Typography>
</Box>
);
}

DemoPageContent.propTypes = {
pathname: PropTypes.string.isRequired,
};

function DashboardLayoutSidebarCollapsed(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 (
<AppProvider
navigation={NAVIGATION}
router={router}
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout defaultSidebarCollapsed>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
);
}

DashboardLayoutSidebarCollapsed.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* Remove this when copying and pasting into your project.
*/
window: PropTypes.func,
};

export default DashboardLayoutSidebarCollapsed;
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as React from 'react';
import Box from '@mui/material/Box';
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 BarChartIcon from '@mui/icons-material/BarChart';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import type { Router, Navigation } from '@toolpad/core';

const NAVIGATION: Navigation = [
{
segment: 'dashboard',
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
{
segment: 'reports',
title: 'Reports',
icon: <BarChartIcon />,
},
];

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 (
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<Typography>Dashboard content for {pathname}</Typography>
</Box>
);
}

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 DashboardLayoutSidebarCollapsed(props: DemoProps) {
const { window } = props;

const [pathname, setPathname] = React.useState('/dashboard');

const router = React.useMemo<Router>(() => {
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 (
<AppProvider
navigation={NAVIGATION}
router={router}
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout defaultSidebarCollapsed>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<DashboardLayout defaultSidebarCollapsed>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { createTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { createTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import type { Router } from '@toolpad/core';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,24 @@ The layout sidebar is collapsible to a mini-drawer (with icons only) in desktop

{{"demo": "DashboardLayoutNoMiniSidebar.js", "height": 400, "iframe": true}}

### Full-size content
### Start with mini-drawer on desktop

The layout content can take up the full available area with styles such as `flex: 1` or `height: 100%`.
The layout sidebar can default to a collapsed mini-drawer on desktop viewport sizes with the `defaultSidebarCollapsed` prop.

{{"demo": "DashboardLayoutFullScreen.js", "height": 400, "iframe": true}}
{{"demo": "DashboardLayoutSidebarCollapsed.js", "height": 400, "iframe": true}}

### Hide navigation

The layout sidebar can be hidden if needed with the `hideNavigation` prop.

{{"demo": "DashboardLayoutSidebarHidden.js", "height": 400, "iframe": true}}

### Full-size content

The layout content can take up the full available area with styles such as `flex: 1` or `height: 100%`.

{{"demo": "DashboardLayoutFullScreen.js", "height": 400, "iframe": true}}

## Account

The `DashboardLayout` comes integrated with the [`<Account />`](/toolpad/core/react-account/) component. It renders as an account management menu when a user is signed in – a `session` object is present – and a button when not.
Expand Down
1 change: 1 addition & 0 deletions docs/pages/toolpad/core/api/dashboard-layout.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"props": {
"children": { "type": { "name": "node" }, "required": true },
"defaultSidebarCollapsed": { "type": { "name": "bool" }, "default": "false" },
"disableCollapsibleSidebar": { "type": { "name": "bool" }, "default": "false" },
"hideNavigation": { "type": { "name": "bool" }, "default": "false" },
"slotProps": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"componentDescription": "",
"propDescriptions": {
"children": { "description": "The content of the dashboard." },
"defaultSidebarCollapsed": {
"description": "Whether the sidebar should start collapsed in desktop size screens."
},
"disableCollapsibleSidebar": {
"description": "Whether the sidebar should not be collapsible to a mini variant in desktop and tablet viewports."
},
Expand Down
27 changes: 25 additions & 2 deletions packages/toolpad-core/src/DashboardLayout/DashboardLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,34 @@ describe('DashboardLayout', () => {
const desktopNavigation = screen.queryByRole('navigation', { name: 'Desktop' });
const navigationToggle = screen.queryByLabelText('Collapse menu');

// Expect that the navigation and toggle button are not rendered
// Expect that navigation and menu button are not rendered
expect(desktopNavigation).toBeNull();
expect(navigationToggle).toBeNull();

// Ensure the main content is still rendered
// Ensure that main content is still rendered
expect(screen.getByText('Hello world')).toBeTruthy();
});

test('renders without default collapsed navigation on desktop', async () => {
const NAVIGATION: Navigation = [
{
title: 'Dashboard',
segment: 'dashboard',
icon: <DashboardIcon />,
},
];

render(
<AppProvider navigation={NAVIGATION}>
<DashboardLayout defaultSidebarCollapsed>Hello world</DashboardLayout>
</AppProvider>,
);

// Expect that menu button has expand action
expect(screen.getAllByLabelText('Expand menu')).toBeTruthy();
expect(screen.queryByLabelText('Collapse menu')).toBeNull();

// Ensure that main content is still rendered
expect(screen.getByText('Hello world')).toBeTruthy();
});
});
14 changes: 13 additions & 1 deletion packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@ export interface DashboardLayoutProps {
* @default false
*/
disableCollapsibleSidebar?: boolean;
/**
* Whether the sidebar should start collapsed in desktop size screens.
* @default false
*/
defaultSidebarCollapsed?: boolean;
/**
* Whether the navigation bar and menu icon should be hidden
* @default false
Expand Down Expand Up @@ -396,6 +401,7 @@ function DashboardLayout(props: DashboardLayoutProps) {
const {
children,
disableCollapsibleSidebar = false,
defaultSidebarCollapsed = false,
hideNavigation = false,
slots,
slotProps,
Expand All @@ -409,7 +415,8 @@ function DashboardLayout(props: DashboardLayoutProps) {
const appWindow = React.useContext(WindowContext);
const applicationTitle = useApplicationTitle();

const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] = React.useState(true);
const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] =
React.useState(!defaultSidebarCollapsed);
const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] = React.useState(false);

const isUnderMdViewport = useMediaQuery(
Expand Down Expand Up @@ -729,6 +736,11 @@ DashboardLayout.propTypes /* remove-proptypes */ = {
* The content of the dashboard.
*/
children: PropTypes.node,
/**
* Whether the sidebar should start collapsed in desktop size screens.
* @default false
*/
defaultSidebarCollapsed: PropTypes.bool,
/**
* Whether the sidebar should not be collapsible to a mini variant in desktop and tablet viewports.
* @default false
Expand Down
Loading