2020-08-17 21:07:16 +01:00
|
|
|
import React from 'react'
|
2020-08-17 21:59:29 +01:00
|
|
|
import PropTypes from 'prop-types'
|
2020-08-17 21:39:25 +01:00
|
|
|
import { connect } from 'react-redux'
|
2020-05-01 06:50:27 +01:00
|
|
|
import detectPassiveEvents from 'detect-passive-events'
|
2020-02-28 15:20:47 +00:00
|
|
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
|
|
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
2020-07-22 16:28:53 +01:00
|
|
|
import { Popper } from 'react-popper'
|
2020-05-01 06:50:27 +01:00
|
|
|
import { CX } from '../../constants'
|
2020-02-28 15:20:47 +00:00
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
2020-02-28 15:20:47 +00:00
|
|
|
|
|
|
|
class PopoverBase extends ImmutablePureComponent {
|
|
|
|
|
|
|
|
static contextTypes = {
|
|
|
|
router: PropTypes.object,
|
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
componentDidMount() {
|
|
|
|
document.addEventListener('click', this.handleDocumentClick, false)
|
|
|
|
document.addEventListener('keydown', this.handleKeyDown, false)
|
|
|
|
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
2020-02-28 15:20:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
componentWillUnmount() {
|
|
|
|
document.removeEventListener('click', this.handleDocumentClick, false)
|
|
|
|
document.removeEventListener('keydown', this.handleKeyDown, false)
|
|
|
|
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDocumentClick = (e) => {
|
|
|
|
const { targetRef, visible, onClose } = this.props
|
|
|
|
|
|
|
|
const containsTargetRef = !targetRef ? false : targetRef.contains(e.target)
|
|
|
|
|
|
|
|
if (this.node && !this.node.contains(e.target) && !containsTargetRef && visible) {
|
|
|
|
onClose()
|
|
|
|
}
|
2020-02-28 15:20:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
handleKeyDown = (e) => {
|
|
|
|
const items = Array.from(this.node.getElementsByTagName('a'))
|
|
|
|
const index = items.indexOf(document.activeElement)
|
|
|
|
let element
|
|
|
|
|
2020-02-28 15:20:47 +00:00
|
|
|
switch (e.key) {
|
2020-05-01 06:50:27 +01:00
|
|
|
case 'ArrowDown':
|
|
|
|
element = items[index + 1]
|
|
|
|
if (element) element.focus()
|
|
|
|
break
|
|
|
|
case 'ArrowUp':
|
|
|
|
element = items[index - 1]
|
|
|
|
if (element) element.focus()
|
|
|
|
break
|
|
|
|
case 'Home':
|
|
|
|
element = items[0]
|
|
|
|
if (element) element.focus()
|
|
|
|
break
|
|
|
|
case 'End':
|
|
|
|
element = items[items.length - 1]
|
|
|
|
if (element) element.focus()
|
|
|
|
break
|
2020-04-08 02:06:59 +01:00
|
|
|
case 'Escape':
|
|
|
|
this.handleClose()
|
|
|
|
break
|
2020-02-28 15:20:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
handleItemClick = (e) => {
|
2020-02-28 15:20:47 +00:00
|
|
|
const i = Number(e.currentTarget.getAttribute('data-index'))
|
|
|
|
const { action, to } = this.props.items[i]
|
|
|
|
|
|
|
|
this.handleClose()
|
|
|
|
|
|
|
|
if (typeof action === 'function') {
|
|
|
|
e.preventDefault()
|
|
|
|
action()
|
|
|
|
} else if (to) {
|
|
|
|
e.preventDefault()
|
|
|
|
this.context.router.history.push(to)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
handleClose = () => {
|
|
|
|
this.props.onClose()
|
2020-02-28 15:20:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
setRef = (n) => {
|
|
|
|
try {
|
|
|
|
this.node = n
|
|
|
|
this.props.innerRef = n
|
|
|
|
} catch (error) {
|
|
|
|
//
|
2020-02-28 15:20:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2020-03-11 23:56:18 +00:00
|
|
|
const {
|
|
|
|
children,
|
|
|
|
visible,
|
|
|
|
position,
|
|
|
|
targetRef,
|
|
|
|
} = this.props
|
2020-02-28 15:20:47 +00:00
|
|
|
|
2020-05-01 06:50:27 +01:00
|
|
|
const containerClasses = CX({
|
2020-08-18 21:49:11 +01:00
|
|
|
d: 1,
|
2020-02-28 15:20:47 +00:00
|
|
|
z4: 1,
|
2020-07-22 16:28:53 +01:00
|
|
|
boxShadowPopover: visible,
|
2020-02-28 15:20:47 +00:00
|
|
|
displayNone: !visible,
|
|
|
|
})
|
|
|
|
|
|
|
|
return (
|
2020-07-22 16:28:53 +01:00
|
|
|
<Popper
|
|
|
|
placement={position}
|
|
|
|
referenceElement={targetRef}
|
|
|
|
>
|
2020-10-29 04:20:33 +00:00
|
|
|
{({ ref, style, placement, arrowProps, isReferenceHidden }) => {
|
|
|
|
if (isReferenceHidden) return null
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div ref={ref} style={style} data-placement={placement} className={[_s.z4, _s.mt5, _s.mb5, _s.px5, _s.py5].join(' ')}>
|
|
|
|
<div ref={arrowProps.ref} style={arrowProps.style} />
|
|
|
|
<div ref={this.setRef} data-popover='true' onKeyDown={this.handleKeyDown} className={containerClasses}>
|
|
|
|
{children}
|
|
|
|
</div>
|
2020-03-11 23:56:18 +00:00
|
|
|
</div>
|
2020-10-29 04:20:33 +00:00
|
|
|
)
|
|
|
|
}}
|
2020-07-22 16:28:53 +01:00
|
|
|
</Popper>
|
2020-02-28 15:20:47 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-08-19 01:22:15 +01:00
|
|
|
|
|
|
|
const mapStateToProps = (state) => ({
|
|
|
|
isModalOpen: !!state.getIn(['modal', 'modalType']),
|
|
|
|
popoverPlacement: state.getIn(['popover', 'placement']),
|
|
|
|
})
|
|
|
|
|
|
|
|
PopoverBase.propTypes = {
|
|
|
|
title: PropTypes.string,
|
|
|
|
disabled: PropTypes.bool,
|
|
|
|
status: ImmutablePropTypes.map,
|
|
|
|
isUserTouching: PropTypes.func,
|
|
|
|
isModalOpen: PropTypes.bool.isRequired,
|
|
|
|
onClose: PropTypes.func.isRequired,
|
|
|
|
position: PropTypes.string,
|
|
|
|
visible: PropTypes.bool,
|
|
|
|
targetRef: PropTypes.node,
|
|
|
|
innerRef: PropTypes.oneOfType([
|
|
|
|
PropTypes.node,
|
|
|
|
PropTypes.func,
|
|
|
|
]),
|
|
|
|
}
|
|
|
|
|
|
|
|
PopoverBase.defaultProps = {
|
|
|
|
title: 'Menu',
|
|
|
|
position: 'bottom',
|
|
|
|
}
|
|
|
|
|
|
|
|
export default connect(mapStateToProps)(PopoverBase)
|