Material UI fixed MenuItem - reactjs

I'm wondering is there possibility to add fixed MenuItem to Menu? I'd like to use one of MenuItems to show header. Also if there's another solutions I'm open to try them too.
Here's the structure:
<Menu>
<MenuItem className={classes.header}>This is header</MenuItem>
<MenuItem>Item</MenuItem>
<MenuItem>Item</MenuItem>
<MenuItem>Item</MenuItem>
</Menu>
I tried to set header MenuItem's position to fixed but it throws whole Menu top of the page.
header: {
position: 'fixed',
},
EDIT: To clear a bit what I'm looking for. GitHub has same kind of menu:

You can use position sticky but you will need to "adjust" z-index too because of this issue:
so you can do something like this (based on material ui example):
import React from "react";
import IconButton from "#material-ui/core/IconButton";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import { makeStyles } from "#material-ui/core/styles";
import MoreVertIcon from "#material-ui/icons/MoreVert";
import { Paper, TextField } from "#material-ui/core";
const useStyles = makeStyles({
header: {
position: "sticky",
top: 0,
backgroundColor: "white",
zIndex: 2
},
search: {
marginBottom: "5px",
},
card: {
width: "100%"
}
});
const options = [
"None",
"Atria",
"Callisto",
"Dione",
"Ganymede",
"Hangouts Call",
"Luna",
"Oberon",
"Phobos",
"Pyxis",
"Sedna",
"Titania",
"Triton",
"Umbriel"
];
const ITEM_HEIGHT = 48;
export default function LongMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const classes = useStyles();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
elevation={1}
className={classes.menu}
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: "800px",
backgroundColor: "white"
}
}}
>
<div className={classes.header}>
<Paper className={classes.card} elevation={3}>
<TextField
className={classes.search}
label={"search filter"}
variant={"outlined"}
></TextField>
</Paper>
</div>
{options.map((option) => (
<MenuItem
key={option}
selected={option === "Pyxis"}
onClick={handleClose}
>
{option}
</MenuItem>
))}
</Menu>
</div>
);
}
here a sandbox link
this is the result after adding z-index:

Related

Material UI: dialog with Groupbutton

