All files OverflowMenu.jsx

21.43% Statements 6/28
0% Branches 0/20
16.67% Functions 2/12
21.43% Lines 6/28
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114                                              1x   1x 1x                                                                                                       1x 1x 1x                                                                  
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Icon } from '@procore/core-react';
import { findDOMNode } from 'react-dom';
 
import baseStyles from './OverflowMenu.scss';
import composeStyles from '../../shared/stylesheetComposer';
import InlinePortal from './InlinePortal';
import DropdownContent from './DropdownContent';
 
export default class OverflowMenu extends React.Component {
  static propTypes = {
    stylesheets: PropTypes.arrayOf(PropTypes.shape()),
    placement: PropTypes.oneOf(['left', 'right'])
  };
 
  static defaultProps = {
    stylesheets: [],
    placement: 'right'
  };
 
  constructor(props) {
    super(props);
 
    this.styles = composeStyles(baseStyles, props.stylesheets);
    this.state = { expanded: false };
  }
 
  componentDidUpdate(_, prevState) {
    if (this.state.expanded && !prevState.expanded) {
      window.addEventListener('click', this.closeDropdown, true);
    } else if (!this.state.expanded && prevState.expanded) {
      window.removeEventListener('click', this.closeDropdown, true);
    }
  }
 
  closeDropdown = (evt) => {
    if (!this.ellipsis.contains(evt.target) && !this.dropdown.contains(evt.target)) {
      this.setState({ expanded: false });
    }
  };
 
  toggleMenu = () => {
    this.setState({ expanded: !this.state.expanded });
  };
 
  itemClickHandler = handler => (e) => {
    this.setState({ expanded: false });
    if (handler && typeof handler === 'function') {
      handler(e);
    }
  };
 
  setLeft = (dropdownLeft) => {
    this.dropdown.style.left = `${Math.ceil(dropdownLeft)}px`;
  };
 
  setTop = (dropdownTop) => {
    const top = dropdownTop + window.pageYOffset;
    this.dropdown.style.top = `${Math.ceil(top)}px`;
  };
 
  position = () => {
    const {
      left: dropdownLeft,
      bottom: dropdownTop,
      width: ellipsisWidth
    } = this.ellipsis.getBoundingClientRect();
 
    const leftPosition = this.props.placement === 'left' ?
      dropdownLeft + (ellipsisWidth - this.dropdown.clientWidth) :
      dropdownLeft;
    this.setLeft(leftPosition);
    this.setTop(dropdownTop);
  };
 
  render() {
    const styles = this.styles;
    const { expanded } = this.state;
    return (
      [
        <div
          key='ellipsis'
          className={cx(
            styles.iconContainer,
            {
              [styles.expanded]: expanded
            }
          )}
          ref={elem => this.ellipsis = elem}
          onClick={this.toggleMenu}
        >
          <Icon icon={'ellipsis-v'} />
        </div>,
        <InlinePortal show={expanded} key='portal'>
          <DropdownContent
            styles={styles}
            onItemClick={this.itemClickHandler}
            ref={(el) => {
              if (el) {
                this.dropdown = findDOMNode(el);
                this.position();
              }
            }}
          >
            {this.props.children}
          </DropdownContent>
        </InlinePortal>
      ]
    );
  }
}