ctxhou/react-poppop

View on GitHub
src/poppop.js

Summary

Maintainability
A
1 hr
Test Coverage
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Transition} from 'react-transition-group';
import Portal from './portal';
import {extractCamelCase} from './utils';
import styles from './style';

export default class PopPop extends Component {
  constructor(props) {
    super(props);
  }

  static defaultProps = {
    position: 'topCenter',
    closeOnOverlay: true,
    overlayStyle: {}
  };

  static propTypes = {
    open: PropTypes.bool,
    closeBtn: PropTypes.bool,
    closeOnOverlay: PropTypes.bool,
    closeOnEsc: PropTypes.bool,
    onClose: PropTypes.func,
    overlayStyle: PropTypes.object,
    contentStyle: PropTypes.object,
    position: PropTypes.oneOf([
      'topLeft', 'topCenter', 'topRight',
      'centerLeft', 'centerCenter', 'centerRight',
      'bottomLeft', 'bottomCenter', 'bottomRight',
    ])
  }

  componentDidMount() {
    if (this.props.closeOnEsc) {
      document.addEventListener('keydown', this.handleEscKeyDown);
    }
  }

  componentWillUnmount() {
    if (this.props.closeOnEsc) {
      document.removeEventListener('keydown', this.handleEscKeyDown);      
    }
  }

  handleOverlayClick = () => {
    if (this.props.onClose && this.props.closeOnOverlay) {
      this.props.onClose();
    }
  }

  handleCloseBtn = () => {
    if (this.props.onClose) {
      this.props.onClose();
    }
  }

  handleEscKeyDown = (e) => {
    if (this.props.onClose && this.props.closeOnEsc && e.keyCode === 27) {
      this.props.onClose();
    }
  }

  render() {
    const {open, position, overlayStyle, contentStyle} = this.props;
    const extractPosition = extractCamelCase(position);
    const mergeWrapperStyle = {
      ...styles.wrapper,
      ...styles.alignItems[extractPosition[0]],
      ...styles.justifyContent[extractPosition[1]]
    }

    const mergeOverlayStyle = {
      ...styles.overlay,
      ...overlayStyle
    }

    const mergeContentStyle = {
      ...styles.content,
      ...contentStyle
    }

    if (!open) return null;

    return (
      <Portal>
        <Transition in={open} appear timeout={0}>
          {state => {
            return <div style={{
                  ...mergeWrapperStyle,
                  ...styles.transitionStyles[state]
                 }}>
              <div onClick={this.handleOverlayClick}
                   style={mergeOverlayStyle}/>
              <div style={mergeContentStyle}>
                {this._renderCloseBtn()}
                {this.props.children}
              </div>
            </div>
          }}
        </Transition>
      </Portal>
    )
  }

  _renderCloseBtn() {
    if (this.props.closeBtn) {
      return (
        <div style={styles.closeBtn} onClick={this.handleCloseBtn}>
          <svg viewBox="0 0 40 40">
            <g><path d="m31.6 10.7l-9.3 9.3 9.3 9.3-2.3 2.3-9.3-9.3-9.3 9.3-2.3-2.3 9.3-9.3-9.3-9.3 2.3-2.3 9.3 9.3 9.3-9.3z"/></g>
          </svg>
        </div>
      );
    }
    return null;
  }
}