I have this file and I'm trying to have a "group batten", and when I click on it, I have a list with ["Confirm Review", "Reject Invoice", "Assign to User"] and I want when I press "Confirm Review" to show me a Dialog, how can I solve this problem?
And this is a file that contains the part of groupButton
import React from "react";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import ButtonGroup from "#material-ui/core/ButtonGroup";
import ArrowDropDownIcon from "#material-ui/icons/ArrowDropDown";
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
import Grow from "#material-ui/core/Grow";
import Paper from "#material-ui/core/Paper";
import Popper from "#material-ui/core/Popper";
import MenuItem from "#material-ui/core/MenuItem";
import MenuList from "#material-ui/core/MenuList";
const options = ["Confirm Review", "Reject Invoice", "Assign to User"];
export default function SplitButton() {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleClick = () => {
console.info(`You clicked ${options[selectedIndex]}`);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
return (
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
<ButtonGroup
variant="contained"
color="primary"
ref={anchorRef}
aria-label="split button"
>
<Button
style={{
paddingLeft: "2.4rem",
paddinRight: "2.4rem",
paddingTop: "1.5rem",
paddingBottom: "1.5rem",
// backgroundColor: "#d82c2c",
// color: "#FFFFFF",
borderRadius: 4,
}}
onClick={handleClick}
>
{options[selectedIndex] || "Action"}{" "}
</Button>
<Button
color="primary"
size="small"
aria-controls={open ? "split-button-menu" : undefined}
aria-expanded={open ? "true" : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
style={{
paddingTop: "1.5rem",
paddingBottom: "1.5rem",
// backgroundColor: "#d82c2c",
// color: "#FFFFFF",
borderRadius: 4,
}}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom",
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu">
{options.map((option, index) => (
<MenuItem
key={option}
disabled={index === 2}
selected={index === selectedIndex}
onClick={(event) => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Grid>
</Grid>
);
}
First, is that MUI v4?
Also, why use style prop when sx (v5) and makeStyle (v4) are a thing.
A high-level overview of what you want, minus all those decorative styles and props.
import { Fragment, useState } from "react";
import { ButtonGroup, Button, Dialog, DialogContent } from "#mui/material";
const ButtonGroupWithDialog = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const handleDialogOpen = () => setDialogOpen(true);
const handleDialogClose = () => setDialogOpen(false);
return (
<Fragment>
<ButtonGroup>
<Button onClick={handleDialogOpen}>Confirm Review</Button>
<Button>Reject Invoice</Button>
<Button>Assign to User</Button>
</ButtonGroup>
<Dialog open={dialogOpen} onClose={handleDialogClose}>
<DialogContent>
{/* content here */}
</DialogContent>
</Dialog>
</Fragment>
);
};

Material ui change color for select box

This is my code,
import { AppBar, createStyles, MenuItem, Select, Toolbar, Typography } from '#mui/material';
import { makeStyles } from '#mui/styles';
import { useState } from 'react';
const useStyles = makeStyles((theme) =>
createStyles({
selectRoot: {
color: "white",
}
}),
);
const Navbar = () => {
const classes = useStyles();
const [sort, setSort] = useState(1);
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1}}>Tools</Typography>
<Select
variant="standard"
value={sort}
className={classes.selectRoot}
onChange={(event) => setSort(event.target.value)}
>
<MenuItem value={1}>Default sort</MenuItem>
<MenuItem value={2}>Sort by Title ASC</MenuItem>
<MenuItem value={3}>Sort by Title DESC</MenuItem>
</Select>
</Toolbar>
</AppBar>
)
}
export default Navbar;
What I need to change the color of select tag. In inspect seems like overridden by this class.
.css-a88p61-MuiInputBase-root-MuiInput-root{
color: rgba(0, 0, 0, 0.87);
}
You need to use .MuiMenuItem-root inside your select tag , this will color your entire menuItems.
MenuProps={{
sx: {
"&& .MuiMenuItem-root":{
backgroundColor: "red",
color: "orange"
}
}
}}
But if you don't want to apply color to a specific menuItem then you need to use the .MuiMenuItem-gutters class in Select Component and set disableGutters={true} to the menuItem to which you don't want to apply the color
Example using .MuiMenuItem-root class
import { AppBar, createStyles, MenuItem, Select, Toolbar, Typography } from '#mui/material';
import { makeStyles } from '#mui/styles';
import { useState } from 'react';
const useStyles = makeStyles((theme) =>
createStyles({
selectRoot: {
color: "white",
},
menuItem:{
color: "red",
}
}),
);
const Select = () => {
const classes = useStyles();
const [sort, setSort] = useState(1);
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1}}>Tools</Typography>
<Select
variant="standard"
value={sort}
className={classes.selectRoot}
onChange={(event) => setSort(event.target.value)}
MenuProps={{
sx: {
"&& .MuiMenuItem-root":{
backgroundColor: "red",
color: "orange"
}
}
}}
>
<MenuItem value={1}>Default sort</MenuItem>
<MenuItem value={2}>Sort by Title ASC</MenuItem>
<MenuItem value={3}>Sort by Title DESC</MenuItem>
</Select>
</Toolbar>
</AppBar>
)
}
export default Select;
Example using .MuiMenuItem-gutters class
import { AppBar, createStyles, MenuItem, Select, Toolbar, Typography } from '#mui/material';
import { makeStyles } from '#mui/styles';
import { useState } from 'react';
const useStyles = makeStyles((theme) =>
createStyles({
selectRoot: {
color: "white",
},
}),
);
const Select = () => {
const classes = useStyles();
const [sort, setSort] = useState(1);
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1}}>Tools</Typography>
<Select
variant="standard"
value={sort}
className={classes.selectRoot}
onChange={(event) => setSort(event.target.value)}
MenuProps={{
sx: {
"&& .MuiMenuItem-gutters": {
backgroundColor: "pink",
color: "red"
},
}
}}
>
<MenuItem disableGutters = {true} value={1}>Default sort</MenuItem>
<MenuItem value={2}>Sort by Title ASC</MenuItem>
<MenuItem value={3}>Sort by Title DESC</MenuItem>
</Select>
</Toolbar>
</AppBar>
)
}
export default Select;
Refer documentation for more css properties

