I am building a reactjs application -- and I am trying to make this popup component modular -so that I could have the button look like a badge/icon combination -- activate the popup menu on hover instead of clicks.
here is a sandbox -- but I need to create popover menus for each -- at the moment its displacing the buttons.
https://codesandbox.io/s/material-demo-forked-wrn2g?file=/demo.js
Here is the component as it is currently http://jsfiddle.net/4benm6wo/
import React from 'react';
import Button from '@material-ui/core/Button';
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';
import Badge from '@material-ui/core/Badge';
import PersonIcon from '@material-ui/icons/Person';
import './PopOverMenu.scss';
export default function MenuListComposition() {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
function handleListKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus();
}
prevOpen.current = open;
}, [open]);
return (
<div className="popover-menu">
<div>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<Badge badgeContent={11} color="primary">
<PersonIcon />
</Badge>
</Button>
<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 autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
);
}
but I want to create a popperMenu component -- where I push the icon, badge count into the poppup -- so I need help implement it with props and states.
this is my current attempt http://jsfiddle.net/4benm6wo/1/
class MenuListComposition extends Component {
constructor(props, context) {
super(props, context);
this.state = { open: false };
}
render() {
return (
<div className="popover-menu">
<div>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<Badge badgeContent={11} color="primary">
<PersonIcon />
</Badge>
</Button>
<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 autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
)
}
}
export default MenuListComposition;
so I would create a button/badge popup kind of like this from the shell
<MenuListComposition badgeCount={10} icon={<PersonIcon />} menu={[{ "label": "Profile", "link": "user/1" }, { "label": "Logout", "link": "logout" }]} />
latest code
LoginButton.js http://jsfiddle.net/z3L89x2e/
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Button from '@material-ui/core/Button';
import PopOverMenu from '../_SharedGlobalComponents/PopOverMenu/PopOverMenu';
import MailIcon from '@material-ui/icons/Mail';
import NotificationsIcon from '@material-ui/icons/Notifications';
import PersonIcon from '@material-ui/icons/Person';
class LoggedInButtons extends Component {
/*
constructor(props, context) {
super(props, context);
}
*/
render() {
return (
<div className="login-badges">
<PopOverMenu
icon={<NotificationsIcon />}
badgecount="3"
menu={[
{ "label": "xxx", "value": "/" },
{ "label": "xxxx", "value": "/" },
{ "label": "xxxxx", "value": "/" },
]}
/>
<PopOverMenu
icon={<MailIcon />}
badgecount="5"
menu={[
{ "label": "xxx", "value": "/" },
{ "label": "xxxx", "value": "/" },
{ "label": "xxxxx", "value": "/" },
]}
/>
<PopOverMenu
icon={<PersonIcon />}
badgecount="2"
menu={[
{ "label": "Profile", "value": "/profile" },
{ "label": "My account", "value": "/my-account" },
{ "label": "Logout", "value": "/logout" },
]}
/>
<Button
variant="text"
color="default"
startIcon={<PersonIcon />}
href="/user/view/2"
>
User 2
</Button>
<Button variant="contained" color="secondary" href="/logout">
log out
</Button>
</div>
)
}
}
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ }, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoggedInButtons))
PopOverMenu.js http://jsfiddle.net/z3L89x2e/2/
import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
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';
import Badge from '@material-ui/core/Badge';
//import './PopOverMenu.scss';
class PopOverMenu extends Component {
constructor(props, context) {
super(props, context);
this.state = { open: false };
this.anchorRef = React.createRef(null);
}
handleToggle = () => {
this.setState({open: !this.state.open});
};
handleClose = () => {
this.setState({open: false});
};
handleListKeyDown = (event) => {
if (event.key === 'Tab') {
event.preventDefault();
this.setState({open: false});
}
}
showMenuItems = () => (
this.props.menu.map((item, i) => (
<MenuItem onClick={this.handleClose}>{item.label}</MenuItem>
))
)
render() {
return (
<div style={{display:'inline', float:'left', marginLeft:'20px', marginTop:'10px'}} className="popover-menu">
<Button
ref={this.anchorRef}
aria-controls={this.state.open ? 'menu-list-grow' : null}
aria-haspopup="true"
onClick={this.handleToggle}
>
<Badge badgeContent={this.props.badgecount} color="primary">
{this.props.icon}
</Badge>
</Button>
<Popper style={{position: 'relative'}} open={this.state.open} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={this.handleClose}>
<MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
{this.showMenuItems()}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
);
}
}
export default PopOverMenu;
this is the latest attempt - it nearly worked - but then I got errors in the ancor el
I am using material ui and the icon/badge modules. I started to get an error on hovering over the menu.
I've tried to follow something like this - of putting the ancor el into the state - but its not working.
How can I convert popover MATERIAL-UI functional component to class based component?
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import Button from '@material-ui/core/Button';
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';
import Badge from '@material-ui/core/Badge';
import './PopOverMenu.scss';
class PopOverMenu extends Component {
constructor(props, context) {
super(props, context);
// this.state = { open: false};
// this.anchorRef = React.createRef(null);
this.state = { anchorEl: null, open: false };
}
handleToggle = (event) => {
this.setState({open: !this.state.open});
// this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
};
handleOpen = (event) => {
this.setState({open: true});
console.log("event.currentTarget", event.currentTarget);
this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({open: false});
//this.setState({ anchorEl: null })
};
handleListKeyDown = (event) => {
if (event.key === 'Tab') {
event.preventDefault();
this.setState({open: false});
}
}
showMenuItems = () => (
this.props.menu.map((item, i) => (
<MenuItem key={i} onClick={this.handleClose}>
<NavLink to={item.value}>{item.label}</NavLink>
</MenuItem>
))
)
render() {
//console.log("this.anchorRef", this.anchorRef)
return (
<div className="popover-menu">
<Button
//ref={this.anchorRef}
aria-controls={this.state.open ? 'menu-list-grow' : null}
aria-haspopup="true"
onClick={this.handleToggle}
onMouseOver={this.handleOpen}
onMouseLeave={this.handleClose}
>
<Badge badgeContent={this.props.badgecount} color="primary">
{this.props.icon}
</Badge>
</Button>
<Popper
className="popper-list"
//anchorEl={this.anchorRef}
open={this.state.open}
anchorEl={this.state.anchorEl}
//role={undefined}
transition
disablePortal
onMouseOver={this.handleOpen}
onMouseLeave={this.handleClose}
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={this.handleClose}>
<MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
{/*this.showMenuItems()*/}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
);
}
}
export default PopOverMenu;
.popover-menu{
width: 40px;
display: inline;
float: left;
//border: 1px solid red;
//background: blue;
padding: 5px;
.popper-list{
width: 160px;
position: relative!important;
//border: 1px solid blue;
}
}