Skip to content

Commit 0bca970

Browse files
committed
fix: make disableAutoFocus work again
1 parent 6abdaa1 commit 0bca970

File tree

10 files changed

+309
-186
lines changed

10 files changed

+309
-186
lines changed

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ mutation functions to a child render function.
1919

2020
<!-- toc -->
2121

22+
- [material-ui-popup-state](#material-ui-popup-state)
23+
- [Table of Contents](#table-of-contents)
2224
- [Installation](#installation)
2325
- [Examples with React Hooks](#examples-with-react-hooks)
2426
- [Menu](#menu)
@@ -28,6 +30,9 @@ mutation functions to a child render function.
2830
- [Bind Functions](#bind-functions)
2931
- [`usePopupState`](#usepopupstate)
3032
- [`usePopupState` Props](#usepopupstate-props)
33+
- [`variant` (`'popover'` or `'popper'`, **required**)](#variant-popover-or-popper-required)
34+
- [`popupId` (`string`, **optional** but strongly encouraged)](#popupid-string-optional-but-strongly-encouraged)
35+
- [`disableAutoFocus` (`boolean`, **optional**)](#disableautofocus-boolean-optional)
3136
- [`usePopupState` return value](#usepopupstate-return-value)
3237
- [Examples with Render Props](#examples-with-render-props)
3338
- [Menu](#menu-1)
@@ -37,6 +42,10 @@ mutation functions to a child render function.
3742
- [Render Props API](#render-props-api)
3843
- [Bind Functions](#bind-functions-1)
3944
- [`PopupState` Props](#popupstate-props)
45+
- [`variant` (`'popover'` or `'popper'`, **required**)](#variant-popover-or-popper-required-1)
46+
- [`popupId` (`string`, **optional** but strongly encouraged)](#popupid-string-optional-but-strongly-encouraged-1)
47+
- [`disableAutoFocus` (`boolean`, **optional**)](#disableautofocus-boolean-optional-1)
48+
- [`children` (`(popupState: InjectedProps) => ?React.Node`, **required**)](#children-popupstate-injectedprops--reactnode-required)
4049
- [Using `Popover` and `Menu` with `bindHover`](#using-popover-and-menu-with-bindhover)
4150

4251
<!-- tocstop -->
@@ -265,6 +274,9 @@ the trigger component may declare the same id in an ARIA prop.
265274

266275
If `true`, will not steal focus when the popup is opened. (And `bindPopover`/`bindMenu`) will inject `disableAutoFocus`, `disableEnforceFocus`, and `disableRestoreFocus`).
267276

277+
You should use this option if you are doing `bindHover`. Otherwise the document may scroll back to the previous focused
278+
element when you move the pointer out of the hovered menu or popover.
279+
268280
## `usePopupState` return value
269281

270282
An object with the following properties:
@@ -369,7 +381,7 @@ import React from 'react'
369381
import PropTypes from 'prop-types'
370382
import { withStyles } from '@material-ui/core/styles'
371383
import Typography from '@material-ui/core/Typography'
372-
import Popover from '@material-ui/core/Popover'
384+
import HoverPopover from 'material-ui-popup-state/HoverPopover'
373385
import PopupState, { bindHover, bindPopover } from 'material-ui-popup-state'
374386

375387
const styles = (theme) => ({
@@ -382,13 +394,13 @@ const styles = (theme) => ({
382394
})
383395

384396
const HoverPopoverPopupState = ({ classes }) => (
385-
<PopupState variant="popover" popupId="demoPopover">
397+
<PopupState variant="popover" popupId="demoPopover" disableAutoFocus>
386398
{(popupState) => (
387399
<div>
388400
<Typography {...bindHover(popupState)}>
389401
Hover with a Popover.
390402
</Typography>
391-
<Popover
403+
<HoverPopover
392404
{...bindPopover(popupState)}
393405
className={classes.popover}
394406
classes={{
@@ -402,10 +414,9 @@ const HoverPopoverPopupState = ({ classes }) => (
402414
vertical: 'top',
403415
horizontal: 'center',
404416
}}
405-
disableRestoreFocus
406417
>
407418
<Typography>The content of the Popover.</Typography>
408-
</Popover>
419+
</HoverPopover>
409420
</div>
410421
)}
411422
</PopupState>
@@ -536,6 +547,8 @@ the trigger component may declare the same id in an ARIA prop.
536547

537548
If `true`, will not steal focus when the popup is opened. (And `bindPopover`/`bindMenu`) will inject `disableAutoFocus`, `disableEnforceFocus`, and `disableRestoreFocus`).
538549

550+
You should use this option if you are doing `bindHover`. Otherwise the document may scroll back to the previous focused
551+
539552
### `children` (`(popupState: InjectedProps) => ?React.Node`, **required**)
540553

541554
The render function. It will be called with an object containing the following
@@ -559,8 +572,8 @@ Material-UI's `Modal` (used by `Popover` and `Menu`) blocks pointer events to al
559572
use the following components to work around this:
560573

561574
```js
562-
import Menu from 'material-ui-popup-state/HoverMenu'
563-
import Popover from 'material-ui-popup-state/HoverPopover'
575+
import HoverMenu from 'material-ui-popup-state/HoverMenu'
576+
import HoverPopover from 'material-ui-popup-state/HoverPopover'
564577
```
565578

566579
These are just wrapper components that pass inline styles to prevent `Modal` from blocking pointer events.

demo/examples/CascadingHoverMenus.hooks.js

Lines changed: 105 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react'
2-
import { withStyles } from '@material-ui/styles'
3-
import Menu from 'material-ui-popup-state/HoverMenu'
2+
import { makeStyles } from '@material-ui/styles'
3+
import HoverMenu from 'material-ui-popup-state/HoverMenu'
44
import MenuItem from '@material-ui/core/MenuItem'
55
import ChevronRight from '@material-ui/icons/ChevronRight'
66
import Button from '@material-ui/core/Button'
@@ -10,91 +10,123 @@ import {
1010
bindMenu,
1111
} from 'material-ui-popup-state/hooks'
1212

13-
const ParentPopupState = React.createContext(null)
13+
const useCascadingMenuStyles = makeStyles((theme) => ({
14+
submenu: {
15+
marginTop: theme.spacing(-1),
16+
},
17+
title: {
18+
flexGrow: 1,
19+
},
20+
moreArrow: {
21+
marginRight: theme.spacing(-1),
22+
},
23+
}))
24+
25+
const CascadingContext = React.createContext({
26+
parentPopupState: null,
27+
rootPopupState: null,
28+
})
29+
30+
function CascadingMenuItem({ onClick, ...props }) {
31+
const { rootPopupState } = React.useContext(CascadingContext)
32+
if (!rootPopupState) throw new Error('must be used inside a CascadingMenu')
33+
const handleClick = React.useCallback(
34+
(event) => {
35+
rootPopupState.close(event)
36+
if (onClick) onClick(event)
37+
},
38+
[rootPopupState, onClick]
39+
)
40+
41+
return <MenuItem {...props} onClick={handleClick} />
42+
}
43+
44+
function CascadingSubmenu({ title, popupId, ...props }) {
45+
const classes = useCascadingMenuStyles()
46+
const { parentPopupState } = React.useContext(CascadingContext)
47+
const popupState = usePopupState({
48+
popupId,
49+
variant: 'popover',
50+
parentPopupState,
51+
disableAutoFocus: true,
52+
})
53+
return (
54+
<React.Fragment>
55+
<MenuItem {...bindHover(popupState)}>
56+
<span className={classes.title}>{title}</span>
57+
<ChevronRight className={classes.moreArrow} />
58+
</MenuItem>
59+
<CascadingMenu
60+
{...props}
61+
classes={{ ...props.classes, paper: classes.submenu }}
62+
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
63+
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
64+
popupState={popupState}
65+
/>
66+
</React.Fragment>
67+
)
68+
}
69+
70+
function CascadingMenu({ popupState, ...props }) {
71+
const { rootPopupState } = React.useContext(CascadingContext)
72+
const context = React.useMemo(
73+
() => ({
74+
rootPopupState: rootPopupState || popupState,
75+
parentPopupState: popupState,
76+
}),
77+
[rootPopupState, popupState]
78+
)
79+
80+
return (
81+
<CascadingContext.Provider value={context}>
82+
<HoverMenu {...props} {...bindMenu(popupState)} />
83+
</CascadingContext.Provider>
84+
)
85+
}
1486

1587
const CascadingHoverMenus = () => {
1688
const popupState = usePopupState({
1789
popupId: 'demoMenu',
1890
variant: 'popover',
19-
deferOpenClose: true,
91+
disableAutoFocus: true,
2092
})
2193
return (
2294
<div style={{ height: 600 }}>
2395
<Button variant="contained" {...bindHover(popupState)}>
2496
Hover to open Menu
2597
</Button>
26-
<ParentPopupState.Provider value={popupState}>
27-
<Menu
28-
{...bindMenu(popupState)}
29-
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
30-
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
98+
<CascadingMenu
99+
popupState={popupState}
100+
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
101+
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
102+
>
103+
<CascadingMenuItem>Tea</CascadingMenuItem>
104+
<CascadingMenuItem>Cake</CascadingMenuItem>
105+
<CascadingMenuItem>Death</CascadingMenuItem>
106+
<CascadingSubmenu
107+
popupId="moreChoicesCascadingMenu"
108+
title="More Choices"
31109
>
32-
<MenuItem onClick={popupState.close}>Tea</MenuItem>
33-
<MenuItem onClick={popupState.close}>Cake</MenuItem>
34-
<MenuItem onClick={popupState.close}>Death</MenuItem>
35-
<Submenu popupId="moreChoicesMenu" title="More Choices">
36-
<MenuItem onClick={popupState.close}>Cheesecake</MenuItem>
37-
<MenuItem onClick={popupState.close}>Cheesedeath</MenuItem>
38-
<Submenu popupId="evenMoreChoicesMenu" title="Even More Choices">
39-
<MenuItem onClick={popupState.close}>Cake (the band)</MenuItem>
40-
<MenuItem onClick={popupState.close}>Death Metal</MenuItem>
41-
</Submenu>
42-
<Submenu popupId="moreBenignChoices" title="More Benign Choices">
43-
<MenuItem onClick={popupState.close}>Salad</MenuItem>
44-
<MenuItem onClick={popupState.close}>Lobotomy</MenuItem>
45-
</Submenu>
46-
</Submenu>
47-
</Menu>
48-
</ParentPopupState.Provider>
110+
<CascadingMenuItem>Cheesecake</CascadingMenuItem>
111+
<CascadingMenuItem>Cheesedeath</CascadingMenuItem>
112+
<CascadingSubmenu
113+
popupId="evenMoreChoicesCascadingMenu"
114+
title="Even More Choices"
115+
>
116+
<CascadingMenuItem>Cake (the band)</CascadingMenuItem>
117+
<CascadingMenuItem>Death Metal</CascadingMenuItem>
118+
</CascadingSubmenu>
119+
<CascadingSubmenu
120+
popupId="moreBenignChoices"
121+
title="More Benign Choices"
122+
>
123+
<CascadingMenuItem>Salad</CascadingMenuItem>
124+
<CascadingMenuItem>Lobotomy</CascadingMenuItem>
125+
</CascadingSubmenu>
126+
</CascadingSubmenu>
127+
</CascadingMenu>
49128
</div>
50129
)
51130
}
52131

53132
export default CascadingHoverMenus
54-
55-
const submenuStyles = (theme) => ({
56-
menu: {
57-
marginTop: theme.spacing(-1),
58-
},
59-
title: {
60-
flexGrow: 1,
61-
},
62-
moreArrow: {
63-
marginRight: theme.spacing(-1),
64-
},
65-
})
66-
67-
const Submenu = withStyles(submenuStyles)(
68-
// Unfortunately, MUI <Menu> injects refs into its children, which causes a
69-
// warning in some cases unless we use forwardRef here.
70-
React.forwardRef(({ classes, title, popupId, children, ...props }, ref) => {
71-
const parentPopupState = React.useContext(ParentPopupState)
72-
const popupState = usePopupState({
73-
popupId,
74-
variant: 'popover',
75-
parentPopupState,
76-
deferOpenClose: true,
77-
})
78-
return (
79-
<ParentPopupState.Provider value={popupState}>
80-
<MenuItem
81-
{...bindHover(popupState)}
82-
selected={popupState.isOpen}
83-
ref={ref}
84-
>
85-
<span className={classes.title}>{title}</span>
86-
<ChevronRight className={classes.moreArrow} />
87-
</MenuItem>
88-
<Menu
89-
{...bindMenu(popupState)}
90-
classes={{ paper: classes.menu }}
91-
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
92-
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
93-
{...props}
94-
>
95-
{children}
96-
</Menu>
97-
</ParentPopupState.Provider>
98-
)
99-
})
100-
)

0 commit comments

Comments
 (0)