I just started plating around with react. I am currently working on my navBar using material-ui and react. When I hover over the menu, the drop-down appears. But in order to close the drop-down, I have to click on the outside of the drop-down. I want to be able to close the dropdown when I hover out of the drop-down or move to the different menu option (in which case a different drop-down should appear). Something like this one: https://www.palantir.com/
I looked around but I didn't find the solution. This was the closest I got: Material-ui: open menu by event hover
I tried using the same technique and added this to my code but to no avail. Any suggestions? Thanks!
Edits: I recreated my problem here: https://react-xmaiyw.stackblitz.io
The problem can be seen when clicked on 'Why us'.
handleClick = (event) => {
event.preventDefault();
this.setState({
open: true,
anchorEl: event.currentTarget,
});
};
handleRequestClose = () => {
this.setState({
open: false,
});
};
render() {
return (
<FlatButton
onClick={this.handleClick}
onMouseOver={this.handleClick}
onMouseLeave={this.handleRequestClose} //When I add this line of
//code, it keeps flickering very fast almost as if drop-down
//doesn't open
label="Why Us?"
/>
)}
The flickering is caused by the opening of the menu underneath your mouse. When the menu opens, the mouse is no longer over the button, so it prompts a mouseleave event, closing the menu, so that your mouse is now above the button again, prompting a mouseenter event, which opens the menu...and so on and so forth.
You can accomplish what you'd like with some additional logic to track where the mouse is, and a timeout to ensure that the user has time to transition the mouse between the button and the menu.
import React from 'react';
import Button from 'material-ui/Button';
import Menu, { MenuItem } from 'material-ui/Menu';
const timeoutLength = 300;
class SimpleMenu extends React.Component {
state = {
anchorEl: null,
// Keep track of whether the mouse is over the button or menu
mouseOverButton: false,
mouseOverMenu: false,
};
handleClick = event => {
this.setState({ open: true, anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ mouseOverButton: false, mouseOverMenu: false });
};
enterButton = () => {
this.setState({ mouseOverButton: true });
}
leaveButton = () => {
// Set a timeout so that the menu doesn't close before the user has time to
// move their mouse over it
setTimeout(() => {
this.setState({ mouseOverButton: false });
}, timeoutLength);
}
enterMenu = () => {
this.setState({ mouseOverMenu: true });
}
leaveMenu = () => {
setTimeout(() => {
this.setState({ mouseOverMenu: false });
}, timeoutLength);
}
render() {
// Calculate open state based on mouse location
const open = this.state.mouseOverButton || this.state.mouseOverMenu;
return (
<div>
<Button
aria-owns={this.state.open ? 'simple-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
onMouseEnter={this.enterButton}
onMouseLeave={this.leaveButton}
>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
open={open}
onClose={this.handleClose}
MenuListProps={{
onMouseEnter: this.enterMenu,
onMouseLeave: this.leaveMenu,
}}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>My account</MenuItem>
<MenuItem onClick={this.handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
}
export default SimpleMenu;
I used the MenuListProps to set the mouseEnter and mouseLeave events directly on the MenuList itself because the Menu component includes a bunch of invisible (disply: none) transition elements that have weird effects on mouse events. The MenuList is the element that's actually displayed so it makes sense to set the mouse events directly on it.
You'll probably need to play around with the timeoutLength and transitions to get everything looking smooth.
I faced same problems.
I solved the issues like this. I gaved LeaveMenu event to total component and menu component seperately, after it, it worked perfectly
import React from 'react';
import {
Menu,
MenuItem as MuiMenuItem,
Avatar,
Divider,
Typography,
Switch,
Fade,
} from '#mui/material';
import { useHistory } from 'react-router-dom';
import { styled } from '#mui/styles';
import { DarkMode as DarkModeIcon } from '#mui/icons-material';
/******************** Styled Components ********************/
const UserAvatarButton = styled('div')(({ active, theme }) => ({
height: 72,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '0px 20px',
cursor: 'pointer',
borderBottom: active ? `3px solid ${theme.palette.primary.main}` : 'none',
borderRadius: 0,
}));
const ProfileMenuNavigation = styled(Menu)(() => ({
'& .MuiList-root': {
paddingTop: 0,
paddingBottom: 0,
minWidth: 220,
maxWidth: 350,
},
}));
const MenuItem = styled(MuiMenuItem)(({ theme }) => ({
padding: 16,
width: '100%',
'&:hover': {
backgroundColor: theme.palette.background.main,
boxShadow: '5px 0px 5px 0px #888888',
transition: 'box-shadow 0.3s ease-in-out',
},
}));
const ProfileMenuText = styled(Typography)(() => ({
fontFamily: 'Poppins',
marginLeft: 16,
marginRight: 16,
fontSize: 16,
fontWeight: 600,
}));
/******************** Main Component ********************/
const ProfileMenu = ({ menus, active }) => {
const history = useHistory();
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
if (anchorEl) {
setAnchorEl(null);
} else {
setAnchorEl(event.currentTarget);
}
};
const handleClose = () => {
setAnchorEl(null);
};
const goPath = (path) => {
setAnchorEl(null);
history.push(path);
};
const leaveMenu = () => {
setTimeout(() => {
setAnchorEl(null);
}, 300);
};
return (
<div onMouseLeave={leaveMenu}>
<UserAvatarButton
id="account-button"
active={active}
aria-controls={open ? 'account-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
onMouseOver={(event) => setAnchorEl(event.currentTarget)}
>
<Avatar
sx={{
width: 38,
height: 38,
}}
alt="Avatar"
src="https://i.pravatar.cc/300"
/>
</UserAvatarButton>
<ProfileMenuNavigation
id="account-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'account-button',
onMouseLeave: leaveMenu,
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
TransitionComponent={Fade}
>
{menus.map((menu, index) => (
<div key={index}>
<MenuItem onClick={() => goPath(menu.path)}>
{menu?.icon}
<ProfileMenuText>{menu.text}</ProfileMenuText>
</MenuItem>
<Divider style={{ margin: 0 }} />
</div>
))}
<MenuItem onClick={() => {}}>
<DarkModeIcon />
<ProfileMenuText>Night Mode</ProfileMenuText>
<div style={{ marginLeft: 16 }}>
<Switch />
</div>
</MenuItem>
</ProfileMenuNavigation>
</div>
);
};
export default ProfileMenu;
Related
I have a simple mui Menu, where one MenuItem should render another React component. The problem is that my Menu is rendered in another file, where close and handleClick functions are defined.
Problem: The component doesn't render on the MenuItem click. Seems like it is because setAnchor(null); in the App component sets the anchor to null always. Does this mean I need to use a different anchor? If yes, how?
The Menu component code is as follows:
interface Props {
handler: Handler;
anchor: HTMLButtonElement | null;
onClose: () => void;
}
const AddDataMenu = ({ handler,anchor, onClose }: Props) => {
const renderDataPopOver = () => {
console.log('this is clicked'); <<<<<<<<<< I can see this function is accessed
<AddDataPopover handler={handler} anchor={anchor} onClose={onClose} />;
};
return (
<div>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{ width: 320, maxWidth: '100%' }}
>
<MenuItem onClick={renderDataPopOver}>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>item 1</Typography>
</MenuItem>
</Menu>
</div>
);
};
export default AddDataMenu;
This is the Main Component where my Menu is rendered.
const App = ({ scope }) => {
const ref = useRef<HTMLButtonElement>(null);
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
const [handler, setHandler] = useState<Handler>();
const close = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(null);
};
const handleClick = () => { <<<<<<< this is accessed before MenuItem click
setAnchor(ref.current);
};
return showAdd && handler ? (
<MessageContainer
message={'test'}
actions={
<Box ml={1}>
<Button ref={ref} color="primary" variant="contained" onClick={handleClick}>
{t('Visualization.chart-initial-states.AddColumns')}
</Button>
<AddDataMenu handler={handler} anchor={anchor} onClose={close} />
</Box>
}
/>
) : (
<DisplayError />
);
};
export default App;
Assuming that the goal is to render a secondary Popover beside Menu on click of MenuItem, perhaps indeed the component would need to assign any MenuItem that triggers it as anchorEl to the rendered Popover.
Basic live example on: stackblitz (this and below examples omitted everything except for the Menu from the original posted code for simplicity).
In AddDataMenu, add AddDataPopover to the output with its initial anchorEl as null so it would not render immediately. Matching anchorEl can be assigned in the event of handleOpen.
A ref array is used to reference multiple MenuItem here, but this is an optional approach.
const AddDataMenu = ({ anchor, onClose }) => {
const [itemAnchor, setItemAnchor] = useState(null);
const itemRef = useRef([]);
const handleClose = () => {
setItemAnchor(null);
};
const handleOpen = (index) => {
setItemAnchor(itemRef.current[index]);
};
return (
<>
<Menu
anchorEl={anchor}
open={Boolean(anchor)}
onClose={onClose}
sx={{
width: 320,
maxWidth: '100%',
}}
>
{[1, 2, 3].map((item, index) => (
<MenuItem
ref={(node) => (itemRef.current[index] = node)}
key={index}
onClick={() => handleOpen(index)}
sx={{ p: 2 }}
>
<ListItemIcon>
<DataIcon />
</ListItemIcon>
<Typography>{`item ${item}`}</Typography>
</MenuItem>
))}
</Menu>
<AddDataPopover anchor={itemAnchor} onClose={handleClose} />
</>
);
};
In AddDataPopover, wire up the anchorEl to the Popover and style the component to be rendered beside active MenuItem.
const AddDataPopover = ({ anchor, onClose }) => {
return (
<Popover
id={'my-popover'}
open={Boolean(anchor)}
anchorEl={anchor}
onClose={onClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'center',
horizontal: 'left',
}}
>
<Typography sx={{ p: 2 }}>The content of Data Popover.</Typography>
</Popover>
);
};
I am trying to code a mobile menu, where when the user clicks on the menu icon, the menu is displayed.
I have the following code:
const StyledMenu = withStyles({
paper: {
border: '1px solid #d3d4d5',
},
})((props, ref) => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
{...props}
/>
))`
and the component:
export default function Header(props) {
const [state, setState] = useState({ mobileView: true, drawerOpen: false, anchorEl: null, openItem: null })
const { mobileView, drawerOpen, anchorEl } = state
const classes = useStyles()
useEffect(() => {
const setResponsiveness = () => {
return window.innerWidth < 900
? setState((prevState) => ({ ...prevState, mobileView: true }))
: setState((prevState) => ({ ...prevState, mobileView: false }));
};
setResponsiveness();
window.addEventListener("resize", () => setResponsiveness());
return () => {
window.removeEventListener("resize", () => setResponsiveness());
}
}, []);
const MobileMenu = (props) => {
const handleClick = (id) => {
if (state.openItem === id) {
setState((prevState) => ({ ...prevState, openItem: null }))
} else {
setState((prevState) => ({ ...prevState, openItem: id }))
}
}
const handleDrawerOpen = (event) =>
setState((prevState) => ({ ...prevState, drawerOpen: true, anchorEl: event.currentTarget }));
const handleDrawerClose = () =>
setState((prevState) => ({ ...prevState, drawerOpen: false }));
return (
<Toolbar>
<IconButton
{...{
edge: "start",
color: "inherit",
"aria-label": "menu",
"aria-haspopup": "true",
variant: "contained",
onClick: handleDrawerOpen,
}}
>
<MenuIcon className={classes.menuIcon} fontSize='40px' />
</IconButton>
<StyledMenu
{...{
id: 'custom-menu',
anchor: "left",
open: drawerOpen,
onClose: handleDrawerClose,
anchorEl: anchorEl
}}
>
<List component='nav' className={classes.appMenu} disablePadding>
{menuItems.map((item, index) => {
const hasSubMenus = item.hasOwnProperty('subMenus')
const ListItemLink = () => {
return (
<ListItem key={index} button component={Link} to={item.link} className={classes.menuItem}>
<ListItemText>{item.label}</ListItemText>
</ListItem>
)
}
const ListItemToggle = () => (
<ListItem key={index} button onClick={() => handleClick(index)} className={classes.menuItem}>
<ListItemText>{item.label}</ListItemText>
{index === state.openItem ? <IconExpandLess /> : <IconExpandMore />}
</ListItem>
)
return (
<React.Fragment key={index}>
{!hasSubMenus ? <ListItemLink /> :
<>
<ListItemToggle />
<Collapse in={index === state.openItem} timeout='auto' className={classes.collapseWrapper}>
<div className={classes.collapseDiv}>
<Divider />
<List component='div' disablePadding>
{item.subMenus.map((el, key) => (
<ListItem key={key} button component={Link} to={el.link} className={classes.menuItem}>
<ListItemText inset>{el.label}</ListItemText>
</ListItem>
))}
</List>
</div>
</Collapse>
</>
}
</React.Fragment>
)
})}
</List>
</StyledMenu>
<Logo />
</Toolbar>
)
}
When the clicking on the button, I get the following error:
React does not recognize the getContentAnchorEl prop on a DOM element.
MUI: The anchorEl prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none.
I solved the problem by moving the anchorEl state from parent (Header) component state to local (MobileMenu) component.
I had the same error message and took the hint, "Make sure the element is present in the document or that it's not display none."
First I was looking on SO when I found this question. I've a similar, but different use case with the #mui/system to show/hide different navigation using <Box sx={{. I'm sharing the way I got rid of the same error you report. I was using display: none, so that in changing from lg to md the anchorEl was nested within a parent element that was indeed set to display none (like the error message suggests).
To get rid of display: none I used visibility along with width to collapse like so:
return (
<List id="id-nav" component="nav" className={classes.root}>
<Box sx={{
display: {
lg: 'inherit'
},
visibility: {
sm: 'hidden',
md: 'hidden',
lg: 'visible'
},
width: {
sm: 0,
md: 0,
lg: 'auto'
},
}}
>
{largeNav}
</Box>
<Box sx={{
display: {
sm: 'inherit',
md: 'inherit'
},
visibility: {
sm: 'visible',
md: 'visible',
lg: 'hidden'
},
width: {
sm: 'auto',
md: 'auto',
lg: 0
}
}}
>
{mediumNav}
</Box>
</List>
);
For future people, if you're wrapping your AppBar in a HideOnScroll/Slide component, that can be the source of the issue (and it was for me). Getting rid of that makes the anchorEl work as expected.
See related question/solution here: Material Ui popover is not on right position
I'm have not used material UI before and all its documentation is with reference with function components. I can not figure out why the popover is not working. Whenever I hover over the text the function is triggered but popover is showing up. I am able to access classes with this.props.classes. It would be great if anyone could help.
import React, { Component } from "react";
import Popover from "#material-ui/core/Popover";
import Typography from "#material-ui/core/Typography";
import { withStyles } from "#material-ui/core/styles";
const useStyles = (theme) => ({
root: {
backgroundColor: "red",
height: "30px",
},
lable: {
transform: "translate(5px, 2px) scale(1)",
pointerEvents: "none",
width: "100%",
height: "100%",
padding: "10px",
color: "red",
},
popover: {
pointerEvents: "none",
color:"pink"
},
paper: {
padding: theme.spacing(1),
},
etc:{
color: "red"
}
});
class SomeThing extends Component {
open;
constructor(props){
super(props)
this.handlePopoverClose = this.handlePopoverClose.bind(this);
this.handlePopoverOpen = this.handlePopoverOpen.bind(this);
this.state={
anchorEl: null,
}
}
componentDidMount(){
this.open = Boolean(this.state.anchorEl);
}
handlePopoverOpen = (event) => {
console.log("triggered!!!", event.currentTarget.innerText);
this.setState({ anchorEl: event.currentTarget.innerText });
};
handlePopoverClose = () => {
console.log("triggered again!!!", this.props);
this.setState({ anchorEl: null });
};
render() {
return (
<div>
<Typography
aria-owns={this.open ? "mouse-over-popover" : undefined}
aria-haspopup="true"
className={this.props.classes.etc}
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
className={this.props.classes.popover}
classes={{
paper: this.props.classes.paper
}}
open={this.open}
anchorEl={this.state.anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
onClose={this.handlePopoverClose}
disableRestoreFocus
>
<Typography>I use Popover.</Typography>
</Popover>
</div>
);
}
}
export default withStyles(useStyles)(SomeThing);
You can define your open inside your render method; this way, open will always be a boolean and will not be set to undefined — which is the reason why it's not working.
render() {
const open = Boolean(this.state.anchorEl);
return (
<div>
<Typography
aria-owns={open ? "mouse-over-popover" : undefined}
aria-haspopup="true"
className={this.props.classes.etc}
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
>
Hover with a Popover.
</Typography>
...
</div>
);
}
I have a small sub-window like div in my app, and I need to display a modal inside the sub-window instead of the whole viewport.
So the backdrop of the modal should only cover the sub-window and not the entire screen
I am using material-ui, so any solution native to material-ui is preferred.
Add disablePortal prop to <Dialog> and add styles as given in the code snippet
For some reason the styles applied to root didn't work through classes or className so had to add the style prop
import { makeStyles, DialogContent, Dialog } from '#material-ui/core';
import React from 'react';
const useStyles = makeStyles({
root: {
position: 'absolute',
},
backdrop: {
position: 'absolute',
},
});
interface ISubWindow {
onClose: () => void;
open: boolean;
}
const SubWindow: React.FC<ISubWindow> = ({onClose, open}) => {
const classes = useStyles();
return (
<Dialog
disablePortal
onClose={onClose}
open={open}
fullWidth
className={classes.root}
classes={{
root: classes.root,
}}
BackdropProps={{
classes: { root: classes.backdrop },
}}
style={{ position: 'absolute' }}
>
<DialogContent />
</Dialog>
);
};
export default SubWindow;
#Rahul's example got me most of the way, but it didn't quite work until I combined it with #Scott Sword and #Gretchen F. 's answers from this similar question.
The most important part was setting the position property of the parent div to relative though I also passed in props slightly differently which has worked a little easier for me:
import { makeStyles, DialogContent, Dialog } from '#material-ui/core';
import React from 'react';
const useStyles = makeStyles({
root: {
height: 'max-content',
minHeight: '100%',
},
backdrop: {
position: 'absolute',
},
paperScrollPaper: {
overflow: 'visible',
}
});
interface ISubWindow {
onClose: () => void;
open: boolean;
}
const SubWindow: React.FC<ISubWindow> = ({onClose, open}) => {
const classes = useStyles();
return (
<Dialog
disableAutoFocus//disables rescrolling the window when the dialog is opened
disableEnforceFocus//allows user to interact outside the dialog
disableScrollLock//prevents the div from shrinking to make room for a scrollbar
disablePortal
onClose={onClose}
open={open}
fullWidth
className={classes.root}
classes={{
paperScrollPaper: classes.paperScrollPaper
}}
BackdropProps={{
classes: { root: classes.backdrop },
}}
style={{ position: 'absolute' }}
>
<DialogContent />
</Dialog>
);
};
export default SubWindow;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
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.