import cx from 'classnames';
import {isEqual, throttle} from 'lodash';
import React, {Children, cloneElement, PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import {Portal} from 'react-overlays';
import componentOrElement from 'react-prop-types/lib/componentOrElement';

// When appending the overlay to `document.body`, clicking on it will register
// as an "outside" click and immediately close the overlay. This classname tells
// `react-onclickoutside` to ignore the click.
const IGNORE_CLICK_OUTSIDE = 'ignore-react-onclickoutside';

function isBody(container) {
  return container === document.body;
}

/**
 * Custom `Overlay` component, since the version in `react-overlays` doesn't
 * work for our needs. Specifically, the `Position` component doesn't provide
 * the customized placement we need.
 */
const Overlay = React.createClass({
  displayName: 'Overlay',

  propTypes: {
    container: PropTypes.oneOfType([
      componentOrElement,
      PropTypes.func,
    ]).isRequired,
    show: PropTypes.bool,
    target: PropTypes.oneOfType([
      componentOrElement,
      PropTypes.func,
    ]).isRequired,
  },

  getDefaultProps() {
    return {
      show: false,
    };
  },

  getInitialState() {
    return {
      bottom: 0,
      left: 0,
      right: 0,
      top: 0,
    };
  },

  componentDidMount() {
    this._updatePosition();
    this._updatePositionThrottled = throttle(this._updatePosition, 100);

    window.addEventListener('resize', this._updatePositionThrottled);
    window.addEventListener('scroll', this._updatePositionThrottled, true);
  },

  componentWillReceiveProps(nextProps) {
    this._updatePositionThrottled();
  },

  componentWillUnmount() {
    window.removeEventListener('resize', this._updatePositionThrottled);
    window.removeEventListener('scroll', this._updatePositionThrottled);
  },

  render() {
    if (!this.props.show) {
      return null;
    }

    const {container, children} = this.props;
    let child = Children.only(children);

    // When not attaching the overlay to `document.body` treat the child as a
    // simple inline element.
    if (!isBody(container)) {
      return child;
    }

    child = cloneElement(child, {
      ...child.props,
      className: cx(child.props.className, IGNORE_CLICK_OUTSIDE),
      style: this.state,
    });

    return (
      <Portal container={container}>
        {child}
      </Portal>
    );
  },

  _updatePosition() {
    // Positioning is only used when body is the container.
    if (!isBody(this.props.container)) {
      return;
    }

    const {target} = this.props;
    const targetElement = typeof target === 'function' ? target() : target;
    const targetNode = findDOMNode(targetElement);

    if (targetNode) {
      const {innerHeight, innerWidth, pageYOffset} = window;
      const {bottom, left, top, width} = targetNode.getBoundingClientRect();
      const newState = {
        bottom: innerHeight - pageYOffset - top,
        left,
        right: innerWidth - left - width,
        top: pageYOffset + bottom,
      };

      // Don't update unless the target element position has changed.
      if (!isEqual(this.state, newState)) {
        this.setState(newState);
      }
    }
  },
});

export default Overlay;

@@ -41,18 +41,8 @@
 qual
-, throttle
 %7D fr
@@ -54,16 +54,16 @@
 odash';%0A
+
 import R
@@ -1321,24 +1321,50 @@
 idMount() %7B%0A
+    this._mounted = true;%0A
     this._up
@@ -1375,24 +1375,25 @@
 Position();%0A
+%0A
     this._up
@@ -1420,17 +1420,54 @@
 d = 
-throttle(
+requestAnimationFrame.bind(%0A      null,%0A      
 this
@@ -1487,12 +1487,13 @@
 ion,
- 100
+%0A    
 );%0A%0A
@@ -1751,24 +1751,51 @@
 Unmount() %7B%0A
+    this._mounted = false;%0A
     window.r
@@ -2587,32 +2587,89 @@
 tainer.%0A    if (
+%0A      !this.props.show %7C%7C%0A      !this._mounted %7C%7C%0A      
 !isBody(this.pro
@@ -2673,32 +2673,37 @@
 props.container)
+%0A    
 ) %7B%0A      return

Make Overlay update its position more smoothly (#165)