jamby/react-onboarder

View on GitHub
src/Onboarder.jsx

Summary

Maintainability
A
45 mins
Test Coverage
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import hexRgb from 'hex-rgb';

import Overlay from './Overlay';
import { prefix, eventId, overlayId } from './helpers';

export default class Onboarder extends Component {
  static propTypes = {
    alpha: PropTypes.string,
    children: PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.node),
      PropTypes.node
    ]),
    color: PropTypes.string,
    delay: PropTypes.number,
    show: PropTypes.bool
  }

  static defaultProps = {
    alpha: "0.3",
    color: "000000",
    delay: 0,
    show: true
  }

  static childContextTypes = {
    onbSubscribe: PropTypes.func,
    onbUpdateStep: PropTypes.func
  }

  constructor() {
    super();
    this.onbUpdateStep = this.onbUpdateStep.bind(this);
    this.onbSubscribe = this.onbSubscribe.bind(this);
    this.stopOnboarder = this.stopOnboarder.bind(this);

    this.subscribers = [];
    this.state = { step: 0, max: 0, stopped: true };
  }

  getChildContext() {
    return {
      onbSubscribe: this.onbSubscribe,
      onbUpdateStep: this.onbUpdateStep
    };
  }

  componentWillMount() {
    if (!document.getElementById(prefix())) { // Check to make sure the prefix exists
      const overlay = document.createElement("div"); // If it doesn't then let's make the div that the Overlay gets mounted onto
      overlay.id = prefix();
      document.body.appendChild(overlay);
    }

    setTimeout(() => {
      this.setState({ stopped: false });
    }, this.props.delay);
  }

  componentDidUpdate(prevProps, prevState) {
    const { show } = this.props;
    const { stopped, step, max } = this.state;
    if (show && stopped === false) {
      if (step >= max) {
        this.stopOnboarder();
        this.removeOverlay();
      } else if (step !== max) {
        if (step === 0) {
          const { alpha, color, delay } = this.props;
          const rgbColor = hexRgb(color);
          ReactDOM.render(<Overlay alpha={alpha} red={rgbColor[0]} green={rgbColor[1]} blue={rgbColor[2]} />, document.getElementById(prefix()));
        }
        this.notifySubscribers(this.subscribers[step]);
      }
    }
  }

  componentWillUnmount() {
    this.removeOverlay();
  }

  onbUpdateStep() { this.setState({ step: this.state.step + 1 }); }

  onbSubscribe(handler) {
    this.subscribers = this.subscribers.concat(handler);
    this.setState({ max: this.subscribers.length });
  }

  removeOverlay() {
    const overlay = document.getElementById(prefix());
    if (overlay) ReactDOM.unmountComponentAtNode(overlay);;
  }

  stopOnboarder() { this.setState({ stopped: true }); }

  notifySubscribers(subscriber) {
    const evt = new Event(eventId(this.state.step));
    window.dispatchEvent(evt);
  }

  render() {
    const { alpha, color, delay } = this.props;
    const rgbColor = hexRgb(color);
    return (
      <div ref={(i) => this._node = i}>
        {this.props.children}
      </div>
    );
  }
}