Position Menu component using material UI react storybook - reactjs

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({});

Related

Mui dropdown menu is not close in onMouseLeave

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.

Open a Single Dropdown Menu (Instead of all of them)

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>
</>
)
}

React MUI popover contains wrong data

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>

Factoring material UI popover into a higher order component and keep the popover state in the `hoc`

I am following the react material ui doc here: https://mui.com/components/popover/, and I'm attempting to factor all the popover view and state logic into a higher order component. The one from the doc is here:
function BasicPopoverFromDoc(props) {
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
style={{color:'white'}}
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
I lifted all the state logic into a hoc as follows, taking <Child/> as a param:
function WithPopOver(props){
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const { Child } = props;
console.log('with Popover: ', open)
return (
<div>
<Child
{...props}
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
/>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
but when i try to use it this way:
function BasicPopover(props){
return (
<WithPopOver
{...props}
Child={() => <Typography style={{color:'white'}}> wrapped in hoc </Typography>}
/>
)
}
It seems like the popover does not display. It refuses to display even when I change open to true by force. What am i missing here?
You haven't passed Child props to it's children, so onMouseEnter and onMouseLeave never fires, try on this:
<WithPopOver
{...props}
Child={(props) => <Typography style={{color:'white'}} {...props}> wrapped in hoc </Typography>}
/>

how to handle multiple popover (material ui)

I tried to use multiple popover within a component. For ex, in below code I have two popovers, but when clicking any of the two button, both the popovers are opened. How can we handle the onclick to open corresponding popover?
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
<Tabs variant="fullWidth" value={value} onChange={onNavChange} indicatorColor="transparent">
<Tab label="Menu 1" className={classes.navTab} component={Link} to="./Menu1"></Tab>
<Tab label="Menu 2" onClick={handleClick} aria-describedby="menu2Popover" aria-haspopup="true"></Tab>
<Tab label="Menu 3" component={Link} to="./Menu3"></Tab>
<Tab label="Menu 4"onClick={handleClick} aria-describedby="menu4Popover" aria-haspopup="true"></Tab>
</Tabs>
<Popover
id="menu2Popover" open={Boolean(anchorEl)} onClose={handleClose}
anchorEl = {anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}>
<MenuList>
<MenuItem>Submenu 1</MenuItem>
<MenuItem>Submenu 2</MenuItem>
</MenuList>
</Popover>
<Popover
id="menu4Popover" open={Boolean(anchorEl)} onClose={handleClose}
anchorEl = {anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}>
<MenuList>
<MenuItem>Submenu 3</MenuItem>
<MenuItem>Submenu 4</MenuItem>
</MenuList>
</Popover>
You need to use different anchorEl's for each Popover:
import * as React from "react";
import { render } from "react-dom";
import { MenuList, MenuItem, Popover, Tabs, Tab } from "#material-ui/core";
import "./styles.css";
interface CustomMenuItem {
anchorEl: null | HTMLElement;
child: any;
}
function Popover1() {
return (
<MenuList>
<MenuItem>Submenu 1</MenuItem>
<MenuItem>Submenu 2</MenuItem>
</MenuList>
);
}
function Popover2() {
return (
<MenuList>
<MenuItem>Submenu 3</MenuItem>
<MenuItem>Submenu 4</MenuItem>
</MenuList>
);
}
function App() {
const [popover1, setPopover1] = React.useState<CustomMenuItem>({
anchorEl: null,
child: <Popover1 />
});
const [popover2, setPopover2] = React.useState<CustomMenuItem>({
anchorEl: null,
child: <Popover2 />
});
return (
<div className="App">
<Tabs variant="fullWidth" indicatorColor="transparent">
<Tab label="Menu 1" />
<Tab
value="Tab2"
label="Menu 2"
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setPopover1({ ...popover1, anchorEl: event.currentTarget })
}
aria-describedby="menu2Popover"
aria-haspopup="true"
/>
<Tab label="Menu 3" />
<Tab
label="Menu 4"
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setPopover2({ ...popover2, anchorEl: event.currentTarget })
}
aria-describedby="menu4Popover"
aria-haspopup="true"
/>
</Tabs>
<Popover
id="menu2Popover"
open={Boolean(popover1.anchorEl)}
onClose={() => setPopover1({ ...popover1, anchorEl: null })}
anchorEl={popover1.anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "left"
}}
transformOrigin={{
vertical: "bottom",
horizontal: "left"
}}
>
{popover1.child}
</Popover>
<Popover
id="menu4Popover"
open={Boolean(popover2.anchorEl)}
onClose={() => setPopover2({ ...popover2, anchorEl: null })}
anchorEl={popover2.anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "left"
}}
transformOrigin={{
vertical: "bottom",
horizontal: "left"
}}
>
{popover2.child}
</Popover>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Look at the codesandbox. If you have any further questions let me know and I can update my answer.
another sample with one Popover
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import Popover from "#material-ui/core/Popover";
import MenuList from "#material-ui/core/MenuList";
import MenuItem from "#material-ui/core/MenuItem";
const styles = theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
textTransform: "none"
}
});
class SimpleTabs extends React.Component {
state = {
value: 0,
anchorEl: null,
popno: -1
};
handlePopoverClose = () => {
this.setState({ anchorEl: null, popno: -1 });
};
handleClick = (e, _popno) => {
this.setState({ anchorEl: e.currentTarget, popno: _popno });
};
handleChange = (event, value) => {
this.setState({ value });
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={this.handleChange}>
<Tab label="Tab 1" onClick={e => this.handleClick(e, 1)} />
<Tab label="Tab 2" onClick={e => this.handleClick(e, 2)} />
<Tab label="Tab 3" onClick={e => this.handleClick(e, 3)} />
</Tabs>
<Popover
id="menu2Popover"
open={this.state.anchorEl !== null}
onClose={this.handlePopoverClose}
anchorEl={this.state.anchorEl}
>
{this.state.popno === 1 && (
<MenuList>
<MenuItem>Tab 1 - Submenu 1</MenuItem>
<MenuItem>Tab 1 - Submenu 2</MenuItem>
</MenuList>
)}
{this.state.popno === 2 && (
<MenuList>
<MenuItem>Tab 2 - Submenu 1</MenuItem>
<MenuItem>Tab 2 - Submenu 2</MenuItem>
</MenuList>
)}
{this.state.popno === 3 && (
<MenuList>
<MenuItem>Tab 3 - Submenu 1</MenuItem>
<MenuItem>Tab 3 - Submenu 2</MenuItem>
</MenuList>
)}
</Popover>
</AppBar>
</div>
);
}
}
export default withStyles(styles)(SimpleTabs);
answer output https://codesandbox.io/s/material-tabs-demo-tpugw

Resources