UNPKG

ddm-dropdown

Version:

Dropdown component used in Deseret Digital projects.

264 lines (214 loc) 5.91 kB
var React = require('react'); var ReactDOM = require('react-dom'); var cx = require('classnames'); var $ = require('jquery'); var DropdownToggle = require('./DropdownToggle'); var DropdownBody = require('./DropdownBody'); var ddmDropdownId = 0; function getDdmDropdownId() { return ddmDropdownId++; } var Dropdown = React.createClass({ propTypes: { title: React.PropTypes.string, className: React.PropTypes.string, url: React.PropTypes.string, arrow: React.PropTypes.bool, hover: React.PropTypes.bool, hoverDelay: React.PropTypes.number, onOpen: React.PropTypes.func, onClose: React.PropTypes.func, links: React.PropTypes.array }, getDefaultProps: function() { return { title: '', className: '', url: null, arrow: true, hover: true, hoverDelay: 300, onOpen: null, onClose: null, links: [] }; }, getInitialState: function() { return { ddmDropdownId: null, open: false, hoverTimeout: null, }; }, toggleOpenState: function() { if (this.state.open) { this.close(); } else { this.open(); } }, open: function() { this.addDocumentCloseHandlers(); this.setState({open: true}); if (this.props.onOpen) { this.props.onOpen(); } $(ReactDOM.findDOMNode(this)).trigger('open.ddm.dropdown'); }, close: function() { this.removeDocumentCloseHandlers(); this.setState({open: false}); if (this.props.onClose) { this.props.onClose(); } $(ReactDOM.findDOMNode(this)).trigger('close.ddm.dropdown'); }, renderToggle: function() { var toggle = null; React.Children.forEach(this.props.children, function(child) { if (child.type === DropdownToggle) { toggle = React.cloneElement(child, { open: this.state.open, onToggleClick: this.handleClick, ref: 'dropdownToggle' }); } }.bind(this)); if (toggle === null) { toggle = ( <DropdownToggle href={this.props.url} open={this.state.open} arrow={this.props.arrow} onToggleClick={this.handleClick} ref="dropdownToggle" > {this.props.title} </DropdownToggle> ); } return toggle; }, renderBody: function() { var body = null, displayChildren = []; // Make sure we do not render a dropdownToggle inside of the body React.Children.forEach(this.props.children, function(child) { if (child.type !== DropdownToggle) { displayChildren.push(child); } }); React.Children.forEach(displayChildren, function(child) { if (child.type === DropdownBody) { body = React.cloneElement(child, { ref: 'dropdownBody' }); } }); if (body === null) { body = ( <DropdownBody links={this.props.links} ref="dropdownBody"> {displayChildren} </DropdownBody> ); } return body; }, render: function() { var dropdownClasses = { 'ddm-dropdown': true, 'ddm-dropdown--open': this.state.open }; this.props.className.split(' ').forEach(function(className) { dropdownClasses[className] = true; }); var toggle = this.renderToggle(); var body = this.renderBody(); return ( <div className={cx(dropdownClasses)} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} > {toggle} {body} </div> ); }, componentDidUpdate: function(prevProps, prevState) { if (this.state.open && !prevState.open) { this.refs.dropdownBody.scrollTop = 0; } if (this.state.open) { $('body').addClass('ddm-dropdown-is-open'); } else { $('body').removeClass('ddm-dropdown-is-open'); } }, componentDidMount: function() { this.setState({ddmDropdownId: getDdmDropdownId()}); }, componentWillUnmount: function() { this.removeDocumentCloseHandlers(); }, addDocumentCloseHandlers: function() { $(document).on('click.ddm.dropdown.' + this.state.ddmDropdownId, this.handleDocumentClick); $(document).on('keyup.ddm.dropdown.' + this.state.ddmDropdownId, this.handleDocumentKeyUp); $(document).on('open.ddm.dropdown.' + this.state.ddmDropdownId, this.handleDropdownOpen); }, removeDocumentCloseHandlers: function() { $(document).off('click.ddm.dropdown.' + this.state.ddmDropdownId); $(document).off('keyup.ddm.dropdown.' + this.state.ddmDropdownId); $(document).off('open.ddm.dropdown.' + this.state.ddmDropdownId); }, handleClick: function() { this.toggleOpenState(); }, handleMouseEnter: function() { if (!this.props.hover) { return; } clearTimeout(this.state.hoverTimeout); var hoverTimeout = setTimeout(function() { this.open(); }.bind(this), this.props.hoverDelay); this.setState({hoverTimeout: hoverTimeout}); }, handleMouseLeave: function() { if (!this.props.hover) { return; } clearTimeout(this.state.hoverTimeout); var hoverTimeout = setTimeout(function() { this.close(); }.bind(this), this.props.hoverDelay); this.setState({hoverTimeout: hoverTimeout}); }, handleDocumentClick: function(e) { if (this.isNodeInComponent(e.target)) { return; } this.close(); }, handleDocumentKeyUp: function(e) { if (e.keyCode === 27) { this.close(); } }, handleDropdownOpen: function(e) { if (this.isNodeInComponent(e.target)) { return; } this.close(); }, isNodeInComponent: function(node) { var componentNode = ReactDOM.findDOMNode(this); while (node) { if (node === componentNode) { return true; } node = node.parentNode; } return false; } }); module.exports = Dropdown;