import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Manager, Popper, Reference } from 'react-popper'; import getUniqueElememtId from 'Utilities/getUniqueElementId'; import { align } from 'Helpers/Props'; import Portal from 'Components/Portal'; import styles from './Menu.css'; const sharedPopperOptions = { modifiers: { preventOverflow: { padding: 0 }, flip: { padding: 0 } } }; const popperOptions = { [align.RIGHT]: { ...sharedPopperOptions, placement: 'bottom-end' }, [align.LEFT]: { ...sharedPopperOptions, placement: 'bottom-start' } }; class Menu extends Component { // // Lifecycle constructor(props, context) { super(props, context); this._scheduleUpdate = null; this._menuButtonId = getUniqueElememtId(); this.state = { isMenuOpen: false, maxHeight: 0 }; } componentDidMount() { this.setMaxHeight(); } componentDidUpdate() { if (this._scheduleUpdate) { this._scheduleUpdate(); } } componentWillUnmount() { this._removeListener(); } // // Control getMaxHeight() { if (!this.props.enforceMaxHeight) { return; } const menuButton = document.getElementById(this._menuButtonId); if (!menuButton) { return; } const { bottom } = menuButton.getBoundingClientRect(); const maxHeight = window.innerHeight - bottom; return maxHeight; } setMaxHeight() { const maxHeight = this.getMaxHeight(); if (maxHeight !== this.state.maxHeight) { this.setState({ maxHeight }); } } _addListener() { // Listen to resize events on the window and scroll events // on all elements to ensure the menu is the best size possible. // Listen for click events on the window to support closing the // menu on clicks outside. window.addEventListener('resize', this.onWindowResize); window.addEventListener('scroll', this.onWindowScroll, { capture: true }); window.addEventListener('click', this.onWindowClick); } _removeListener() { window.removeEventListener('resize', this.onWindowResize); window.removeEventListener('scroll', this.onWindowScroll, { capture: true }); window.removeEventListener('click', this.onWindowClick); } // // Listeners onWindowClick = (event) => { const menuButton = document.getElementById(this._menuButtonId); if (!menuButton) { return; } if (!menuButton.contains(event.target) && this.state.isMenuOpen) { this.setState({ isMenuOpen: false }); this._removeListener(); } } onWindowResize = () => { this.setMaxHeight(); } onWindowScroll = (event) => { if (this.state.isMenuOpen) { this.setMaxHeight(); } } onMenuButtonPress = () => { const state = { isMenuOpen: !this.state.isMenuOpen }; if (this.state.isMenuOpen) { this._removeListener(); } else { state.maxHeight = this.getMaxHeight(); this._addListener(); } this.setState(state); } // // Render render() { const { className, children, alignMenu } = this.props; const { maxHeight, isMenuOpen } = this.state; const childrenArray = React.Children.toArray(children); const button = React.cloneElement( childrenArray[0], { onPress: this.onMenuButtonPress } ); return ( {({ ref }) => (
{button}
)}
{({ ref, style, scheduleUpdate }) => { this._scheduleUpdate = scheduleUpdate; return React.cloneElement( childrenArray[1], { forwardedRef: ref, style: { ...style, maxHeight }, isOpen: isMenuOpen } ); }}
); } } Menu.propTypes = { className: PropTypes.string, children: PropTypes.node.isRequired, alignMenu: PropTypes.oneOf([align.LEFT, align.RIGHT]), enforceMaxHeight: PropTypes.bool.isRequired }; Menu.defaultProps = { className: styles.menu, alignMenu: align.LEFT, enforceMaxHeight: true }; export default Menu;