UNPKG

reactstrap

Version:
239 lines (217 loc) 5.57 kB
import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; import { Popper as ReactPopper } from 'react-popper'; import { getTarget, targetPropType, mapToCssModules, DOMElement, tagPropType, } from './utils'; import Fade from './Fade'; function noop() {} const propTypes = { children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, popperClassName: PropTypes.string, placement: PropTypes.string, placementPrefix: PropTypes.string, arrowClassName: PropTypes.string, hideArrow: PropTypes.bool, tag: tagPropType, isOpen: PropTypes.bool, cssModule: PropTypes.object, offset: PropTypes.arrayOf(PropTypes.number), fallbackPlacements: PropTypes.array, flip: PropTypes.bool, container: targetPropType, target: targetPropType.isRequired, modifiers: PropTypes.array, strategy: PropTypes.string, boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]), onClosed: PropTypes.func, fade: PropTypes.bool, transition: PropTypes.shape(Fade.propTypes), }; const defaultProps = { boundariesElement: 'scrollParent', placement: 'auto', hideArrow: false, isOpen: false, offset: [0, 0], flip: true, container: 'body', modifiers: [], onClosed: noop, fade: true, transition: { ...Fade.defaultProps, }, }; class PopperContent extends React.Component { constructor(props) { super(props); this.setTargetNode = this.setTargetNode.bind(this); this.getTargetNode = this.getTargetNode.bind(this); this.getRef = this.getRef.bind(this); this.onClosed = this.onClosed.bind(this); this.state = { isOpen: props.isOpen }; } static getDerivedStateFromProps(props, state) { if (props.isOpen && !state.isOpen) { return { isOpen: props.isOpen }; } return null; } componentDidUpdate() { if ( this._element && this._element.childNodes && this._element.childNodes[0] && this._element.childNodes[0].focus ) { this._element.childNodes[0].focus(); } } onClosed() { this.props.onClosed(); this.setState({ isOpen: false }); } getTargetNode() { return this.targetNode; } getContainerNode() { return getTarget(this.props.container); } getRef(ref) { this._element = ref; } setTargetNode(node) { this.targetNode = typeof node === 'string' ? getTarget(node) : node; } renderChildren() { const { cssModule, children, isOpen, flip, target, offset, fallbackPlacements, placementPrefix, arrowClassName: _arrowClassName, hideArrow, popperClassName: _popperClassName, tag, container, modifiers, strategy, boundariesElement, onClosed, fade, transition, placement, ...attrs } = this.props; const arrowClassName = mapToCssModules( classNames('arrow', _arrowClassName), cssModule, ); const popperClassName = mapToCssModules( classNames( _popperClassName, placementPrefix ? `${placementPrefix}-auto` : '', ), this.props.cssModule, ); const modifierNames = modifiers.map((m) => m.name); const baseModifiers = [ { name: 'offset', options: { offset, }, }, { name: 'flip', enabled: flip, options: { fallbackPlacements, }, }, { name: 'preventOverflow', options: { boundary: boundariesElement, }, }, ].filter((m) => !modifierNames.includes(m.name)); const extendedModifiers = [...baseModifiers, ...modifiers]; const popperTransition = { ...Fade.defaultProps, ...transition, baseClass: fade ? transition.baseClass : '', timeout: fade ? transition.timeout : 0, }; return ( <Fade {...popperTransition} {...attrs} in={isOpen} onExited={this.onClosed} tag={tag} > <ReactPopper referenceElement={this.targetNode} modifiers={extendedModifiers} placement={placement} strategy={strategy} > {({ ref, style, placement: popperPlacement, isReferenceHidden, arrowProps, update, }) => ( <div ref={ref} style={style} className={popperClassName} data-popper-placement={popperPlacement} data-popper-reference-hidden={ isReferenceHidden ? 'true' : undefined } > {typeof children === 'function' ? children({ update }) : children} {!hideArrow && ( <span ref={arrowProps.ref} className={arrowClassName} style={arrowProps.style} /> )} </div> )} </ReactPopper> </Fade> ); } render() { this.setTargetNode(this.props.target); if (this.state.isOpen) { return this.props.container === 'inline' ? this.renderChildren() : ReactDOM.createPortal( <div ref={this.getRef}>{this.renderChildren()}</div>, this.getContainerNode(), ); } return null; } } PopperContent.propTypes = propTypes; PopperContent.defaultProps = defaultProps; export default PopperContent;