Skip to content

Commit

Permalink
Merge pull request #1359 from cozy/am-positions
Browse files Browse the repository at this point in the history
feat: Support ActionMenu placement and scroll
  • Loading branch information
y-lohse authored Jan 27, 2020
2 parents dad0040 + b0321ca commit bcf5e0c
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 27 deletions.
34 changes: 34 additions & 0 deletions react/ActionMenu/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,37 @@ const hideMenu = () => setState({ menuDisplayed: false });
</ActionMenu>}
</div>
```

### Placement

The `placement` and `anchorElRef` prop can be used to control the placement of the menu on desktop. `anchorElRef` should be a ref to a DOM element and not a react component.

```
import ActionMenu, { ActionMenuItem } from 'cozy-ui/transpiled/react/ActionMenu';
import Icon from 'cozy-ui/transpiled/react/Icon';
const testRef = React.createRef();
initialState = { menuDisplayed: isTesting() };
const showMenu = () => setState({ menuDisplayed: true });
const hideMenu = () => setState({ menuDisplayed: false });
const anchorRef = React.createRef();
<div>
<button onClick={showMenu} ref={anchorRef}>Show action menu</button>
{state.menuDisplayed &&
<ActionMenu
anchorElRef={anchorRef}
placement="bottom-end"
buttonRef={testRef}
onClose={hideMenu}>
<ActionMenuItem left={<Icon icon='file' />}>Item 1</ActionMenuItem>
</ActionMenu>}
</div>
```

### preventOverflow

Set `preventOverflow` to `true` to keep the ActionMenu visible on desktop, even if `anchorElRef` is outside the viewport.
77 changes: 65 additions & 12 deletions react/ActionMenu/index.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,63 @@
import React from 'react'
import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import cx from 'classnames'
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import styles from './styles.styl'
import { Media, Bd, Img } from '../Media'
import BottomDrawer from '../BottomDrawer'
import withBreakpoints from '../helpers/withBreakpoints'
import Popper from '@material-ui/core/Popper'

const ActionMenuWrapper = ({ inline, onClose, children }) =>
inline ? (
<ClickAwayListener onClickAway={onClose}>{children}</ClickAwayListener>
const ActionMenuWrapper = ({
inline,
onClose,
anchorElRef,
placement,
preventOverflow,
children
}) => {
const getAnchorElement = useCallback(() => {
return anchorElRef.current
}, [anchorElRef])
const normalOverflowModifiers = {
preventOverflow: { enabled: false },
hide: { enabled: false }
}

return inline ? (
<Popper
anchorEl={getAnchorElement}
modifiers={preventOverflow ? null : normalOverflowModifiers}
open
placement={placement}
>
<ClickAwayListener onClickAway={onClose}>{children}</ClickAwayListener>
</Popper>
) : (
<BottomDrawer onClose={onClose}>{children}</BottomDrawer>
)
}

const ActionMenu = ({
children,
className,
onClose,
placement,
preventOverflow,
anchorElRef,
breakpoints: { isDesktop }
}) => {
const shouldDisplayInline = isDesktop
const containerRef = React.createRef()
return (
<div
className={cx(
{ [styles['c-actionmenu-container']]: shouldDisplayInline },
className
)}
>
<ActionMenuWrapper onClose={onClose} inline={shouldDisplayInline}>
<div className={className} ref={containerRef}>
<ActionMenuWrapper
onClose={onClose}
inline={shouldDisplayInline}
anchorElRef={anchorElRef || containerRef}
placement={placement}
preventOverflow={preventOverflow}
>
<div
className={cx(styles['c-actionmenu'], {
[styles['c-actionmenu--inline']]: shouldDisplayInline
Expand All @@ -47,7 +76,31 @@ ActionMenu.propTypes = {
/** Extra class */
className: PropTypes.string,
/** What to do on close */
onClose: PropTypes.func
onClose: PropTypes.func,
/** Controls the placement of the menu on desktop */
placement: PropTypes.oneOf([
'bottom-end',
'bottom-start',
'bottom',
'left-end',
'left-start',
'left',
'right-end',
'right-start',
'right',
'top-end',
'top-start',
'top'
]),
/** Will keep the menu visible when scrolling */
preventOverflow: PropTypes.bool,
/** The reference element for the menu placement and overflow prevention. */
anchorElRef: PropTypes.object
}

ActionMenu.defaultProps = {
placement: 'bottom-start',
preventOverflow: false
}

const ActionMenuHeader = ({ children }) => {
Expand Down
3 changes: 0 additions & 3 deletions react/ActionMenu/styles.styl
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
.c-actionmenu--inline
@extends $actionmenu--inline

.c-actionmenu-container
@extends $actionmenu-container

.c-actionmenu-header
@extends $actionmenu-header

Expand Down
5 changes: 3 additions & 2 deletions react/BottomDrawer/styles.styl
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
@require 'tools/mixins'
@require 'settings/spaces'
@require 'settings/z-index'

.with-transition
transition transform .1s ease-out

.BottomDrawer-content
z-index $file-action-menu
z-index $drawer-index
position fixed
bottom rem(9)
bottom env(safe-area-inset-bottom) // @stylint ignore
left 0
width 'calc(100% - %s)' % rem(16)
width 'calc(100% - %s)' % (spacing_values.m * 2)
margin 0 spacing_values.m
padding-bottom rem(5)
box-sizing border-box
9 changes: 9 additions & 0 deletions react/Menu/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,12 @@ const showItem = item => {
</ActionMenu>}
</div>
```

