-
-
Notifications
You must be signed in to change notification settings - Fork 310
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
add prop sidebarCollapsed and onToggleSidebar #4516
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
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'; | ||
import { Button } from '@mui/material'; | ||
|
||
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, toggleSidebar }) { | ||
return ( | ||
<Box | ||
sx={{ | ||
py: 4, | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
textAlign: 'center', | ||
}} | ||
> | ||
<Typography>Dashboard content for {pathname}</Typography> | ||
<Button onClick={() => toggleSidebar()}>Toggle Sidebar</Button> | ||
</Box> | ||
); | ||
} | ||
|
||
DemoPageContent.propTypes = { | ||
pathname: PropTypes.string.isRequired, | ||
toggleSidebar: PropTypes.func.isRequired, | ||
}; | ||
|
||
function DashboardLayoutSidebarCollapsedProp(props) { | ||
const { window } = props; | ||
|
||
const [pathname, setPathname] = React.useState('/dashboard'); | ||
const [navigationMenuOpen, toggleSidebar] = React.useState(true); | ||
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 navigationMenuOpen={navigationMenuOpen}> | ||
<DemoPageContent | ||
pathname={pathname} | ||
toggleSidebar={() => toggleSidebar(!navigationMenuOpen)} | ||
/> | ||
</DashboardLayout> | ||
</AppProvider> | ||
); | ||
} | ||
|
||
DashboardLayoutSidebarCollapsedProp.propTypes = { | ||
/** | ||
* Injected by the documentation to work in an iframe. | ||
* Remove this when copying and pasting into your project. | ||
*/ | ||
window: PropTypes.func, | ||
}; | ||
|
||
export default DashboardLayoutSidebarCollapsedProp; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
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, | ||
type Router, | ||
type Navigation, | ||
} from '@toolpad/core/AppProvider'; | ||
import { DashboardLayout } from '@toolpad/core/DashboardLayout'; | ||
import { Button } from '@mui/material'; | ||
|
||
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, | ||
toggleSidebar, | ||
}: { | ||
pathname: string; | ||
toggleSidebar: () => void; | ||
}) { | ||
return ( | ||
<Box | ||
sx={{ | ||
py: 4, | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
textAlign: 'center', | ||
}} | ||
> | ||
<Typography>Dashboard content for {pathname}</Typography> | ||
<Button onClick={() => toggleSidebar()}>Toggle Sidebar</Button> | ||
</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 DashboardLayoutSidebarCollapsedProp(props: DemoProps) { | ||
const { window } = props; | ||
|
||
const [pathname, setPathname] = React.useState('/dashboard'); | ||
const [navigationMenuOpen, toggleSidebar] = React.useState<boolean>(true); | ||
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 navigationMenuOpen={navigationMenuOpen}> | ||
<DemoPageContent | ||
pathname={pathname} | ||
toggleSidebar={() => toggleSidebar(!navigationMenuOpen)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also show the typical usage for the new callbacks in this example, which would change the |
||
/> | ||
</DashboardLayout> | ||
</AppProvider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<DashboardLayout navigationMenuOpen={navigationMenuOpen}> | ||
<DemoPageContent | ||
pathname={pathname} | ||
toggleSidebar={() => toggleSidebar(!navigationMenuOpen)} | ||
/> | ||
</DashboardLayout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,6 +126,12 @@ The layout sidebar can be hidden if needed with the `hideNavigation` prop. | |
|
||
{{"demo": "DashboardLayoutSidebarHidden.js", "height": 400, "iframe": true}} | ||
|
||
### Toggle sidebar | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section needs to be a bit more complete as in referring to the whole concept of using "controlled state" for the navigation menu, as well as what kind of use case that could serve. We also need to document the new callbacks, and how they could be used together with Anyway, I can take care of the documentation part when the functionality is done unless you really want to tackle it! |
||
|
||
The sidebar can be toggled if needed with the `navigationMenuOpen` prop. | ||
|
||
{{"demo": "DashboardLayoutSidebarCollapsedProp.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%`. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -410,4 +410,62 @@ describe('DashboardLayout', () => { | |
// Ensure that main content is still rendered | ||
expect(screen.getByText('Hello world')).toBeTruthy(); | ||
}); | ||
|
||
test('renders the sidebar in collapsed state when navigationMenuOpen is false', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks a lot for adding tests, they look good! |
||
render( | ||
<DashboardLayout navigationMenuOpen={false}> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
// Expect that menu button has expand action | ||
expect(screen.getAllByLabelText('Expand menu')).toBeTruthy(); | ||
expect(screen.queryByLabelText('Collapse menu')).toBeNull(); | ||
}); | ||
|
||
test('renders the sidebar in expanded state when navigationMenuOpen is true', () => { | ||
render( | ||
<DashboardLayout navigationMenuOpen> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
expect(screen.getAllByLabelText('Collapse menu')).toBeTruthy(); | ||
}); | ||
|
||
test('calls onNavigationMenuOpen callback when navigationMenuOpen state changes to open', () => { | ||
const mockToggleSidebar = vi.fn(); | ||
const { rerender } = render( | ||
<DashboardLayout navigationMenuOpen={false} onNavigationMenuClose={mockToggleSidebar}> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
// Trigger sidebar open action | ||
rerender( | ||
<DashboardLayout navigationMenuOpen onNavigationMenuClose={mockToggleSidebar}> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
expect(mockToggleSidebar).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
test('calls onNavigationMenuClose callback when navigationMenuOpen state changes to close', () => { | ||
const mockToggleSidebar = vi.fn(); | ||
const { rerender } = render( | ||
<DashboardLayout navigationMenuOpen onNavigationMenuClose={mockToggleSidebar}> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
// Trigger sidebar close action | ||
rerender( | ||
<DashboardLayout navigationMenuOpen={false} onNavigationMenuClose={mockToggleSidebar}> | ||
<div>Test Content</div> | ||
</DashboardLayout>, | ||
); | ||
|
||
expect(mockToggleSidebar).toHaveBeenCalledOnce(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.