- Notifications
You must be signed in to change notification settings - Fork31
boilerplate for common Material-UI Menu, Popover and Popper use cases
License
jcoreio/material-ui-popup-state
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Takes care of the boilerplate for common Menu, Popover and Popper use cases.
Provides aCustom React Hook that keeps track of the local state for a single popup, and functions to connect trigger, toggle, andpopover/menu/popper components to the state.
Also provides aRender Props Component thatkeeps track of the local state for a single popup, and passes the state andmutation functions to a child render function.
Requires MUI >= 5.0.0 and React >= 16.8.0.For MUI v4 you'll needmaterial-ui-popup-state@^1.9.3.
- material-ui-popup-state
- Requirements
- Table of Contents
- Installation
- Examples with React Hooks
- React Hooks API
- Examples with Render Props
- Render Props API
- Using
PopoverandMenuwithbindHover - Chaining event handlers
npm install --save material-ui-popup-state
import*asReactfrom'react'importButtonfrom'@mui/material/Button'importMenufrom'@mui/material/Menu'importMenuItemfrom'@mui/material/MenuItem'import{usePopupState,bindTrigger,bindMenu,}from'material-ui-popup-state/hooks'constMenuPopupState=()=>{constpopupState=usePopupState({variant:'popover',popupId:'demoMenu'})return(<div><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Menu</Button><Menu{...bindMenu(popupState)}><MenuItemonClick={popupState.close}>Cake</MenuItem><MenuItemonClick={popupState.close}>Death</MenuItem></Menu></div>)}exportdefaultMenuPopupState
importReactfrom'react'importPropTypesfrom'prop-types'import{withStyles}from'@mui/material/styles'importTypographyfrom'@mui/material/Typography'importButtonfrom'@mui/material/Button'importPopoverfrom'@mui/material/Popover'import{usePopupState,bindTrigger,bindPopover,}from'material-ui-popup-state/hooks'conststyles=(theme)=>({typography:{margin:theme.spacing.unit*2,},})constPopoverPopupState=({ classes})=>{constpopupState=usePopupState({variant:'popover',popupId:'demoPopover',})return(<div><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Popover</Button><Popover{...bindPopover(popupState)}anchorOrigin={{vertical:'bottom',horizontal:'center',}}transformOrigin={{vertical:'top',horizontal:'center',}}><TypographyclassName={classes.typography}> The content of the Popover.</Typography></Popover></div>)}PopoverPopupState.propTypes={classes:PropTypes.object.isRequired,}exportdefaultwithStyles(styles)(PopoverPopupState)
importReactfrom'react'importPropTypesfrom'prop-types'import{withStyles}from'@mui/material/styles'importTypographyfrom'@mui/material/Typography'importButtonfrom'@mui/material/Button'importPopperfrom'@mui/material/Popper'import{usePopupState,bindToggle,bindPopper,}from'material-ui-popup-state/hooks'importFadefrom'@mui/material/Fade'importPaperfrom'@mui/material/Paper'conststyles=(theme)=>({typography:{padding:theme.spacing.unit*2,},})constPopperPopupState=({ classes})=>{constpopupState=usePopupState({variant:'popper',popupId:'demoPopper'})return(<div><Buttonvariant="contained"{...bindToggle(popupState)}> Toggle Popper</Button><Popper{...bindPopper(popupState)}transition>{({ TransitionProps})=>(<Fade{...TransitionProps}timeout={350}><Paper><TypographyclassName={classes.typography}> The content of the Popper.</Typography></Paper></Fade>)}</Popper></div>)}PopperPopupState.propTypes={classes:PropTypes.object.isRequired,}exportdefaultwithStyles(styles)(PopperPopupState)
material-ui-popup-state/hooks exports several helper functions you can use toconnect components easily:
anchorRef: creates areffunction to pass to theanchorEl(by default, thecurrentTargetof the mouse event that triggered the popupis used; only useanchorRefif you want a different element to be the anchor).bindMenu: creates props to control aMenucomponent.bindPopover: creates props to control aPopovercomponent.bindPopper: creates props to control aPoppercomponent.bindDialog: creates props to control aDialogcomponent.bindTrigger: creates props for a component that opens the popup when clicked.bindContextMenu: creates props for a component that opens the popup on when right clicked (contextmenuevent).NOTE:bindPopover/bindMenuwill position the Popover/Menu to thecontextmenuevent location. To positionusing thecontextmenutarget element instead, passanchorReference="anchorEl"after{...bindPopover(popupState)}/{...bindMenu(popupState)}.bindToggle: creates props for a component that toggles the popup when clicked.bindHover: creates props for a component that opens the popup while hovered.NOTE: Seethis guidance if you are usingbindHoverwithPopoverorMenu.bindFocus: creates props for a component that opens the popup while focus.bindDoubleClick: creates props for a component that opens the popup while double click.
To use one of these functions, you should call it with the objectreturned byusePopupState and spread the return value into the desiredelement:
import*asReactfrom'react'importButtonfrom'@mui/material/Button'importMenufrom'@mui/material/Menu'importMenuItemfrom'@mui/material/MenuItem'import{usePopupState,bindTrigger,bindMenu,}from'material-ui-popup-state/hooks'constMenuPopupState=()=>{constpopupState=usePopupState({variant:'popover',popupId:'demoMenu'})return(<div><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Menu</Button><Menu{...bindMenu(popupState)}><MenuItemonClick={popupState.close}>Cake</MenuItem><MenuItemonClick={popupState.close}>Death</MenuItem></Menu></div>)}exportdefaultMenuPopupState
This is aCustom Hook that usesuseState internally, therefore theRules of Hooks apply tousePopupState.
Use'popover' if your popup is aPopover orMenu; use'popper' if yourpopup is aPopper.
Right now this only affects whetherbindTrigger/bindToggle/bindHover returnanaria-controls prop or anaria-describedby prop.
Theid for the popup component. It will be passed to the child props so thatthe trigger component may declare the same id in an ARIA prop.
Defaults toReact.useId() ifReact.useId exists; in older versions of Reactyou will have to manually provide apopupId.
Iftrue, will not steal focus when the popup is opened. (AndbindPopover/bindMenu will injectdisableAutoFocus,disableEnforceFocus, anddisableRestoreFocus).
Defaults totrue when the popup is opened by thebindHover orbindFocus element.
An object with the following properties:
open([eventOrAnchorEl]): opens the popup. You must pass in an anchor element or an event with acurrentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.close(): closes the popuptoggle([eventOrAnchorEl]): opens the popup if it is closed, or closes the popup if it is open. If the popup is currently closed, you must pass an anchor element or an event with acurrentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.setOpen(open, [eventOrAnchorEl]): sets whether the popup is open. Ifopenis truthy, you must pass in an anchor element or an event with acurrentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.isOpen:true/falseif the popup is open/closedanchorEl: the current anchor elementanchorPosition: the current anchor positionsetAnchorEl: sets the anchor element (thecurrentTargetof the triggeringmouse event is used by default unless you have calledsetAnchorEl)popupId: thepopupIdprop you passed toPopupStatevariant: thevariantprop you passed toPopupState
import*asReactfrom'react'importButtonfrom'@mui/material/Button'importMenufrom'@mui/material/Menu'importMenuItemfrom'@mui/material/MenuItem'importPopupState,{bindTrigger,bindMenu}from'material-ui-popup-state'constMenuPopupState=()=>(<PopupStatevariant="popover"popupId="demoMenu">{(popupState)=>(<React.Fragment><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Menu</Button><Menu{...bindMenu(popupState)}><MenuItemonClick={popupState.close}>Cake</MenuItem><MenuItemonClick={popupState.close}>Death</MenuItem></Menu></React.Fragment>)}</PopupState>)exportdefaultMenuPopupState
importReactfrom'react'importPropTypesfrom'prop-types'import{withStyles}from'@mui/material/styles'importTypographyfrom'@mui/material/Typography'importButtonfrom'@mui/material/Button'importPopoverfrom'@mui/material/Popover'importPopupState,{bindTrigger,bindPopover}from'material-ui-popup-state'conststyles=(theme)=>({typography:{margin:theme.spacing.unit*2,},})constPopoverPopupState=({ classes})=>(<PopupStatevariant="popover"popupId="demoPopover">{(popupState)=>(<div><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Popover</Button><Popover{...bindPopover(popupState)}anchorOrigin={{vertical:'bottom',horizontal:'center',}}transformOrigin={{vertical:'top',horizontal:'center',}}><TypographyclassName={classes.typography}> The content of the Popover.</Typography></Popover></div>)}</PopupState>)PopoverPopupState.propTypes={classes:PropTypes.object.isRequired,}exportdefaultwithStyles(styles)(PopoverPopupState)
importReactfrom'react'importPropTypesfrom'prop-types'import{withStyles}from'@mui/material/styles'importTypographyfrom'@mui/material/Typography'importHoverPopoverfrom'material-ui-popup-state/HoverPopover'importPopupState,{bindHover,bindPopover}from'material-ui-popup-state'conststyles=(theme)=>({popover:{pointerEvents:'none',},paper:{padding:theme.spacing.unit,},})constHoverPopoverPopupState=({ classes})=>(<PopupStatevariant="popover"popupId="demoPopover">{(popupState)=>(<div><Typography{...bindHover(popupState)}> Hover with a Popover.</Typography><HoverPopover{...bindPopover(popupState)}className={classes.popover}classes={{paper:classes.paper,}}anchorOrigin={{vertical:'bottom',horizontal:'center',}}transformOrigin={{vertical:'top',horizontal:'center',}}><Typography>The content of the Popover.</Typography></HoverPopover></div>)}</PopupState>)HoverPopoverPopupState.propTypes={classes:PropTypes.object.isRequired,}exportdefaultwithStyles(styles)(HoverPopoverPopupState)
importReactfrom'react'importPropTypesfrom'prop-types'import{withStyles}from'@mui/material/styles'importTypographyfrom'@mui/material/Typography'importButtonfrom'@mui/material/Button'importPopperfrom'@mui/material/Popper'importPopupState,{bindToggle,bindPopper}from'material-ui-popup-state'importFadefrom'@mui/material/Fade'importPaperfrom'@mui/material/Paper'conststyles=(theme)=>({typography:{padding:theme.spacing.unit*2,},})constPopperPopupState=({ classes})=>(<PopupStatevariant="popper"popupId="demoPopper">{(popupState)=>(<div><Buttonvariant="contained"{...bindToggle(popupState)}> Toggle Popper</Button><Popper{...bindPopper(popupState)}transition>{({ TransitionProps})=>(<Fade{...TransitionProps}timeout={350}><Paper><TypographyclassName={classes.typography}> The content of the Popper.</Typography></Paper></Fade>)}</Popper></div>)}</PopupState>)PopperPopupState.propTypes={classes:PropTypes.object.isRequired,}exportdefaultwithStyles(styles)(PopperPopupState)
material-ui-popup-state exports several helper functions you can use toconnect components easily:
anchorRef: creates areffunction to pass to theanchorEl(by default, thecurrentTargetof the mouse event that triggered the popupis used; only useanchorRefif you want a different element to be the anchor).bindMenu: creates props to control aMenucomponent.bindPopover: creates props to control aPopovercomponent.bindPopper: creates props to control aPoppercomponent.bindDialog: creates props to control aDialogcomponent.bindTrigger: creates props for a component that opens the popup when clicked.bindContextMenu: creates props for a component that opens the popup on when right clicked (contextmenuevent).NOTE:bindPopover/bindMenuwill position the Popover/Menu to thecontextmenuevent location. To positionusing thecontextmenutarget element instead, passanchorReference="anchorEl"after{...bindPopover(popupState)}/{...bindMenu(popupState)}.bindToggle: creates props for a component that toggles the popup when clicked.bindHover: creates props for a component that opens the popup while hovered.NOTE: Seethis guidance if you are usingbindHoverwithPopoverorMenu.bindFocus: creates props for a component that opens the popup while hovered.
To use one of these functions, you should call it with the propsPopupStatepassed to your child function, and spread the return value into the desiredelement:
import*asReactfrom'react'importButtonfrom'@mui/material/Button'importMenufrom'@mui/material/Menu'importMenuItemfrom'@mui/material/MenuItem'importPopupState,{bindTrigger,bindMenu}from'material-ui-popup-state'constMenuPopupState=()=>(<PopupStatevariant="popover"popupId="demoMenu">{(popupState)=>(<React.Fragment><Buttonvariant="contained"{...bindTrigger(popupState)}> Open Menu</Button><Menu{...bindMenu(popupState)}><MenuItemonClick={popupState.close}>Cake</MenuItem><MenuItemonClick={popupState.close}>Death</MenuItem></Menu></React.Fragment>)}</PopupState>)exportdefaultMenuPopupState
Use'popover' if your popup is aPopover orMenu; use'popper' if yourpopup is aPopper.
Right now this only affects whetherbindTrigger/bindToggle/bindHover returnanaria-controls prop or anaria-describedby prop.
Theid for the popup component. It will be passed to the child props so thatthe trigger component may declare the same id in an ARIA prop.
Defaults toReact.useId() ifReact.useId exists; in older versions of Reactyou will have to manually provide apopupId.
Iftrue, will not steal focus when the popup is opened. (AndbindPopover/bindMenu will injectdisableAutoFocus,disableEnforceFocus, anddisableRestoreFocus).
Defaults totrue when the popup is opened by thebindHover orbindFocus element.
The render function. It will be called with an object containing the followingprops (exported as theInjectedProps type):
open([eventOrAnchorEl]): opens the popupclose(): closes the popuptoggle([eventOrAnchorEl]): opens the popup if it is closed, or closes the popup if it is open.setOpen(open, [eventOrAnchorEl]): sets whether the popup is open.isOpen:true/falseif the popup is open/closedanchorEl: the current anchor elementanchorPosition: the current anchor positionsetAnchorEl: sets the anchor element (thecurrentTargetof the triggeringmouse event is used by default unless you have calledsetAnchorEl)popupId: thepopupIdprop you passed toPopupStatevariant: thevariantprop you passed toPopupState
MUI'sModal (used byPopover andMenu) blocks pointer events to all other components, interfering withbindHover(the popover or menu will open when the mouse enters thebindHover element, but won't close when the mouse leaves). You canuse the following components to work around this:
importHoverMenufrom'material-ui-popup-state/HoverMenu'importHoverPopoverfrom'material-ui-popup-state/HoverPopover'
These are just wrapper components that pass inline styles to preventModal from blocking pointer events.
What if you need to perform additional actions inonClick, but it's being injected by{...bindTrigger(popupState)} etc?
There are two options:
This is the most straightforward, explicit option.
constbutton=(<Button{...bindTrigger(popupState)}onClick={(e:React.MouseEvent)=>{bindTrigger(popupState).onClick(e)performCustomAction(e)}}> Open Menu</Button>)
If you don't like the above option, you can use the providedmaterial-ui-popup-state/chainEventHandlers helper:
import{chainEventHandlers}from'material-ui-popup-state/chainEventHandlers'constbutton=(<Button{...chainEventHandlers(bindTrigger(popupState),{onClick:(e:React.MouseEvent)=>{bindTrigger(popupState).onClick(e)performCustomAction(e)},})}> Open Menu</Button>)
chainEventHandlers accepts a variable number of props arguments and combines any function props of the same nameinto a function that invokes the chained functions in sequence. For all other properties the behavior is likeObject.assign.
Warning
chainEventHandlers doesn't memoize the combined event handler functions, so they will cause components torerender. If you need memoized functions, you will need to perform the memoization with your own code, for exampleusingReact.useCallback andchaining event handlers manually.
About
boilerplate for common Material-UI Menu, Popover and Popper use cases
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors11
Uh oh!
There was an error while loading.Please reload this page.