How to make Material UI Temporary Drawer button as icon instead of text?

I'm using Material UI to create a navigation bar with Temporary Drawer. When the user clicks on the hamburger menu icon, I want the menu to fade-in to the screen and slide from the right.
Basically, all the functionality works, except only the button named 'RIGHT' works instead of the hamburger icon I have created beside it..
I have tried removing 'right' and replace it with the icon, but when I do that the menu comes out from left-top...
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
/*
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
*/
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
<IconButton>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
{['right'].map((anchor) => (
<React.Fragment key={anchor}>
<Button onClick={toggleDrawer(anchor, true)}>{anchor}</Button>
<Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
{list(anchor)}
</Drawer>
</React.Fragment>
))}
</IconButton>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
I've made your code work for what you need and left some comments inside. I don't know what you ideally want to make, but don't copy all the code from the MUI example which you might not fully understand.
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
Above is used to control the state of Drawer.
Following is the code that you can just copy.
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
{/* You Don't need the map here unless you want many button to toggle one Drawer,
like the example on https://material-ui.com/components/drawers/.
and the point here for your question is to move the onClick to IconButton, and you may
not want the button inside IconButton, since it is a button already,
the inside button would make it a little ugly */}
<IconButton onClick={handleDrawerOpen} onClose={handleDrawerClose}>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
RIGHT{/* delete the text here if you don't need */}
</IconButton>
<Drawer anchor='right' open={anchorEl} onClose={handleDrawerClose}>
{list('right')}
</Drawer>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
Recomandation: since you have import React, { useEffect, useState } from 'react', you may not use React.useState() but use useState() directly.

How to implement edit photo like LinkedIn edit background feature in React.js?

Anybody has experience or idea how to implement an edit photo feature like in LinkedIn edit photo or background in React.js?
Here's the functionalities I wanted.
Can upload image
Show image
Edit uploaded image
zoom-in/zoom-out
rotate
delete
Use react-cropper package which is a react version of cropper.js. You can perform all the functionalities you need using that. There are some changes required in this code also add rotate functionality from cropper.js.
import React, { useState, useRef } from "react";
import Cropper from "react-cropper";
import Button from "#material-ui/core/Button";
import "cropperjs/dist/cropper.css";
import Grid from '#material-ui/core/Grid';
import IconButton from '#material-ui/core/IconButton';
import Tooltip from '#material-ui/core/Tooltip';
import CheckCircleIcon from '#material-ui/icons/CheckCircle';
import ArrowForwardIcon from '#material-ui/icons/ArrowForward';
import ArrowBackIcon from '#material-ui/icons/ArrowBack';
import ArrowDownwardIcon from '#material-ui/icons/ArrowDownward';
import ArrowUpwardIcon from '#material-ui/icons/ArrowUpward';
import CancelIcon from '#material-ui/icons/Cancel';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
button: {
backgroundColor: "#2774b8",
'&:hover': {
backgroundColor: "#2774b8",
}
},
toolsCtr: {
display: 'flex',
padding: '0.5rem'
},
tooltip: {
marginRight: '0.5rem'
},
icnBtn: {
padding: '8px',
borderRadius: '3px',
backgroundColor: '#f0f8ff'
},
icn: {
fontSize: '1.5rem',
color: '#2774b8'
},
cropperCtr: {
border: '2px solid #d9e5f0'
}
}))
export default function Crop(props) {
const cropperRef = useRef();
const classes = useStyles();
const [uploadedImg, setUploadedImg] = useState(props && props.image)
const [croppedLiveUrl, setCoppedLiveUrl] = useState("");
const [croppedData, setCoppedData] = useState({ url: "", data: {} });
const selForProcessing = () => {
props.useImage(croppedLiveUrl);
}
const _onMoveCropBox = () => {
if (null !== cropperRef.current) {
setCoppedLiveUrl(cropperRef.current.cropper.getCroppedCanvas().toDataURL());
setCoppedData({ url: "", data: {} });
}
};
const _onMoveImageLeft = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(-10, 0);
}
}
const _onMoveImageRight = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(10, 0);
}
}
const _onMoveImageTop = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, -10);
}
}
const _onMoveImageBottom = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, 10);
}
}
return (
<React.Fragment>
<div className={classes.cropperCtr}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<Cropper
ref={cropperRef}
src={uploadedImg}
style={{ height: 400, width: "100%", overflow:
'scroll' }}
guides={false}
crop={() => { _onMoveCropBox() }}
crossOrigin={"true"}
autoCrop={false}
movable={true}
move={() => { _onMoveImageTop() }}
/>
</Grid>
</Grid>
<div className={classes.toolsCtr}>
<Tooltip title="Use Selection" aria-label="use" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
selForProcessing()
}}>
<CheckCircleIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Left" aria-label="left" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageLeft()
}}>
<ArrowBackIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Right" aria-label="right" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageRight()
}}>
<ArrowForwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Top" aria-label="top" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageTop()
}}>
<ArrowUpwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Down" aria-label="down" className={classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageBottom()
}}>
<ArrowDownwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
</div>
</div>
</React.Fragment >
);
}

