On this MUI AppBar/Drawer sample, I have added a MUI Menu to edit profile, with an anchor on a div. It's located on the top right corner. I like to be able to click on the username or the profile icon to pop the menu out. For some reason the menu never disappear. I have try to add console.log in the callbacks. It shows the open callback is called right after the close one. Can someone explain why and how to close the menu ?
Here is my code, and the full project on Codesandbox :
import React from "react";
import Box from "#mui/material/Box";
import Grid from "#mui/material/Grid";
import IconButton from "#mui/material/IconButton";
import Menu from "#mui/material/Menu";
import MenuIcon from "#mui/icons-material/Menu";
import MenuItem from "#mui/material/MenuItem";
import Toolbar from "#mui/material/Toolbar";
import AccountCircle from "#mui/icons-material/AccountCircle";
import { styled } from "#mui/material/styles";
import { useNavigate } from "react-router-dom";
export default function TopBar(props) {
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleMenuOpen = (event) => {
console.log("set menu open");
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
console.log("set menu close");
setAnchorEl(null);
};
const UserName = styled("div")(({ theme }) => ({
fontSize: "1em",
marginRight: "0.7em",
display: "inline",
height: "100%",
textAlign: "center",
cursor: "pointer"
}));
const Logo = styled("img")(({ theme }) => ({
height: "42px",
width: "42px",
backgroundColor: "#FFF",
marginRight: "0.7em"
}));
return (
<Toolbar>
<Grid
style={{ justifyContent: "space-between", alignItems: "center" }}
container
>
<Grid item style={{ display: "inline-flex", alignItems: "center" }}>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={props.handleDrawerOpen}
edge="start"
sx={{
...(props.drawerOpen && {
display: {
xs: "block",
sm: "none"
}
})
}}
>
<MenuIcon />
</IconButton>
</Grid>
<Grid item style={{ display: "inline-flex", alignItems: "center" }}>
<Logo
src=""
alt="logo"
onClick={() => {
props.handleDrawerClose();
navigate("/");
}}
/>
<Box
sx={{
whiteSpace: "nowrap",
fontSize: { xs: "1em", sm: "1.25em" },
fontWeight: { xs: 400, sm: 500 }
}}
>
Toolbar test
</Box>
</Grid>
<Grid item style={{ display: "flex", alignItems: "center" }}>
<div
style={{ display: "inline-flex", alignItems: "center" }}
onClick={handleMenuOpen}
>
<UserName sx={{ display: { xs: "none", sm: "block" } }}>
{props.userName}
</UserName>
<IconButton
color="inherit"
aria-label="user"
edge="start"
size="large"
>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem
onClick={() => {
props.handleDrawerClose();
navigate("/test");
}}
>
My Profile
</MenuItem>
<MenuItem
onClick={() => {
props.handleDrawerClose();
navigate("/logout");
}}
>
Logout
</MenuItem>
</Menu>
</div>
</Grid>
</Grid>
</Toolbar>
);
}
You somehow managed that when you trigger the onClose event, you are simultaneously triggering handleMenuOpen function. I would suggest you use a button base to handle opening the menu.
Something like this:
<ButtonBase onClick={handleMenuOpen}>
<UserName sx={{ display: { xs: "none", sm: "block" } }}>
{props.userName}
</UserName>
<AccountCircle />
</ButtonBase>
And dont forget to remove the onClick event from your div.
You can also take a look of the fork that i made.
Related
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.
guys so I'm trying to create a navbar with a set of private navbar links that will be only visible to the user when they log in. Otherwise, the only pages before logging in with a JWT are the register and login page and the register page is set as the '/' directory.
I would like the user prior to registering to see only the /Register, and /Login pages
if the user is logged in I want them to see the /Balance, /Deposit, /Withdraw, and Alldata page
I can't seem to figure this out on MUI can someone help me out, please? Here is my code.
import * as React from 'react';
import AppBar from '#mui/material/AppBar';
import Box from '#mui/material/Box';
import Toolbar from '#mui/material/Toolbar';
import IconButton from '#mui/material/IconButton';
import Typography from '#mui/material/Typography';
import Menu from '#mui/material/Menu';
import MenuIcon from '#mui/icons-material/Menu';
import Container from '#mui/material/Container';
import Avatar from '#mui/material/Avatar';
import Button from '#mui/material/Button';
import Tooltip from '#mui/material/Tooltip';
import MenuItem from '#mui/material/MenuItem';
import AdbIcon from '#mui/icons-material/Adb';
import { Link, useNavigate } from 'react-router-dom'
import { logout, reset } from '../features/auth/authSlice'
import { useSelector, useDispatch } from 'react-redux'
const Navigation = () => {
const navigate = useNavigate()
const dispatch = useDispatch()
const { user } = useSelector((state) => state.auth)
const pages = ['Register', 'Login', 'Deposit', 'Withdraw', 'Alldata'];
const settings = ['Balance', 'Logout'];
const onLogout = () => {
dispatch(logout())
dispatch(reset())
navigate('/')
}
const [anchorElNav, setAnchorElNav] = React.useState(null);
const [anchorElUser, setAnchorElUser] = React.useState(null);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
<Typography
variant="h6"
noWrap
component="a"
href="/"
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: 'block', md: 'none' },
}}
>
{pages.map((page) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Typography textAlign="center">
<Link to={`/${page}`}>
{page}
</Link>
</Typography>
</MenuItem>
)
)}
</Menu>
</Box>
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href=""
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
BADBANK
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button
key={page}
onClick={handleCloseNavMenu}
sx={{ my: 2, color: 'white', display: 'block' }}
>
<Link to={`/${page}`}>
{page}
</Link>
</Button>
))}
</Box>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{settings.map((setting) => (
<MenuItem
key={setting}
onClick={onLogout}>
<Typography textAlign="center">{setting}</Typography>
</MenuItem>
))}
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
export default Navigation;
you can make a conditional statement on links array based on auth state
const pages = user ? ['Deposit', 'Withdraw', 'Alldata'] : ['Register', 'Login'];
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",
});
I'm using the material-ui and material-ui icons for my react project. I'm in new in react.
Can someone please guide me how to make icons with label? I want to place the label just below the icon. For example, text "Home" written under "Home Icons". I'm trying to implement something similar to what Microsoft Team has implemented in sidebar navigation (web version)
I read the API, and I found there's a prop Component. I try to experiment, however, whenever I'm using it icons disappear.
Please visit this link https://codesandbox.io/s/material-demo-forked-nohsm?file=/demo.js I'm getting this result:
Here's my code
import React from "react";
import AppBar from "#material-ui/core/AppBar";
import Drawer from "#material-ui/core/Drawer";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import NotificationsIcon from "#material-ui/icons/Notifications";
import { ListItem, Toolbar } from "#material-ui/core";
import List from "#material-ui/core/List";
import ListItemIcon from "#material-ui/core/ListItemIcon";
const drawerWidth = 72;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
drawer: {
width: drawerWidth,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing(5)
},
drawerPaper: {
width: drawerWidth
}
}));
export default function Demo() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="fixed" color="primary">
<Toolbar></Toolbar>
</AppBar>
<Drawer
classes={{
paper: classes.drawerPaper
}}
variant="permanent"
className={classes.drawer}
>
{/* <NotificationsIcon size="large" /> */}
<List>
<ListItem>
<ListItemIcon>
<NotificationsIcon color="primary" fontSize="large" />
Activity
</ListItemIcon>
</ListItem>
</List>
</Drawer>
</div>
);
}
I want to similar to this one:
Please help. Thank you.
sometimes it's not MUI at all but pure CSS. MUI already provides icon and label, you just have to work CSS a bit, this one may work just customizing ListItemIcon styles with flex properties (see listItemIcon class rules):
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
listItemIcon: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column'
},
drawer: {
width: drawerWidth,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing(5)
},
drawerPaper: {
width: drawerWidth
}
}));
export default function Demo() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="fixed" color="primary">
<Toolbar></Toolbar>
</AppBar>
<Drawer
classes={{
paper: classes.drawerPaper
}}
variant="permanent"
className={classes.drawer}
>
{/* <NotificationsIcon size="large" /> */}
<List>
<ListItem>
<ListItemIcon className={classes.listItemIcon}>
<NotificationsIcon color="primary" fontSize="large" />
Activity
</ListItemIcon>
</ListItem>
</List>
</Drawer>
</div>
);
}
When MUI does not provide default styles for it, you can always use flex-box
I tried this way, and it worked. Need to wrap the Icons in a div and it will work.
<List>
<ListItem style={{ textAlign: "center" }}>
<div>
<NotificationsIcon color="primary" fontSize="large" />
Activity
</div>
</ListItem>
<ListItem style={{ textAlign: "center" }}>
<div>
<NotificationsIcon color="primary" fontSize="large" />
Activity
</div>
</ListItem>
</List>
https://codesandbox.io/s/material-demo-forked-b73ds?file=/demo.js
I want to center a Button inside of a Card that is in the center of the screen. So far I haven't been able to.
This is wha I have so far:
import React from 'react'
import Button from '#material-ui/core/Button'
import { makeStyles } from '#material-ui/core/styles'
import { Card } from '#material-ui/core'
import Grid from '#material-ui/core/Grid'
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #9013FE 15%, #50E3C2 90%)',
minWidth: '100%',
minHeight: '100vh',
display: "flex",
flexDirection: "column",
justifyContent: "center"
},
card: {
maxWidth: '40%',
minHeight: '20vh',
},
})
export default function LoginPage () {
const classes = useStyles()
return (
<Grid className={classes.root} spacing={0} align="center" justify="center">
<Card className={classes.card} align="center" justify="center">
<Button variant="contained" color="primary">
Hello World
</Button>
</Card>
</Grid>
)
}
It's centered vertically but not horizontally, when I change other parameter I get it centered horizontally but not vertically.
Screenshot of the result
Any Ideas?
Best regards.
You can use alignItems.
Code Sandbox demo: https://codesandbox.io/s/stack-overflow-material-ui-centering-0rgqx
const useStyles = makeStyles({
root: {
background: "linear-gradient(45deg, #9013FE 15%, #50E3C2 90%)",
minWidth: "100%",
minHeight: "100vh",
display: "flex",
flexDirection: "column",
justifyContent: "center"
},
card: {
maxWidth: "40%",
minHeight: "20vh",
display: "flex",
alignItems: "center"
}
});
export default function LoginPage() {
const classes = useStyles();
return (
<Grid
className={classes.root}
spacing={0}
alignItems="center"
justify="center"
>
<Card className={classes.card}>
<Button variant="contained" color="primary">
Hello World
</Button>
</Card>
</Grid>
);
}