import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { findDOMNode } from 'react-dom'; import cx from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import Button from '/imports/ui/components/button/component'; import styles from './styles'; import DropdownTrigger from './trigger/component'; import DropdownContent from './content/component'; const intlMessages = defineMessages({ close: { id: 'app.dropdown.close', description: 'Close button label', }, }); const noop = () => {}; const propTypes = { /** * The dropdown needs a trigger and a content component as children */ children: (props, propName, componentName) => { const children = props[propName]; if (!children || children.length < 2) { return new Error( `Invalid prop \`${propName}\` supplied to` + ` \`${componentName}\`. Validation failed.`, ); } const trigger = children.find(x => x.type === DropdownTrigger); const content = children.find(x => x.type === DropdownContent); if (!trigger) { return new Error( `Invalid prop \`${propName}\` supplied to` + ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`, ); } if (!content) { return new Error( `Invalid prop \`${propName}\` supplied to` + ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`, ); } return null; }, isOpen: PropTypes.bool, onHide: PropTypes.func, onShow: PropTypes.func, autoFocus: PropTypes.bool, }; const defaultProps = { children: null, isOpen: false, onShow: noop, onHide: noop, autoFocus: false, }; class Dropdown extends Component { constructor(props) { super(props); this.state = { isOpen: false }; this.handleShow = this.handleShow.bind(this); this.handleHide = this.handleHide.bind(this); this.handleStateCallback = this.handleStateCallback.bind(this); this.handleToggle = this.handleToggle.bind(this); this.handleWindowClick = this.handleWindowClick.bind(this); } componentDidUpdate(prevProps, prevState) { if (prevState.isOpen !== this.props.isOpen && this.state.isOpen !== this.props.isOpen) { this.setState({ isOpen: this.props.isOpen }, this.handleStateCallback); } } handleStateCallback() { const { onShow, onHide } = this.props; if (this.state.isOpen && onShow) { onShow(); } else if (onHide) { onHide(); } } handleShow() { const { addEventListener } = window; addEventListener('click', this.handleWindowClick, false); this.setState({ isOpen: true }, this.handleStateCallback); } handleHide() { const { removeEventListener } = window; removeEventListener('click', this.handleWindowClick, false); const { autoFocus } = this.props; this.setState({ isOpen: false }, this.handleStateCallback); if (autoFocus) { const triggerElement = findDOMNode(this.trigger); triggerElement.focus(); } } handleWindowClick(event) { if (this.state.isOpen) { const dropdownElement = findDOMNode(this); const shouldUpdateState = event.target !== dropdownElement && !dropdownElement.contains(event.target) && this.state.isOpen; if (shouldUpdateState) { this.handleHide(); } } } handleToggle() { this.state.isOpen ? this.handleHide() : this.handleShow(); } render() { const { children, className, style, intl, ...otherProps } = this.props; let trigger = children.find(x => x.type === DropdownTrigger); let content = children.find(x => x.type === DropdownContent); trigger = React.cloneElement(trigger, { ref: (ref) => { this.trigger = ref; }, dropdownToggle: this.handleToggle, dropdownShow: this.handleShow, dropdownHide: this.handleHide, }); content = React.cloneElement(content, { ref: (ref) => { this.content = ref; }, 'aria-expanded': this.state.isOpen, dropdownToggle: this.handleToggle, dropdownShow: this.handleShow, dropdownHide: this.handleHide, }); return (
{trigger} {content} {this.state.isOpen ?
); } } Dropdown.propTypes = propTypes; Dropdown.defaultProps = defaultProps; export default injectIntl(Dropdown);