How to Make Material-UI Menu based on Hover, not Click

I am using Material-UI Menu.
It should work as it was, but just using mouse hover, not click.
Here is my code link: https://codesandbox.io/embed/vn3p5j40m0
Below is the code of what I tried. It opens correctly, but doesn't close when the mouse moves away.
import React from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
function SimpleMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(event.currentTarget);
}
function handleClose() {
setAnchorEl(null);
}
return (
<div>
<Button
aria-owns={anchorEl ? "simple-menu" : undefined}
aria-haspopup="true"
onClick={handleClick}
onMouseEnter={handleClick}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
onMouseLeave={handleClose}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
export default SimpleMenu;
The code below seems to work reasonably. The main changes compared to your sandbox are to use onMouseOver={handleClick} instead of onMouseEnter on the button. Without this change, it doesn't open reliably if the mouse isn't over where part of the menu will be. The other change is to use MenuListProps={{ onMouseLeave: handleClose }}. Using onMouseLeave directly on Menu doesn't work because the Menu includes an overlay as part of the Menu leveraging Modal and the mouse never "leaves" the overlay. MenuList is the portion of Menu that displays the menu items.
import React from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
function SimpleMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
if (anchorEl !== event.currentTarget) {
setAnchorEl(event.currentTarget);
}
}
function handleClose() {
setAnchorEl(null);
}
return (
<div>
<Button
aria-owns={anchorEl ? "simple-menu" : undefined}
aria-haspopup="true"
onClick={handleClick}
onMouseOver={handleClick}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
MenuListProps={{ onMouseLeave: handleClose }}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
export default SimpleMenu;
I've updated Ryan's original answer to fix the issue where it doesn't close when you move the mouse off the element to the side.
How it works is to disable the pointerEvents on the MUI backdrop so you can continue to detect the hover behind it (and re-enables it again inside the menu container). This means we can add a leave event listener to the button as well.
It then keeps track of if you've hovered over either the button or menu using currentlyHovering.
When you hover over the button it shows the menu, then when you leave it starts a 50ms timeout to close it, but if we hover over the button or menu again in that time it will reset currentlyHovering and keep it open.
I've also added these lines so the menu opens below the button:
getContentAnchorEl={null}
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
import React from "react";
import Button from "#material-ui/core/Button";
import Menu from "#material-ui/core/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import makeStyles from "#material-ui/styles/makeStyles";
const useStyles = makeStyles({
popOverRoot: {
pointerEvents: "none"
}
});
function SimpleMenu() {
let currentlyHovering = false;
const styles = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
if (anchorEl !== event.currentTarget) {
setAnchorEl(event.currentTarget);
}
}
function handleHover() {
currentlyHovering = true;
}
function handleClose() {
setAnchorEl(null);
}
function handleCloseHover() {
currentlyHovering = false;
setTimeout(() => {
if (!currentlyHovering) {
handleClose();
}
}, 50);
}
return (
<div>
<Button
aria-owns={anchorEl ? "simple-menu" : undefined}
aria-haspopup="true"
onClick={handleClick}
onMouseOver={handleClick}
onMouseLeave={handleCloseHover}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
MenuListProps={{
onMouseEnter: handleHover,
onMouseLeave: handleCloseHover,
style: { pointerEvents: "auto" }
}}
getContentAnchorEl={null}
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
PopoverClasses={{
root: styles.popOverRoot
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
export default SimpleMenu;
Using an interactive HTML tooltip with menu items works perfectly, without requiring you to necessarily click to view menu items.
Here is an example for material UI v.4.
import React from 'react';
import { withStyles, Theme, makeStyles } from '#material-ui/core/styles';
import Tooltip from '#material-ui/core/Tooltip';
import { MenuItem, IconButton } from '#material-ui/core';
import MoreVertIcon from '#material-ui/icons/MoreVert';
import styles from 'assets/jss/material-dashboard-pro-react/components/tasksStyle.js';
// #ts-ignore
const useStyles = makeStyles(styles);
const LightTooltip = withStyles((theme: Theme) => ({
tooltip: {
backgroundColor: theme.palette.common.white,
color: 'rgba(0, 0, 0, 0.87)',
boxShadow: theme.shadows[1],
fontSize: 11,
padding: 0,
margin: 4,
},
}))(Tooltip);
interface IProps {
menus: {
action: () => void;
name: string;
}[];
}
const HoverDropdown: React.FC<IProps> = ({ menus }) => {
const classes = useStyles();
const [showTooltip, setShowTooltip] = useState(false);
return (
<div>
<LightTooltip
interactive
open={showTooltip}
onOpen={() => setShowTooltip(true)}
onClose={() => setShowTooltip(false)}
title={
<React.Fragment>
{menus.map((item) => {
return <MenuItem onClick={item.action}>{item.name}</MenuItem>;
})}
</React.Fragment>
}
>
<IconButton
aria-label='more'
aria-controls='long-menu'
aria-haspopup='true'
className={classes.tableActionButton}
>
<MoreVertIcon />
</IconButton>
</LightTooltip>
</div>
);
};
export default HoverDropdown;
Usage:
<HoverDropdown
menus={[
{
name: 'Item 1',
action: () => {
history.push(
codeGeneratorRoutes.getEditLink(row.values['node._id'])
);
},
},{
name: 'Item 2',
action: () => {
history.push(
codeGeneratorRoutes.getEditLink(row.values['node._id'])
);
},
},{
name: 'Item 3',
action: () => {
history.push(
codeGeneratorRoutes.getEditLink(row.values['node._id'])
);
},
},{
name: 'Item 4',
action: () => {
history.push(
codeGeneratorRoutes.getEditLink(row.values['node._id'])
);
},
},
]}
/>
I gave up using Menu component because it implemented Popover. To solve the overlay problem I had to write too much code. So I tried to use the old CSS way:
CSS: relative parent element + absolute menu element
Component: Paper + MenuList
<ListItem>
<Link href="#" >
{user.name}
</Link>
<AccountPopover elevation={4}>
<MenuList>
<MenuItem>Profile</MenuItem>
<MenuItem>Logout</MenuItem>
</MenuList>
</AccountPopover>
</ListItem>
styled components:
export const ListItem = styled(Stack)(() => ({
position: 'relative',
"&:hover .MuiPaper-root": {
display: 'block'
}
}))
export const AccountPopover = styled(Paper)(() => ({
position: 'absolute',
zIndex:2,
right: 0,
top: 30,
width: 170,
display: 'none'
}))
use **MenuListProps** in the Menu component and use your menu **closeFunction** -
MenuListProps={{ onMouseLeave: handleClose }}
example-
<Menu
dense
id="demo-positioned-menu"
anchorEl={anchorEl}
open={open}
onClose={handleCloseMain}
title={item?.title}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
MenuListProps={{ onMouseLeave: handleClose }}
/>
I hope it will work perfectly.

Resources