gab-social/app/javascript/gabsocial/components/popover/popover_base.js

156 lines
3.9 KiB
JavaScript
Raw Normal View History

import React from 'react'
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'
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
2020-04-11 23:29:19 +01:00
const mapStateToProps = (state) => ({
2020-05-01 06:50:27 +01:00
isModalOpen: !!state.getIn(['modal', 'modalType']),
2020-02-28 15:20:47 +00:00
popoverPlacement: state.getIn(['popover', 'placement']),
})
export default
@connect(mapStateToProps)
2020-02-28 15:20:47 +00:00
class PopoverBase extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
title: PropTypes.string,
disabled: PropTypes.bool,
status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func,
isModalOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
2020-03-11 23:56:18 +00:00
position: PropTypes.string,
2020-02-28 15:20:47 +00:00
visible: PropTypes.bool,
2020-03-11 23:56:18 +00:00
targetRef: PropTypes.node,
2020-04-23 07:13:29 +01:00
innerRef: PropTypes.oneOfType([
PropTypes.node,
PropTypes.func,
]),
2020-02-28 15:20:47 +00:00
}
static defaultProps = {
title: 'Menu',
2020-03-11 23:56:18 +00:00
position: 'bottom',
2020-02-28 15:20:47 +00:00
}
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-02-28 15:20:47 +00:00
default: 1,
z4: 1,
boxShadowPopover: visible,
2020-02-28 15:20:47 +00:00
displayNone: !visible,
})
return (
<Popper
placement={position}
referenceElement={targetRef}
>
{({ ref, style, placement, arrowProps }) => (
<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}
2020-03-11 23:56:18 +00:00
</div>
</div>
)}
</Popper>
2020-02-28 15:20:47 +00:00
)
}
}