#### `position` and `popover`

`ActionMenu` relies on the Material-UI (Popover)[https://v3.material-ui.com/utils/popper/] component to handle both `position` and `popover`.

- `position='left'` is now `placement='bottom-start'`, and is still the default behavior.
- `position='right'` is now `placement='bottom-end'`.
- All other Popover.js placement options are also supported
- The Menu `popover` prop is now called `preventOverflow`
23 changes: 20 additions & 3 deletions react/__snapshots__/examples.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`ActionMenu should render examples: ActionMenu 1`] = `
"<div>
<div><button>Show action menu</button>
<div class=\\"styles__c-actionmenu-container___JhthU\\">
<div>
<div class=\\"styles__c-actionmenu___22Fp1 styles__c-actionmenu--inline___1SXZa\\">
<div class=\\"styles__media___cSJMp styles__c-actionmenu-item___gODqd\\">
<div class=\\"styles__img___3SHpG u-mh-1\\"><svg class=\\"styles__icon___23x3R\\" width=\\"16\\" height=\\"16\\">
Expand Down Expand Up @@ -32,7 +32,7 @@ exports[`ActionMenu should render examples: ActionMenu 1`] = `
exports[`ActionMenu should render examples: ActionMenu 2`] = `
"<div>
<div><button>Show action menu</button>
<div class=\\"styles__c-actionmenu-container___JhthU\\">
<div>
<div class=\\"styles__c-actionmenu___22Fp1 styles__c-actionmenu--inline___1SXZa\\">
<div class=\\"styles__media___cSJMp styles__c-actionmenu-item___gODqd\\">
<div class=\\"styles__img___3SHpG u-mh-1\\"><svg class=\\"styles__icon___23x3R\\" width=\\"16\\" height=\\"16\\">
Expand Down Expand Up @@ -61,7 +61,24 @@ exports[`ActionMenu should render examples: ActionMenu 2`] = `
exports[`ActionMenu should render examples: ActionMenu 3`] = `
"<div>
<div><button>Show action menu</button>
<div class=\\"styles__c-actionmenu-container___JhthU\\">
<div>
<div class=\\"styles__c-actionmenu___22Fp1 styles__c-actionmenu--inline___1SXZa\\">
<div class=\\"styles__media___cSJMp styles__c-actionmenu-item___gODqd\\">
<div class=\\"styles__img___3SHpG u-mh-1\\"><svg class=\\"styles__icon___23x3R\\" width=\\"16\\" height=\\"16\\">
<use xlink:href=\\"#file\\"></use>
</svg></div>
<div class=\\"styles__bd___1Uv-F u-mr-1\\">Item 1</div>
</div>
</div>
</div>
</div>
</div>"
`;

exports[`ActionMenu should render examples: ActionMenu 4`] = `
"<div>
<div><button>Show action menu</button>
<div>
<div class=\\"styles__c-actionmenu___22Fp1 styles__c-actionmenu--inline___1SXZa\\">
<div class=\\"styles__media___cSJMp styles__c-actionmenu-item___gODqd\\">
<div class=\\"styles__img___3SHpG u-mh-1\\"><svg class=\\"styles__icon___23x3R\\" width=\\"16\\" height=\\"16\\">
Expand Down
5 changes: 5 additions & 0 deletions react/examples.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import testFromStyleguidist from '../test/testFromStyleguidist'
import path from 'path'

// Popper does not work well inside of jest as it heavily relies on DOM APIs (see https://github.com/popperjs/popper-core/issues/478).
jest.mock('@material-ui/core/Popper', () => {
return ({ children }) => children
})

const makeRequire = subpath => m => {
if (m.indexOf('.') === 0) {
return require('./' + path.join(subpath, m))
Expand Down
6 changes: 0 additions & 6 deletions stylus/components/action-menu.styl
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
@require './popover.styl'
@require '../tools/mixins'

$actionmenu-container
position relative

$actionmenu
@extend $popover

hr
margin-top 0

$actionmenu--inline
position absolute
bottom auto
left auto
width auto
min-width rem(220)

Expand Down
3 changes: 2 additions & 1 deletion stylus/settings/z-index.styl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ $bar-index = $nav-index + 1 // Not actually used, just as reference
$selection-index = 30
$popover-index = 40
$overlay-index = 50
$file-action-menu = 60
$file-action-menu = 60 // replaced by $drawer-index
$drawer-index = 60
$modal-index = 70
$alert-index = 80

0 comments on commit bcf5e0c

Please sign in to comment.