All files / src/components/DropdownBox index.jsx

0% Statements 0/84
0% Branches 0/64
0% Functions 0/14
0% Lines 0/41
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                                                                                                                                                                                                                   
import styles from './style.postcss';
 
import React, { PureComponent } from 'react';
import classnames from 'classnames';
import DropdownBoxItem from './DropdownBoxItem';
import DropdownBoxContainer from './DropdownBoxContainer';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import PropTypes from 'prop-types';
 
const AlignmentClasses = [
  styles.__alignRightFromBottom,
  styles.__alignLeftFromBottom,
  styles.__alignLeftFromTop,
  styles.__alignBottomFromLeft,  // default
  styles.__alignRightFromBottom, // fallback when space is limited in single student group
];
 
const isElementVisible = (el) => {
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  const rect = el.getBoundingClientRect();
 
  // is it in the viewport?
  if (rect.right < 0 || rect.bottom < 0 ||
        rect.left > viewportWidth || rect.top > viewportHeight) {
    return false;
  }
 
  // we can just check visibility of three points here (left, centre, right)
  // if we just check the centre the right side might be clipped off. other scenarios seem OK.
  return (
    // centre
    el.contains(
      document.elementFromPoint(
        rect.right - (rect.width / 2),
        rect.bottom - (rect.height / 2)))) &&
    // left
    el.contains(
      document.elementFromPoint(
        rect.left + 1,
        rect.bottom - (rect.height / 2))) &&
    // right
    el.contains(
      document.elementFromPoint(
        rect.right - 1,
        rect.bottom - (rect.height / 2)));
};
 
const isDropdownOptionsFullyVisible = (el) => {
  if (! el.children || el.children.length < 2) {
    return isElementVisible(el);
  }
  // check the visibility of first and last dropdown items
  const firstChildEl = el.children[0];
  const lastChildEl = el.children[el.children.length - 1];
  return isElementVisible(firstChildEl) && isElementVisible(lastChildEl);
};
 
class DropdownBox extends PureComponent {
  constructor(props) {
    super(props);
    this._onRef = this._onRef.bind(this);
  }
 
  _onRef(el) {
    if (! el || getComputedStyle(el).position !== 'absolute') return;
    // run through alignments until we get one that looks good
    const alignmentClassesToTry = AlignmentClasses.concat();  // clone
    let alignmentClassToTry;
    let lastAlignmentClassTried;
    while (alignmentClassesToTry.length && ! isDropdownOptionsFullyVisible(el)) {
      alignmentClassToTry = alignmentClassesToTry.shift();
      el.classList.add(alignmentClassToTry);
      if (lastAlignmentClassTried) el.classList.remove(lastAlignmentClassTried);
      lastAlignmentClassTried = alignmentClassToTry;
    }
  }
 
  render() {
    const { className, children, open, smartPosition } = this.props;
    return <CSSTransitionGroup transitionName="dropdown">
      {
        open === true &&
        <div className={styles.DropdownBox_transitionWrapper}>
          <div className={classnames(styles.DropdownBox, className)}
              ref={smartPosition && this._onRef}>
            {React.Children.toArray(children).filter((child) => child.type === DropdownBoxItem)}
          </div>
        </div>
      }
    </CSSTransitionGroup>;
  }
}
 
DropdownBox.propTypes = {
  children: PropTypes.node,
  className: PropTypes.string,
  open: PropTypes.bool,
  smartPosition: PropTypes.bool,
};
 
DropdownBox.Item = DropdownBoxItem;
DropdownBox.Container = DropdownBoxContainer;
 
export default DropdownBox;