All files / src/containers/Sidebar component.jsx

74.55% Statements 41/55
70.83% Branches 17/24
82.61% Functions 19/23
76.92% Lines 40/52
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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149                          10x 10x 11x       11x   27x 26x       10x 10x 10x 10x 10x       9x                                   9x 7x 7x   2x 2x         7x 7x       2x 2x       7x 7x       2x 2x       7x 7x       7x                           1x       7x       7x 7x       20x               10x 1x     9x 9x 9x     9x               1x                  
import styles from './style.postcss';
 
import React, { PureComponent } from 'react';
import Link from './Link';
import is from 'is_js';
import { OrderedMap, List, Map } from 'immutable';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import classnames from 'classnames';
import testClass from 'domain/testClass';
import PropTypes from 'prop-types';
 
class Sidebar extends PureComponent {
  static _getItems(props) {
    const { routes } = props;
    return new List(routes).reduce((reduction, value) => {
      Iif (is.not.object(value.sidebar)) {
        return reduction;
      }
 
      return reduction.merge(value.sidebar);
    }, new OrderedMap())
        .filter((item) => item instanceof Map && ! item.isEmpty())
        .sortBy((item) => item.get('order'));
  }
 
  constructor(props) {
    super(props);
    this._items = Sidebar._getItems(props);
    this._onClickedOutside = this._onClickedOutside.bind(this);
    this._onPageScrolled = this._onPageScrolled.bind(this);
    this._color = styles.headerBgColor;
  }
 
  componentDidMount() {
    this._setUp(this.props);
  }
 
  componentWillUpdate(props) {
    this._items = Sidebar._getItems(props);
    this._setUp(props);
  }
 
  componentDidUpdate() {
    this._onPageScrolled();
  }
 
  componentWillUnmount() {
    this._removeClickOutsideEvents();
    this._removePageSrolledEvents();
  }
 
  _setUp(props) {
    if (props.expanded === true) {
      this._addClickOutsideEvents();
      this._addPageSrolledEvents();
    } else {
      this._removeClickOutsideEvents();
      this._removePageSrolledEvents();
    }
  }
 
  _addClickOutsideEvents() {
    document.body.addEventListener('click', this._onClickedOutside);
    document.body.addEventListener('touchstart', this._onClickedOutside);
  }
 
  _removeClickOutsideEvents() {
    document.body.removeEventListener('click', this._onClickedOutside);
    document.body.removeEventListener('touchstart', this._onClickedOutside);
  }
 
  _addPageSrolledEvents() {
    document.addEventListener('scroll', this._onPageScrolled);
    document.addEventListener('wheel', this._onPageScrolled);
  }
 
  _removePageSrolledEvents() {
    document.removeEventListener('scroll', this._onPageScrolled);
    document.removeEventListener('wheel', this._onPageScrolled);
  }
 
  _onClickedOutside(evt) {
    Iif (! this._node) return;
    Iif (this._node.contains(evt.target)) {
      return;
    }
 
    this._contract(evt);
  }
 
  _onPageScrolled() {
    if (! this._node) return;
    const expandedNode = this._node.querySelector(`.${styles.Sidebar_expanded}`);
    if (! expandedNode) {
      return;
    }
 
    expandedNode.style.paddingTop = `calc(${styles.headerSize} - ${document.body.scrollTop}px)`;
  }
 
  _expand(evt) {
    is.function(this.props.onExpand) && this.props.onExpand(evt);
  }
 
  _contract(evt) {
    is.function(this.props.onCollapse) && this.props.onCollapse(evt);
  }
 
  _renderExpanded() {
    const { location: { pathname } } = this.props;
    return <div className={classnames(styles.Sidebar_expanded, testClass('sidebar-expanded'))}
        style={{ backgroundColor: this._color }}>
      {
        // eslint-disable-next-line react/no-array-index-key
        this._items.map((config, link) => <Link key={link} to={link} currentPath={pathname}>
          <button className={styles.Sidebar_item}>{config.get('text')}</button>
        </Link>).toArray()
      }
    </div>;
  }
 
  render() {
    if (this._items.size <= 0) {
      return null;
    }
 
    const { expanded } = this.props;
    const onClickBar = expanded === true ? undefined : (e) => this._expand(e);
    return <div className={classnames(styles.Sidebar, testClass('sidebar'))}
        onClick={onClickBar}
        style={{ backgroundColor: this._color }}
        ref={(c) => { this._node = c; }}>
      <CSSTransitionGroup transitionName="sidebar">
        {expanded === true && this._renderExpanded()}
      </CSSTransitionGroup>
    </div>;
  }
}
 
Sidebar.propTypes = {
  routes: PropTypes.array.isRequired,
  location: PropTypes.shape({ pathname: PropTypes.string.isRequired }).isRequired,
  expanded: PropTypes.bool,
  onCollapse: PropTypes.func,
  onExpand: PropTypes.func,
};
 
export default Sidebar;