All files / src/components/Button button.jsx

94.44% Statements 34/36
91.18% Branches 31/34
100% Functions 9/9
100% Lines 31/31
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                      37x 37x       24x 21x 21x 21x       21x     21x 14x 14x 14x 6x 6x   8x 2x             8x 8x 6x       36x   36x     19x             37x 37x   37x   36x             36x   12x                 24x 9x               15x       2x                                        
import styles from './style.postcss';
 
import React, { PureComponent } from 'react';
import classnames from 'classnames';
import { Link } from 'react-router';
import { Type, validateType } from './type';
import is from 'is_js';
import PropTypes from 'prop-types';
 
class Button extends PureComponent {
  constructor(props) {
    super(props);
    this._handleButtonClick = this._handleButtonClick.bind(this);
  }
 
  _handleButtonClick(e) {
    if (this.props.disabled) return;
    const { onClick } = this.props;
    const ret = is.function(onClick) && onClick(e);
    this._setActiveClassOnClick(ret);
  }
 
  _setActiveClassOnClick(ret) {
    const { onClickActiveClassAddDelay, onClickActiveClassRemoveDelay } = this.props;
 
    // defer this - we don't want to show an active state if something happens immediately
    setTimeout(async () => {
      Iif (! this._node) return;
      this._node.classList.add(styles.__active);
      if (ret instanceof Promise) {
        try { await ret; } catch (e) { /**/ }
        this._unsetActiveClassIfNonActive();
      } else {
        setTimeout(() => {
          this._unsetActiveClassIfNonActive();
        }, is.number(onClickActiveClassRemoveDelay) ? onClickActiveClassRemoveDelay : 1000);
      }
    }, is.number(onClickActiveClassAddDelay) ? onClickActiveClassAddDelay : 100);
  }
 
  _unsetActiveClassIfNonActive() {
    Iif (this.props.active) return;
    if (! this._node) return;
    this._node.classList.remove(styles.__active);
  }
 
  _renderButton(type, modeClasses, assignOnClick = true) {
    const classes = classnames(type, modeClasses, this.props.className);
 
    return <button className={classes}
        disabled={this.props.disabled}
        onClick={assignOnClick ? this._handleButtonClick : undefined}
        ref={(c) => { this._node = c; }}
        {...this.props.attrs || {}}>
      {this.props.children}
    </button>;
  }
 
  render() {
    const props = this.props;
    const type = props.type || Type.default;
 
    validateType(type);
 
    const modeClasses = {
      [styles.__block]: !! props.block,
      [styles.__autoWidth]: !! props.autoWidth,
      [styles.__active]: !! props.active,
    };
 
    // case: link to URL via `linkHref` OR disabled with `linkTo`
    if (is.existy(props.linkHref) || (is.existy(props.linkTo) && props.disabled)) {
      // eslint-disable-next-line no-script-url
      return <a href={! props.disabled ? props.linkHref : 'javascript:void(0)'}
          className={classnames(styles.ButtonLink, modeClasses, props.className)}
          target={props.newTab ? '_blank' : undefined}
          onClick={this._handleButtonClick}>
        {this._renderButton(type, modeClasses, false)}
      </a>;
    }
 
    // case: link to route via `linkTo`
    if (is.existy(props.linkTo)) {
      return <Link to={props.linkTo}
          draggable={false}
          className={classnames(styles.ButtonLink, modeClasses, props.className)}
          onClick={this._handleButtonClick}>
        {this._renderButton(type, modeClasses, false)}
      </Link>;
    }
 
    return this._renderButton(type, modeClasses);
  }
}
 
Button.propTypes = {
  onClick: PropTypes.func,
  type: PropTypes.string,
  disabled: PropTypes.bool,
  newTab: PropTypes.bool,
  children: PropTypes.node,
  linkTo: PropTypes.string,
  linkHref: PropTypes.string,
  block: PropTypes.bool,
  autoWidth: PropTypes.bool,
  active: PropTypes.bool,
  className: PropTypes.string,
  attrs: PropTypes.shape({
    type: PropTypes.string,
  }),
  onClickActiveClassAddDelay: PropTypes.number,  // default: 100ms
  onClickActiveClassRemoveDelay: PropTypes.number,  // default: 1000ms
};
 
export default Button;