boilerplate for common Material-UI Menu, Popover and Popper use cases
Installations
npm install material-ui-popup-state
Developer
Developer Guide
Module System
CommonJS, ESM
Min. Node Version
>=16
Typescript Support
Yes
Node Version
20.10.0
NPM Version
10.2.3
Statistics
455 Stars
550 Commits
28 Forks
7 Watching
17 Branches
8 Contributors
Updated on 22 Nov 2024
Bundle Size
6.47 kB
Minified
2.24 kB
Minified + Gzipped
Languages
TypeScript (97.06%)
JavaScript (2.94%)
Total Downloads
Cumulative downloads
Total Downloads
32,665,452
Last day
-5.2%
44,377
Compared to previous day
Last week
3.2%
235,033
Compared to previous week
Last month
11.5%
1,022,178
Compared to previous month
Last year
13.1%
11,228,187
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Peer Dependencies
2
material-ui-popup-state
Takes care of the boilerplate for common Menu, Popover and Popper use cases.
Provides a Custom React Hook that keeps track of the local state for a single popup, and functions to connect trigger, toggle, and popover/menu/popper components to the state.
Also provides a Render Props Component that keeps track of the local state for a single popup, and passes the state and mutation functions to a child render function.
Requirements
Requires MUI >= 5.0.0 and React >= 16.8.0.
For MUI v4 you'll need material-ui-popup-state@^1.9.3
.
Table of Contents
- material-ui-popup-state
- Requirements
- Table of Contents
- Installation
- Examples with React Hooks
- React Hooks API
- Examples with Render Props
- Render Props API
- Using
Popover
andMenu
withbindHover
- Chaining event handlers
Installation
1npm install --save material-ui-popup-state
Examples with React Hooks
Menu
1import * as React from 'react' 2import Button from '@mui/material/Button' 3import Menu from '@mui/material/Menu' 4import MenuItem from '@mui/material/MenuItem' 5import { 6 usePopupState, 7 bindTrigger, 8 bindMenu, 9} from 'material-ui-popup-state/hooks' 10 11const MenuPopupState = () => { 12 const popupState = usePopupState({ variant: 'popover', popupId: 'demoMenu' }) 13 return ( 14 <div> 15 <Button variant="contained" {...bindTrigger(popupState)}> 16 Open Menu 17 </Button> 18 <Menu {...bindMenu(popupState)}> 19 <MenuItem onClick={popupState.close}>Cake</MenuItem> 20 <MenuItem onClick={popupState.close}>Death</MenuItem> 21 </Menu> 22 </div> 23 ) 24} 25 26export default MenuPopupState
Popover
1import React from 'react' 2import PropTypes from 'prop-types' 3import { withStyles } from '@mui/material/styles' 4import Typography from '@mui/material/Typography' 5import Button from '@mui/material/Button' 6import Popover from '@mui/material/Popover' 7import { 8 usePopupState, 9 bindTrigger, 10 bindPopover, 11} from 'material-ui-popup-state/hooks' 12 13const styles = (theme) => ({ 14 typography: { 15 margin: theme.spacing.unit * 2, 16 }, 17}) 18 19const PopoverPopupState = ({ classes }) => { 20 const popupState = usePopupState({ 21 variant: 'popover', 22 popupId: 'demoPopover', 23 }) 24 return ( 25 <div> 26 <Button variant="contained" {...bindTrigger(popupState)}> 27 Open Popover 28 </Button> 29 <Popover 30 {...bindPopover(popupState)} 31 anchorOrigin={{ 32 vertical: 'bottom', 33 horizontal: 'center', 34 }} 35 transformOrigin={{ 36 vertical: 'top', 37 horizontal: 'center', 38 }} 39 > 40 <Typography className={classes.typography}> 41 The content of the Popover. 42 </Typography> 43 </Popover> 44 </div> 45 ) 46} 47 48PopoverPopupState.propTypes = { 49 classes: PropTypes.object.isRequired, 50} 51 52export default withStyles(styles)(PopoverPopupState)
Popper
1import React from 'react' 2import PropTypes from 'prop-types' 3import { withStyles } from '@mui/material/styles' 4import Typography from '@mui/material/Typography' 5import Button from '@mui/material/Button' 6import Popper from '@mui/material/Popper' 7import { 8 usePopupState, 9 bindToggle, 10 bindPopper, 11} from 'material-ui-popup-state/hooks' 12import Fade from '@mui/material/Fade' 13import Paper from '@mui/material/Paper' 14 15const styles = (theme) => ({ 16 typography: { 17 padding: theme.spacing.unit * 2, 18 }, 19}) 20 21const PopperPopupState = ({ classes }) => { 22 const popupState = usePopupState({ variant: 'popper', popupId: 'demoPopper' }) 23 return ( 24 <div> 25 <Button variant="contained" {...bindToggle(popupState)}> 26 Toggle Popper 27 </Button> 28 <Popper {...bindPopper(popupState)} transition> 29 {({ TransitionProps }) => ( 30 <Fade {...TransitionProps} timeout={350}> 31 <Paper> 32 <Typography className={classes.typography}> 33 The content of the Popper. 34 </Typography> 35 </Paper> 36 </Fade> 37 )} 38 </Popper> 39 </div> 40 ) 41} 42 43PopperPopupState.propTypes = { 44 classes: PropTypes.object.isRequired, 45} 46 47export default withStyles(styles)(PopperPopupState)
React Hooks API
Bind Functions
material-ui-popup-state/hooks
exports several helper functions you can use to
connect components easily:
anchorRef
: creates aref
function to pass to theanchorEl
(by default, thecurrentTarget
of the mouse event that triggered the popup is used; only useanchorRef
if you want a different element to be the anchor).bindMenu
: creates props to control aMenu
component.bindPopover
: creates props to control aPopover
component.bindPopper
: creates props to control aPopper
component.bindDialog
: creates props to control aDialog
component.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 (contextmenu
event). NOTE:bindPopover
/bindMenu
will position the Popover/Menu to thecontextmenu
event location. To position using thecontextmenu
target 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: See this guidance if you are usingbindHover
withPopover
orMenu
.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 object
returned by usePopupState
and spread the return value into the desired
element:
1import * as React from 'react' 2import Button from '@mui/material/Button' 3import Menu from '@mui/material/Menu' 4import MenuItem from '@mui/material/MenuItem' 5import { 6 usePopupState, 7 bindTrigger, 8 bindMenu, 9} from 'material-ui-popup-state/hooks' 10 11const MenuPopupState = () => { 12 const popupState = usePopupState({ variant: 'popover', popupId: 'demoMenu' }) 13 return ( 14 <div> 15 <Button variant="contained" {...bindTrigger(popupState)}> 16 Open Menu 17 </Button> 18 <Menu {...bindMenu(popupState)}> 19 <MenuItem onClick={popupState.close}>Cake</MenuItem> 20 <MenuItem onClick={popupState.close}>Death</MenuItem> 21 </Menu> 22 </div> 23 ) 24} 25 26export default MenuPopupState
usePopupState
This is a Custom Hook that uses useState
internally, therefore the Rules of Hooks apply to usePopupState
.
usePopupState
Props
variant
('popover'
, 'popper'
, or 'dialog'
, required)
Use 'popover'
if your popup is a Popover
or Menu
; use 'popper'
if your
popup is a Popper
.
Right now this only affects whether bindTrigger
/bindToggle
/bindHover
return
an aria-controls
prop or an aria-describedby
prop.
popupId
(string
, optional)
The id
for the popup component. It will be passed to the child props so that
the trigger component may declare the same id in an ARIA prop.
Defaults to React.useId()
if React.useId
exists; in older versions of React
you will have to manually provide a popupId
.
disableAutoFocus
(boolean
, optional)
If true
, will not steal focus when the popup is opened. (And bindPopover
/bindMenu
will inject disableAutoFocus
, disableEnforceFocus
, and disableRestoreFocus
).
Defaults to true
when the popup is opened by the bindHover
or bindFocus
element.
usePopupState
return value
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. Ifopen
is 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
/false
if the popup is open/closedanchorEl
: the current anchor elementanchorPosition
: the current anchor positionsetAnchorEl
: sets the anchor element (thecurrentTarget
of the triggering mouse event is used by default unless you have calledsetAnchorEl
)popupId
: thepopupId
prop you passed toPopupState
variant
: thevariant
prop you passed toPopupState
Examples with Render Props
Menu
1import * as React from 'react' 2import Button from '@mui/material/Button' 3import Menu from '@mui/material/Menu' 4import MenuItem from '@mui/material/MenuItem' 5import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state' 6 7const MenuPopupState = () => ( 8 <PopupState variant="popover" popupId="demoMenu"> 9 {(popupState) => ( 10 <React.Fragment> 11 <Button variant="contained" {...bindTrigger(popupState)}> 12 Open Menu 13 </Button> 14 <Menu {...bindMenu(popupState)}> 15 <MenuItem onClick={popupState.close}>Cake</MenuItem> 16 <MenuItem onClick={popupState.close}>Death</MenuItem> 17 </Menu> 18 </React.Fragment> 19 )} 20 </PopupState> 21) 22 23export default MenuPopupState
Popover
1import React from 'react' 2import PropTypes from 'prop-types' 3import { withStyles } from '@mui/material/styles' 4import Typography from '@mui/material/Typography' 5import Button from '@mui/material/Button' 6import Popover from '@mui/material/Popover' 7import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state' 8 9const styles = (theme) => ({ 10 typography: { 11 margin: theme.spacing.unit * 2, 12 }, 13}) 14 15const PopoverPopupState = ({ classes }) => ( 16 <PopupState variant="popover" popupId="demoPopover"> 17 {(popupState) => ( 18 <div> 19 <Button variant="contained" {...bindTrigger(popupState)}> 20 Open Popover 21 </Button> 22 <Popover 23 {...bindPopover(popupState)} 24 anchorOrigin={{ 25 vertical: 'bottom', 26 horizontal: 'center', 27 }} 28 transformOrigin={{ 29 vertical: 'top', 30 horizontal: 'center', 31 }} 32 > 33 <Typography className={classes.typography}> 34 The content of the Popover. 35 </Typography> 36 </Popover> 37 </div> 38 )} 39 </PopupState> 40) 41 42PopoverPopupState.propTypes = { 43 classes: PropTypes.object.isRequired, 44} 45 46export default withStyles(styles)(PopoverPopupState)
Mouse Over Interaction
1import React from 'react' 2import PropTypes from 'prop-types' 3import { withStyles } from '@mui/material/styles' 4import Typography from '@mui/material/Typography' 5import HoverPopover from 'material-ui-popup-state/HoverPopover' 6import PopupState, { bindHover, bindPopover } from 'material-ui-popup-state' 7 8const styles = (theme) => ({ 9 popover: { 10 pointerEvents: 'none', 11 }, 12 paper: { 13 padding: theme.spacing.unit, 14 }, 15}) 16 17const HoverPopoverPopupState = ({ classes }) => ( 18 <PopupState variant="popover" popupId="demoPopover"> 19 {(popupState) => ( 20 <div> 21 <Typography {...bindHover(popupState)}> 22 Hover with a Popover. 23 </Typography> 24 <HoverPopover 25 {...bindPopover(popupState)} 26 className={classes.popover} 27 classes={{ 28 paper: classes.paper, 29 }} 30 anchorOrigin={{ 31 vertical: 'bottom', 32 horizontal: 'center', 33 }} 34 transformOrigin={{ 35 vertical: 'top', 36 horizontal: 'center', 37 }} 38 > 39 <Typography>The content of the Popover.</Typography> 40 </HoverPopover> 41 </div> 42 )} 43 </PopupState> 44) 45 46HoverPopoverPopupState.propTypes = { 47 classes: PropTypes.object.isRequired, 48} 49 50export default withStyles(styles)(HoverPopoverPopupState)
Popper
1import React from 'react' 2import PropTypes from 'prop-types' 3import { withStyles } from '@mui/material/styles' 4import Typography from '@mui/material/Typography' 5import Button from '@mui/material/Button' 6import Popper from '@mui/material/Popper' 7import PopupState, { bindToggle, bindPopper } from 'material-ui-popup-state' 8import Fade from '@mui/material/Fade' 9import Paper from '@mui/material/Paper' 10 11const styles = (theme) => ({ 12 typography: { 13 padding: theme.spacing.unit * 2, 14 }, 15}) 16 17const PopperPopupState = ({ classes }) => ( 18 <PopupState variant="popper" popupId="demoPopper"> 19 {(popupState) => ( 20 <div> 21 <Button variant="contained" {...bindToggle(popupState)}> 22 Toggle Popper 23 </Button> 24 <Popper {...bindPopper(popupState)} transition> 25 {({ TransitionProps }) => ( 26 <Fade {...TransitionProps} timeout={350}> 27 <Paper> 28 <Typography className={classes.typography}> 29 The content of the Popper. 30 </Typography> 31 </Paper> 32 </Fade> 33 )} 34 </Popper> 35 </div> 36 )} 37 </PopupState> 38) 39 40PopperPopupState.propTypes = { 41 classes: PropTypes.object.isRequired, 42} 43 44export default withStyles(styles)(PopperPopupState)
Render Props API
Bind Functions
material-ui-popup-state
exports several helper functions you can use to
connect components easily:
anchorRef
: creates aref
function to pass to theanchorEl
(by default, thecurrentTarget
of the mouse event that triggered the popup is used; only useanchorRef
if you want a different element to be the anchor).bindMenu
: creates props to control aMenu
component.bindPopover
: creates props to control aPopover
component.bindPopper
: creates props to control aPopper
component.bindDialog
: creates props to control aDialog
component.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 (contextmenu
event). NOTE:bindPopover
/bindMenu
will position the Popover/Menu to thecontextmenu
event location. To position using thecontextmenu
target 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: See this guidance if you are usingbindHover
withPopover
orMenu
.bindFocus
: creates props for a component that opens the popup while hovered.
To use one of these functions, you should call it with the props PopupState
passed to your child function, and spread the return value into the desired
element:
1import * as React from 'react' 2import Button from '@mui/material/Button' 3import Menu from '@mui/material/Menu' 4import MenuItem from '@mui/material/MenuItem' 5import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state' 6 7const MenuPopupState = () => ( 8 <PopupState variant="popover" popupId="demoMenu"> 9 {(popupState) => ( 10 <React.Fragment> 11 <Button variant="contained" {...bindTrigger(popupState)}> 12 Open Menu 13 </Button> 14 <Menu {...bindMenu(popupState)}> 15 <MenuItem onClick={popupState.close}>Cake</MenuItem> 16 <MenuItem onClick={popupState.close}>Death</MenuItem> 17 </Menu> 18 </React.Fragment> 19 )} 20 </PopupState> 21) 22 23export default MenuPopupState
PopupState
Props
variant
('popover'
, 'popper'
, or 'dialog'
, required)
Use 'popover'
if your popup is a Popover
or Menu
; use 'popper'
if your
popup is a Popper
.
Right now this only affects whether bindTrigger
/bindToggle
/bindHover
return
an aria-controls
prop or an aria-describedby
prop.
popupId
(string
, optional)
The id
for the popup component. It will be passed to the child props so that
the trigger component may declare the same id in an ARIA prop.
Defaults to React.useId()
if React.useId
exists; in older versions of React
you will have to manually provide a popupId
.
disableAutoFocus
(boolean
, optional)
If true
, will not steal focus when the popup is opened. (And bindPopover
/bindMenu
will inject disableAutoFocus
, disableEnforceFocus
, and disableRestoreFocus
).
Defaults to true
when the popup is opened by the bindHover
or bindFocus
element.
children
((popupState: InjectedProps) => ?React.Node
, required)
The render function. It will be called with an object containing the following
props (exported as the InjectedProps
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
/false
if the popup is open/closedanchorEl
: the current anchor elementanchorPosition
: the current anchor positionsetAnchorEl
: sets the anchor element (thecurrentTarget
of the triggering mouse event is used by default unless you have calledsetAnchorEl
)popupId
: thepopupId
prop you passed toPopupState
variant
: thevariant
prop you passed toPopupState
Using Popover
and Menu
with bindHover
MUI's Modal
(used by Popover
and Menu
) blocks pointer events to all other components, interfering with bindHover
(the popover or menu will open when the mouse enters the bindHover
element, but won't close when the mouse leaves). You can
use the following components to work around this:
1import HoverMenu from 'material-ui-popup-state/HoverMenu' 2import HoverPopover from 'material-ui-popup-state/HoverPopover'
These are just wrapper components that pass inline styles to prevent Modal
from blocking pointer events.
Chaining event handlers
What if you need to perform additional actions in onClick
, but it's being injected by {...bindTrigger(popupState)}
etc?
There are two options:
Chaining event handlers manually
This is the most straightforward, explicit option.
1const button = ( 2 <Button 3 {...bindTrigger(popupState)} 4 onClick={(e: React.MouseEvent) => { 5 bindTrigger(popupState).onClick(e) 6 performCustomAction(e) 7 }} 8 > 9 Open Menu 10 </Button> 11)
Using material-ui-popup-state/chainEventHandlers
If you don't like the above option, you can use the provided material-ui-popup-state/chainEventHandlers
helper:
1import { chainEventHandlers } from 'material-ui-popup-state/chainEventHandlers' 2 3const button = ( 4 <Button 5 {...chainEventHandlers(bindTrigger(popupState), { 6 onClick: (e: React.MouseEvent) => { 7 bindTrigger(popupState).onClick(e) 8 performCustomAction(e) 9 }, 10 })} 11 > 12 Open Menu 13 </Button> 14)
chainEventHandlers
accepts a variable number of props arguments and combines any function props of the same name
into a function that invokes the chained functions in sequence. For all other properties the behavior is like
Object.assign
.
[!WARNING]
chainEventHandlers
doesn't memoize the combined event handler functions, so they will cause components to rerender. If you need memoized functions, you will need to perform the memoization with your own code, for example usingReact.useCallback
and chaining event handlers manually.
No vulnerabilities found.
Reason
11 commit(s) and 8 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE.md:0
- Info: FSF or OSI recognized license: MIT License: LICENSE.md:0
Reason
Found 1/30 approved changesets -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'master'
- Warn: branch protection not enabled for branch 'beta'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 3 are checked with a SAST tool
Reason
15 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc
- Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx
- Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc
- Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp
- Warn: Project is vulnerable to: GHSA-8mmm-9v2q-x3f9
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg
- Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
Score
3
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn MoreOther packages similar to material-ui-popup-state
@progress/kendo-react-popup
React Popup positions a piece of content next to a specific anchor component. KendoReact Popup package
@progress/kendo-vue-popup
Kendo UI for Vue Popup package
@mui/material
Material UI is an open-source React component library that implements Google's Material Design. It's comprehensive and can be used in production out of the box.
@mui/icons-material
Material Design icons distributed as SVG React components.