I have an array of data and map it into Popover component. These components have MenuItem which contains some data. But they render only the last data, instead of the different data that exists. Example:
Wrong data, should be to 59 as shown below. The textfield and popover contain the same MenuItem components, only thing different is the popovers.
Code:
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
// New section
<Fab
className={classes.fabStyle}
size="small"
variant="outlined"
onClick={handlePopoverClick}
>
<ExpandMoreIcon />
</Fab>
<Popover
id={id}
open={open}
className={classes.popoverContainer}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
PaperProps={{ elevation: 1 }}
>
{action.children.map((child) => {
if (child.type === "link") {
console.log(child.to);
return (
<MenuItem>
<Link
style={{
textDecoration: "none",
color:
themes.default.palette
.text.primary,
}}
to={child.to}
>
{child.label}
{child.to}
</Link>
</MenuItem>
);
} else if (
child.type === "dialog"
) {
return (
<MenuItem
onClick={() =>
child.handleOpenDialog(row)
}
>
{child.label}
</MenuItem>
);
}
})}
</Popover>
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>
);
};
According to the documentation, I can override the style of the selected class by passing a new class under .MuiSelected. something like below:
const useStyles = makeStyles(() => ({
selectedLink: {
"&.Mui-selected": {
backgroundColor: "red",
},
},
}));
Then, I use it:
const MainDrawerMenu: React.FC = () => {
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const classes = useStyles();
// Responsive swipe on mobile
const iOS =
typeof navigator !== "undefined" &&
/iPad|iPhone|iPod/.test(navigator.userAgent);
const handleSelected = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
selectedIndex: number
) => {
setSelectedIndex(selectedIndex);
};
return (
<React.Fragment>
<SwipeableDrawer
classes={{ paper: classes.drawerMenuHolder }}
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
}}
onOpen={() => {
setIsDrawerOpen(true);
}}>
<List disablePadding>
<ListItemButton
component={Link}
classes={{ selected: classes.selectedLink }}
to='/'
onClick={(event) => {
setIsDrawerOpen(false);
handleSelected(event, 0);
}}
selected={selectedIndex === 0}>
<ListItemText disableTypography className={classes.drawerItem}>
Home
</ListItemText>
</ListItemButton>
</List>
</SwipeableDrawer>
<IconButton
className={classes.iconMenuBtn}
onClick={() => {
setIsDrawerOpen(!isDrawerOpen);
}}>
<MenuIcon fontSize='large' className={classes.menuIcon} />
</IconButton>
</React.Fragment>
);
};
Yet, it doesn't work. I can see it in the dev tools, but for some reason it gets overridden by another class. See screenshot. I have tried also creating a new class with the css naming convention but no luck...
Solution below:
<ListItemButton sx={{
'&.Mui-selected': {
backgroundColor: 'rgba(220, 0, 50, 0.1)'
}
}} selected={0}>
<ListItemText primary={'label in item selected'} />
</ListItemButton>
I'm working on Menu component using material ui , where i need to map menu to storybook. I want user can change the position of menu component as per their need. Can i map anchorOrigin & transformOrigin dynamically. As i'm new to React, not sure how to achieve it. Thanks . Below is my code.
export const Menu= ({ }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<ThemeProvider theme={muiTheme}>
<Button
id="demo-positioned-button"
aria-controls={open ? 'demo-positioned-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
style={{ textTransform: "capitalize" }}
>
Dashboard
</Button>
</ThemeProvider>
<Menu
id="demo-positioned-menu"
aria-labelledby="demo-positioned-button"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
**stories.js**
export const position= Menu.bind({});
I have a menu. in some MenuItems must appear submenu while hovering.
onMouseOver is working correctly but when i leave mouse from menuitem it is not closing
here is my functions
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
if (anchorEl !== event.currentTarget) {
setAnchorEl(event.currentTarget);
}
};
const handleClose = () => {
setAnchorEl(null);
};
const navConf = NavigateConfig();
here is my Jsx
<MenuList sx={{
display: { xs: 'none', lg: 'block' }, display: 'flex'
}}>
{navConf.map((item) => (
<>
<MenuItem
// id="simple-menu"
onMouseOver={item.child ? (e) => handleClick(e) : () => { }}
aria-owns={anchorEl ? "simple-menu" : undefined}
aria-haspopup="true"
>
{item.title}
{anchorEl &&
<Menu
id="simple-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{ onMouseLeave: handleClose, 'aria-labelledby': 'simple-menu', }}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<div className='topDiv'></div>
{item.child?.map((e) => {
return <MenuItem>{e.title}</MenuItem>
})}
</Menu>}
</MenuItem>
</>
))}
<LanguagePopover />
</MenuList>
submenu is disappearing when i click outside of it
Do you just need to add an onMouseLeave?
<MenuItem
onMouseOver={item.child ? (e) => handleClick(e) : () => { }}
onMouseLeave={item.child ? (e) => handleClose() : () => { }}
onMouseOver runs every time your mouse moves and is overlapping the element, but it wont automatically undo what it does when the mouse leaves again.
I followed the documentation to create a dropdown menu using Material UI. However, none of the docs have a good example for handling multiple dropdowns in the same menu.
I got it mostly working - however, when I open a dropdown, they ALL open. I'm assuming this is because open = Boolean(anchorEl) opens the menu whenever an anchorEl is set. So how can I adjust this so it only opens the specific menu that is clicked?
const NavBarMainMenu = () => {
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const handleClick = event => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
return (
<>
<Box sx={{ flexGrow: 1, display: { xs: "none", lg: "flex" } }}>
{pages.map(page => {
return (
<>
<Button
key={page.title}
id={page.title + "-button"}
onClick={handleClick}
aria-controls={open ? page.title : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
>
{page.title}
</Button>
<Menu
anchorEl={anchorEl}
id={page.title}
open={open}
onClose={handleClose}
onClick={handleClose}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
>
{page.children.map(child => {
return <MenuItem key={child.title}>{child.title}</MenuItem>
})}
</Menu>
</>
)
})}
</Box>
</>
)
}
You can try something like this:
const NavBarMainMenu = () => {
const [anchorEls, setAnchorEls] = useState({}) // <-- Here use a object
const isOpen = (id) => Boolean(anchorEls[id]) // <-- Here you need a function to get if open is true
const handleClick = (event, id) => {
setAnchorEsl({ ...anchorEls, [id]: event.currentTarget }) // <-- Here you set anchor value using an id
}
const handleClose = (id) => {
setAnchorEls({ ...anchorEls, [id]: null }) // <-- Here you delete anchor value by ID
}
return (
<>
<Box sx={{ flexGrow: 1, display: { xs: "none", lg: "flex" } }}>
{pages.map(page => {
return (
<>
<Button
key={page.title}
id={page.title + "-button"}
onClick={(event) => handleClick(event, page.title)}
aria-controls={isOpen(page.title) ? page.title : undefined}
aria-haspopup="true"
aria-expanded={isOpen(page.title) ? "true" : undefined}
>
{page.title}
</Button>
<Menu
anchorEl={anchorEls[page.title]}
id={page.title}
open={isOpen(page.title)}
onClose={() => handleClose(page.title)}
onClick={() => handleClose(page.title)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
>
{page.children.map(child => {
return <MenuItem key={child.title}>{child.title}</MenuItem>
})}
</Menu>
</>
)
})}
</Box>
</>
)
}