I am new to React and Material UI and I've been trying really hard to connect Mini Variant Drawer that I have now with Responsive drawer, so when you use it on phone it changes to Responsive drawer. If anyone could help me it would mean a lot to me, I tried but I always failed.
It would also help me get the knowledge for further connections with drawers.
Here is my code that I use for Mini Variant Drawer:
import React, {useState} from 'react';
import clsx from 'clsx';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import Drawer from '#material-ui/core/Drawer';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import List from '#material-ui/core/List';
import Typography from '#material-ui/core/Typography';
import Divider from '#material-ui/core/Divider';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import MenuOpenIcon from '#material-ui/icons/MenuOpen';
import { MainListItems } from './listItems';
import AccountMenu from '../../components/AccountMenu'
import ChangePasswordDialog from './ChangePasswordDialog';
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
toolbar: {
paddingRight: 60,
},
toolbarIcon: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
menuButtonHidden: {
display: 'none',
},
mainTitle: {
flexGrow: 1,
marginLeft: 12
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing.unit * 7,
[theme.breakpoints.up('sm')]: {
width: theme.spacing.unit * 9,
},
},
appBarSpacer: theme.mixins.toolbar,
content: {
flexGrow: 1,
height: '100vh',
overflow: 'auto',
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
},
paper: {
padding: theme.spacing(2),
display: 'flex',
overflow: 'auto',
flexDirection: 'column',
},
fixedHeight: {
height: 240,
},
}));
export default function SideMenuLayout({
drawerOpen,
setDrawerOpen,
children,
title
}) {
const classes = useStyles();
const handleDrawerOpen = () => {
setDrawerOpen(true);
};
const handleDrawerClose = () => {
setDrawerOpen(false);
};
const [changePasswordDialogOpen, setChangePasswordDialogOpen] = useState(false)
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="absolute" className={clsx(classes.appBar, drawerOpen && classes.appBarShift)}>
<Toolbar className={classes.toolbar}>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
className={clsx(classes.menuButton, drawerOpen && classes.menuButtonHidden)}
>
<MenuIcon />
</IconButton>
<Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
{drawerOpen ? title : `MyTitle - ${title}`}
</Typography>
<AccountMenu onChangePassword={() => setChangePasswordDialogOpen(true)} />
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
classes={{
paper: clsx(classes.drawerPaper, !drawerOpen && classes.drawerPaperClose),
}}
open={drawerOpen}
>
<div className={classes.toolbarIcon}>
<Typography component="h1" variant="h6" color="inherit" noWrap className={classes.mainTitle}>
MyTitle
</Typography>
<IconButton onClick={handleDrawerClose}>
<MenuOpenIcon />
</IconButton>
</div>
<Divider />
<List><MainListItems /></List>
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
{children}
</main>
<ChangePasswordDialog
isOpen={changePasswordDialogOpen}
setOpen={setChangePasswordDialogOpen}
/>
</div>
);
}
I too have stumbled upon some issue trying to mix together the two drawer variants, but after a couple of hours I've found a configuration that seems to work properly.
NOTE: I'm absolutely no expert in JS/CSS/HTML, I'm way more a backend guy, so this solution can certainly be optimized and modified to fit your needs.
In my case the drawer is used to select the "page" to display. So I'll start from there.
The "page" component simply contains a Router. Its contents are put inside a Box with display: flex:
const HomePage = () => {
return (
<Router>
<Box sx={{display: 'flex'}}>
<HomePageInner />
<Switch>
<Route path="/">
<p>Example</p>
</Route>
</Switch>
</Box>
</Router>
);
}
The HomePageInner is the component that renders the "menu" and appbar.
It keeps tracks of the open/closed status:
const drawerWidth = 240;
const HomePageInner = () => {
const [open, setOpen] = useState(true);
const toggleDrawer = () => setOpen(!open);
const drawer = <DrawerContents onClick={toggleDrawer} />;
return (
<>
<MainAppBar open={open} onClick={toggleDrawer}/>
<Box
component="nav"
aria-label="menu items"
>
<MobileDrawer open={open} onClose={toggleDrawer} drawer={drawer}/>
<DesktopDrawer open={open} drawer={drawer}/>
</Box>
</>
);
}
Here you can see that for clarity I have defined two distinct components for "mobile" and for "desktop". The contents of the drawer are shared and are defined in a separate DrawerContents component that is passed as a child.
There is an outer Box that wraps both Drawers. In the examples on this Box there is an sx property that specifies width: {sm: drawerWidth}, flexShrink: {sm :0}, the flexShrink should be removed, but trying to add/remove the width setting does not seem to do anything, but if something is broken for you try to set that property.
The DrawerContents is pretty simple. It places the "chevron" icon button on the top that can be used to toggle the state and shows the links:
const DrawerContents: FunctionComponent<{ onClick: () => void }> = (props) => {
return <>
<Toolbar
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
px: [1],
}}
>
<IconButton onClick={props.onClick}>
<ChevronLeftIcon/>
</IconButton>
</Toolbar>
<Divider/>
<List>
<ListItemLink primary="First" to="/first" icon={<DashboardIcon/>}/>
</List>
<List>
<ListItemLink primary="Second" to="/second" icon={<TodayIcon/>}/>
</List>
<Divider/>
<List>
<ListItemLink primary="Third" to="/third" icon={<SettingsIcon/>}/>
</List>
</>;
}
Now for the interesting part! The Drawers and the AppBar. So, the MobileDrawer is pretty much exactly the same as the temporary drawer in the "Responsive Drawer" example:
const MobileDrawer: FunctionComponent<{ container?: (() => any), open: boolean, onClose: () => void }> = (props) => {
return <Drawer
variant="temporary"
container={props.container}
open={props.open}
onClose={props.onClose}
ModalProps={{
keepMounted: true,
}}
sx={{
display: {xs: "block", sm: "none"},
"& .MuiDrawer-paper": {boxSizing: "border-box", width: drawerWidth},
}}
>
{props.children}
</Drawer>;
}
For the DesktopDrawer is mostly the same of the permanent drawer in th Responsive Drawer example however you have to modify the styling in the sx property so that it changes width when the status changes:
const DesktopDrawer: FunctionComponent<{ open: boolean }> = (props) => {
return <Drawer
variant="permanent"
sx={{
display: {xs: "none", sm: "block"},
"& .MuiDrawer-paper": {
position: "relative",
whiteSpace: "nowrap",
width: drawerWidth,
transition: theme => theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
boxSizing: "border-box",
...(!props.open && {
overflowX: "hidden",
transition: theme => theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme => ({xs: theme.spacing(7), sm: theme.spacing(9)}),
})
},
}}
open={props.open}
>
{props.children}
</Drawer>;
}
Note that here I made ample use of the function form of the sx properties that gives you access to the theme. I derived the correct values by "mergin" the values I saw in the Mini-Variant example and the Responsive example.
And now the MainAppBar. This too is quite similar to the Responsive Drawer example, however again we have to modify the sx property:
const MainAppBar: FunctionComponent<{ open: boolean, onClick: () => void }> = (props) => {
const location = useLocation();
const headers: { [key: string]: string } = {
'/first': "First",
'/second': "Second",
'/third': "Third",
};
return <AppBar
position="fixed"
sx={{
zIndex: theme => theme.zIndex.drawer + 1,
transition: theme => theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(props.open && {
ml: {sm: `${drawerWidth}px`},
width: {sm: `calc(100% - ${drawerWidth}px)`},
transition: theme => theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
})
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={props.onClick}
sx={{mr: 2}}
>
<MenuIcon/>
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
sx={{flexGrow: 1}}
>
{headers[location.pathname] || "Error"}
</Typography>
</Toolbar>
</AppBar>;
}
Note that here you want to:
remove the display: { sm: 'none' } in the sx of the IconButton to toggle the state
The width and ml settings must be included only in the open state
With all this configuration I get a drawer that for larger screen can be opened/closed like the mini variant:
But is also responsive:
PS: I didn't have the time to write a short answer, so I wrote a long one by copy&pasting my code and cutting away some stuff. If I find some time I might modify the sample code to be shorter and/or be self-contained.
Related
To make this layout I am using the MUI Dashboard example.
https://github.com/mui/material-ui/blob/v5.11.7/docs/data/material/getting-started/templates/dashboard/Dashboard.js
I'm not using styled as engine, but styled-components.
const MainLayout = () => {
const themeAux = useTheme();
const [open, setOpen] = useState(false);
const toggleDrawer = () => setOpen(!open);
const DrawerContent = () => (
<Fragment>
<Toolbar
sx={{
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
px: [1],
}}
>
<IconButton onClick={toggleDrawer}>
<ChevronLeft />
</IconButton>
</Toolbar>
<Divider />
<List component={'nav'}>
<ListItemButton component={'a'} href={''}>
<ListItemIcon>
<Drafts />
</ListItemIcon>
<ListItemText primary={'Main'} />
</ListItemButton>
</List>
</Fragment>
);
const TopBar = () => (
<AppBar
position={'absolute'} // fixed
sx={{
zIndex: (theme) => theme.zIndex.drawer + 1,
transition: (theme) =>
theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
ml: { sm: drawerWidth },
width: { sm: `calc(100% - ${drawerWidth}px)` },
transition: (theme) =>
theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}}
>
<Toolbar>
<IconButton
sx={{ mr: 2, ...(open && { display: { sm: 'none' } }) }}
edge={'start'}
onClick={toggleDrawer}
>
<Menu />
</IconButton>
<Typography component={'h4'} variant={'h4'} noWrap sx={{ flexGrow: 1 }}>
HEADERS
</Typography>
</Toolbar>
</AppBar>
);
const LargeDrawer = () => (
<Drawer
variant={'permanent'}
open={open}
sx={{
'& .MuiDrawer-paper': {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: (theme) =>
theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
boxSizing: 'border-box',
...(!open && {
overflowX: 'hidden',
transition: (theme) =>
theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: (theme) => ({ xs: theme.spacing(7), sm: theme.spacing(9) }),
}),
},
}}
>
<DrawerContent />
</Drawer>
);
return (
<Box
sx={{
display: 'flex',
}}
>
<TopBar />
<LargeDrawer />
<Box
component={'main'}
sx={{
flexGrow: 1,
height: '100vh',
overflow: 'auto',
}}
>
<Toolbar />
<Container maxWidth={'lg'} sx={{ my: 4 }}>
<Outlet />
</Container>
</Box>
</Box>
);
};
export default MainLayout;
What am I doing wrong, that causes the transitions not to work when opening and closing the drawer?
I'm developing nextjs app. For a start I downloaded this default dashboard theme from MUI website https://mui.com/material-ui/getting-started/templates/dashboard/ code: https://github.com/mui/material-ui/blob/v5.10.13/docs/data/material/getting-started/templates/dashboard/Dashboard.tsx
However, when I try to implement localStorage that will store "open" drawer state like in the snippet bellow it bugs- on refresh it's always in the default state, even if the value in localStorage is set to false (when trying to log the stored value, it is correct). When I try to click the button after the refresh with false value nothing happens because it sets the value to true, only next click takes an effect. Any idea what may cause that bug? According to error: Warning: Prop className did not match. Server: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-1johsky-MuiButtonBase-root-MuiIconButton-root" Client: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-dhvns5-MuiButtonBase-root-MuiIconButton-root"
My code:
import * as React from 'react';
import { styled, createTheme, ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import MuiDrawer from '#mui/material/Drawer';
import Box from '#mui/material/Box';
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import List from '#mui/material/List';
import Typography from '#mui/material/Typography';
import Divider from '#mui/material/Divider';
import IconButton from '#mui/material/IconButton';
import Badge from '#mui/material/Badge';
import Container from '#mui/material/Container';
import Grid from '#mui/material/Grid';
import Paper from '#mui/material/Paper';
import Link from '#mui/material/Link';
import MenuIcon from '#mui/icons-material/Menu';
import ChevronLeftIcon from '#mui/icons-material/ChevronLeft';
import NotificationsIcon from '#mui/icons-material/Notifications';
import theme from "../theme";
import { firstListMenu, secondaryListMenu } from './MenuList';
import {useEffect, useState} from "react";
const drawerWidth: number = 200;
interface AppBarProps extends MuiAppBarProps {
open?: boolean;
}
type Props = {
children?: JSX.Element;
}
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})<AppBarProps>(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
'& .MuiDrawer-paper': {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
boxSizing: 'border-box',
...(!open && {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing(7),
[theme.breakpoints.up('sm')]: {
width: theme.spacing(9),
},
}),
},
}),
);
const mdTheme = theme;
export default function Layout({children}: Props) {
const [open, setOpen] = useState(() => {
try {
const value = window.localStorage.getItem('open');
if (value) {
return JSON.parse(value);
} else {
window.localStorage.setItem('open', JSON.stringify(true));
return true;
}
} catch (err) {
return true;
}
});
const toggleDrawer = () => {
try {
window.localStorage.setItem('open', JSON.stringify(!open));
} catch (err) {
console.log(err)
}
setOpen(!open);
};
useEffect(() => {
console.log(open)
})
return (
<ThemeProvider theme={mdTheme}>
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="absolute" open={open}>
<Toolbar
sx={{
pr: '24px', // keep right padding when drawer closed
}}
>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={toggleDrawer}
sx={{
marginRight: '36px',
...(open && { display: 'none' }),
}}
>
<MenuIcon />
</IconButton>
<Typography
component="h1"
variant="h6"
color="inherit"
noWrap
sx={{ flexGrow: 1 }}
>
Dashboard
</Typography>
<IconButton color="inherit">
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
</Badge>
</IconButton>
</Toolbar>
</AppBar>
<Drawer variant="permanent" open={open}>
<Toolbar
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
px: [1],
}}
>
<IconButton onClick={toggleDrawer}>
<ChevronLeftIcon />
</IconButton>
</Toolbar>
<Divider />
<List component="nav">
{firstListMenu}
<Divider sx={{ my: 1 }} />
{secondaryListMenu}
</List>
</Drawer>
<Box
component="main"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === 'light'
? theme.palette.grey[100]
: theme.palette.grey[900],
flexGrow: 1,
height: '100vh',
overflow: 'auto',
}}
>
<Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Grid container spacing={3}>
{/* Chart */}
<Grid item xs={12} md={8} lg={9}>
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
height: 240,
}}
>
{children}
</Paper>
</Grid>
{/* Recent Deposits */}
<Grid item xs={12} md={4} lg={3}>
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
height: 240,
}}
>
</Paper>
</Grid>
{/* Recent Orders */}
<Grid item xs={12}>
<Paper sx={{ p: 2, display: 'flex', flexDirection: 'column' }}>
</Paper>
</Grid>
</Grid>
</Container>
</Box>
</Box>
</ThemeProvider>
);
}
The problem occured because it is rendered on server side. I had to change localStorage to cookies and use getServerSideProps.
I wish to remove all the styles from my main component.
All the JS style of the material UI is on the same file and it is starting to get long code.
I wish to create a new file that contains all the styles on other file and on Navbar.js I will just call the components, just same as CSS file that I call to class
import styled from "#emotion/styled";
import { Notifications, Pets } from "#mui/icons-material";
import {
AppBar,
Avatar,
Badge,
Box,
InputBase,
Menu,
MenuItem,
Toolbar,
Typography,
} from "#mui/material";
import MailIcon from "#mui/icons-material/Mail";
import React, { useState } from "react";
const StyledToolbar = styled(Toolbar)({
display: "flex",
justifyContent: "space-between",
});
const Search = styled("div")(({ theme }) => ({
backgroundColor: "white",
padding: "0 10px",
borderRadius: theme.shape.borderRadius,
width: "40%",
}));
const Icons = styled(Box)(({ theme }) => ({
display: "none",
gap: "20px",
alignItems: "center",
[theme.breakpoints.up("sm")]: {
display: "flex",
},
}));
const UserBox = styled(Box)(({ theme }) => ({
display: "flex",
gap: "10px",
alignItems: "center",
[theme.breakpoints.up("sm")]: {
display: "none",
},
}));
const Navbar = () => {
const [open, setOpen] = useState(false);
return (
<AppBar position="sticky">
<StyledToolbar>
<Typography variant="h6" sx={{ display: { xs: "none", sm: "block" } }}>
PALSAM
</Typography>
<Pets sx={{ display: { xs: "block", sm: "none" } }} />
<Search>
<InputBase placeholder="search..." />
</Search>
<Icons>
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
<Badge badgeContent={4} color="error">
<Notifications />
</Badge>
<Avatar
sx={{ width: 30, height: 30 }}
src="https://images.pexels.com/photos/846741/pexels-photo-846741.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
onClick={(e) => setOpen(true)}
/>
</Icons>
<UserBox onClick={(e) => setOpen(true)}>
<Avatar
sx={{ width: 30, height: 30 }}
src="https://images.pexels.com/photos/846741/pexels-photo-846741.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
/>
<Typography variant="span">Jhon</Typography>
</UserBox>
</StyledToolbar>
<Menu
id="demo-positioned-menu"
aria-labelledby="demo-positioned-button"
open={open}
onClose={(e) => setOpen(false)}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem>Logout</MenuItem>
</Menu>
</AppBar>
);
};
export default Navbar;
You can create a new js file and add export before each const then you can import them from that file
export const StyledToolbar = styled(Toolbar)({
display: "flex",
justifyContent: "space-between",
});
This is my first post, so let me know if I have done anything wrong. I have searched for an answer, and have not been able to find anything on this site or anywhere else. I have started playing around with React to make an intranet application at work. My previous knowledge is mostly .Net and I was only an amateur at that.
For my application I am leveraging the NPM Material UI modules. I am trying to get the Drawer to always be below the header bar and the header bar to move all the way across the screen. Currently, the position of the drawer blocks out part of the header part. My current App.js is as follows
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { withStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import Drawer from '#material-ui/core/Drawer';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import List from '#material-ui/core/List';
import Typography from '#material-ui/core/Typography';
import Divider from '#material-ui/core/Divider';
import IconButton from '#material-ui/core/IconButton';
import Badge from '#material-ui/core/Badge';
import ChevronLeftIcon from '#material-ui/icons/ChevronLeft';
import ChevronRightIcon from '#material-ui/icons/ChevronRight';
import NotificationsIcon from '#material-ui/icons/Notifications';
import { mainListItems, secondaryListItems } from './Components/Sidebar.js';
const drawerWidth = 240;
const styles = theme => ({
root: {
display: 'flex',
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
chevronLeft: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
chevronRight: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
chevronRightButton: {
marginLeft: 12,
marginRight: 36,
},
chevronRightButtonHidden: {
display: 'none',
},
chevronLeftButton: {
marginLeft: 12,
marginRight: 36,
},
chevronLeftButtonHidden: {
display: 'none',
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing.unit * 7,
[theme.breakpoints.up('sm')]: {
width: theme.spacing.unit * 9,
},
},
appBarSpacer: theme.mixins.toolbar,
content: {
flexGrow: 1,
padding: theme.spacing.unit * 3,
height: '100vh',
overflow: 'auto',
},
chartContainer: {
marginLeft: -22,
},
tableContainer: {
height: 320,
},
});
class Dashboard extends React.Component {
state = {
open: true,
};
handleDrawerOpen = () => {
this.setState({ open: true });
};
handleDrawerClose = () => {
this.setState({ open: false });
};
render() {
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<div className={classes.root}>
<AppBar
position="absolute"
className={classNames(classes.appBar, this.state.open && classes.appBarShift)}
>
<Toolbar disableGutters={!this.state.open} className={classes.toolbar}>
<Typography variant="title" color="inherit" noWrap className={classes.title}>
Dashboard
</Typography>
<IconButton color="inherit">
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
</Badge>
</IconButton>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
classes={{
paper: classNames(classes.drawerPaper, !this.state.open && classes.drawerPaperClose),}}
open={this.state.open}
>
<Divider />
<List>{mainListItems}</List>
<Divider />
<List>{secondaryListItems}</List>
<div className={classes.chevronLeft}>
<IconButton
color="inherit"
aria-label="Close drawer"
onClick={this.handleDrawerClose}
// className={classNames(
// classes.chevronLeftButton,
// this.state.open && classes.chevronLeftButtonHidden,)}
>
<ChevronLeftIcon />
</IconButton>
</div>
<IconButton
color="inherit"
aria-label="Open drawer"
onClick={this.handleDrawerOpen}
className={classNames(
classes.chevronRightButton,
this.state.open && classes.chevronRightButtonHidden,)}
>
<ChevronRightIcon />
</IconButton>
</Drawer>
</div>
</React.Fragment>
);
}
}
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Dashboard);
You will no doubt notice the Chevrons don't work correctly. Only one is supposed to be visible at a time, but I am still working on that.
Any advice you can give me to correct the appearance would be greatly appreciated.
Thanks in advance.
Regards,
Mitch
I'm currently using the material-ui react library and have copied the responsive drawer component example.
Is there a possible way to separate the sidebar and header into a separate component and call the sidebar function and my own main content in the main section using the classes.content styling set in the sidebar component
<main className={classes.content}>
<Typography noWrap>{'You think water moves fast? You should see ice.'}
</Typography>
</main>
e.g my home function wants to use the side
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import List from 'material-ui/List';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import Hidden from 'material-ui/Hidden';
import Divider from 'material-ui/Divider';
import MenuIcon from 'material-ui-icons/Menu';
import { mailFolderListItems, otherMailFolderListItems } from './tileData';
const drawerWidth = 240;
const styles = theme => ({
root: {
width: '100%',
height: 430,
marginTop: theme.spacing.unit * 3,
zIndex: 1,
overflow: 'hidden',
},
appFrame: {
position: 'relative',
display: 'flex',
width: '100%',
height: '100%',
},
appBar: {
position: 'absolute',
marginLeft: drawerWidth,
[theme.breakpoints.up('md')]: {
width: `calc(100% - ${drawerWidth}px)`,
},
},
navIconHide: {
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
drawerHeader: theme.mixins.toolbar,
drawerPaper: {
width: 250,
[theme.breakpoints.up('md')]: {
width: drawerWidth,
position: 'relative',
height: '100%',
},
},
content: {
backgroundColor: theme.palette.background.default,
width: '100%',
padding: theme.spacing.unit * 3,
height: 'calc(100% - 56px)',
marginTop: 56,
[theme.breakpoints.up('sm')]: {
height: 'calc(100% - 64px)',
marginTop: 64,
},
},
});
class ResponsiveDrawer extends React.Component {
state = {
mobileOpen: false,
};
handleDrawerToggle = () => {
this.setState({ mobileOpen: !this.state.mobileOpen });
};
render() {
const { classes, theme } = this.props;
const drawer = (
<div>
<div className={classes.drawerHeader} />
<Divider />
<List>{mailFolderListItems}</List>
<Divider />
<List>{otherMailFolderListItems}</List>
</div>
);
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
color="contrast"
aria-label="open drawer"
onClick={this.handleDrawerToggle}
className={classes.navIconHide}
>
<MenuIcon />
</IconButton>
<Typography type="title" color="inherit" noWrap>
Responsive drawer
</Typography>
</Toolbar>
</AppBar>
<Hidden mdUp>
<Drawer
type="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={this.state.mobileOpen}
classes={{
paper: classes.drawerPaper,
}}
onRequestClose={this.handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden mdDown implementation="css">
<Drawer
type="permanent"
open
classes={{
paper: classes.drawerPaper,
}}
>
{drawer}
</Drawer>
</Hidden>
<main className={classes.content}>
<Typography noWrap>{'You think water moves fast? You should see ice.'}</Typography>
</main>
</div>
</div>
);
}
}
ResponsiveDrawer.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
export default withStyles(styles, { withTheme: true })(ResponsiveDrawer);
You can move separate styling into separate components by just moving the styling into the new React component. I've done your case as an example.
The main component:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Typography from 'material-ui/Typography';
const styles = theme => ({
content: {
backgroundColor: theme.palette.background.default,
width: '100%',
padding: theme.spacing.unit * 3,
height: 'calc(100% - 56px)',
marginTop: 56,
[theme.breakpoints.up('sm')]: {
height: 'calc(100% - 64px)',
marginTop: 64,
},
},
});
const Main = (props) => {
const { classes } = props;
return (
<main className={classes.content}>
<Typography noWrap>You think water moves fast? You should see ice.</Typography>
</main>
);
};
Main.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles, { withTheme: true })(Main);
The rest, using the extracted main component:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import List from 'material-ui/List';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import Hidden from 'material-ui/Hidden';
import Divider from 'material-ui/Divider';
import MenuIcon from 'material-ui-icons/Menu';
import { mailFolderListItems, otherMailFolderListItems } from './tileData';
const drawerWidth = 240;
const styles = theme => ({
root: {
width: '100%',
height: 430,
marginTop: theme.spacing.unit * 3,
zIndex: 1,
overflow: 'hidden',
},
appFrame: {
position: 'relative',
display: 'flex',
width: '100%',
height: '100%',
},
appBar: {
position: 'absolute',
marginLeft: drawerWidth,
[theme.breakpoints.up('md')]: {
width: `calc(100% - ${drawerWidth}px)`,
},
},
navIconHide: {
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
drawerHeader: theme.mixins.toolbar,
drawerPaper: {
width: 250,
[theme.breakpoints.up('md')]: {
width: drawerWidth,
position: 'relative',
height: '100%',
},
},
});
class ResponsiveDrawer extends React.Component {
state = {
mobileOpen: false,
};
handleDrawerToggle = () => {
this.setState({ mobileOpen: !this.state.mobileOpen });
};
render() {
const { classes, theme } = this.props;
const drawer = (
<div>
<div className={classes.drawerHeader} />
<Divider />
<List>{mailFolderListItems}</List>
<Divider />
<List>{otherMailFolderListItems}</List>
</div>
);
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
color="contrast"
aria-label="open drawer"
onClick={this.handleDrawerToggle}
className={classes.navIconHide}
>
<MenuIcon />
</IconButton>
<Typography type="title" color="inherit" noWrap>
Responsive drawer
</Typography>
</Toolbar>
</AppBar>
<Hidden mdUp>
<Drawer
type="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={this.state.mobileOpen}
classes={{
paper: classes.drawerPaper,
}}
onRequestClose={this.handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden mdDown implementation="css">
<Drawer
type="permanent"
open
classes={{
paper: classes.drawerPaper,
}}
>
{drawer}
</Drawer>
</Hidden>
<Main />
</div>
</div>
);
}
}
ResponsiveDrawer.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
export default withStyles(styles, { withTheme: true })(ResponsiveDrawer);
So, you can just pull the styling out with the React component that you're extracting. The docs give some more details about material-ui's styling system.