You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Lidarr/frontend/src/Components/Menu/Menu.js

208 lines
4.0 KiB

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TetherComponent from 'react-tether';
import { align } from 'Helpers/Props';
import styles from './Menu.css';
const baseTetherOptions = {
skipMoveElement: true,
constraints: [
{
to: 'window',
attachment: 'together',
pin: true
}
]
};
const tetherOptions = {
[align.RIGHT]: {
...baseTetherOptions,
attachment: 'top right',
targetAttachment: 'bottom right'
},
[align.LEFT]: {
...baseTetherOptions,
attachment: 'top left',
targetAttachment: 'bottom left'
}
};
class Menu extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isMenuOpen: false,
maxHeight: 0
};
}
componentDidMount() {
this.setMaxHeight();
}
componentWillUnmount() {
this._removeListener();
}
//
// Control
getMaxHeight() {
if (!this.props.enforceMaxHeight) {
return;
}
const menu = ReactDOM.findDOMNode(this.refs.menu);
if (!menu) {
return;
}
const { bottom } = menu.getBoundingClientRect();
const maxHeight = window.innerHeight - bottom;
return maxHeight;
}
setMaxHeight() {
this.setState({
maxHeight: this.getMaxHeight()
});
}
_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 menu = ReactDOM.findDOMNode(this.refs.menu);
const menuContent = ReactDOM.findDOMNode(this.refs.menuContent);
if (!menu) {
return;
}
if ((!menu.contains(event.target) || menuContent.contains(event.target)) && this.state.isMenuOpen) {
this.setState({ isMenuOpen: false });
this._removeListener();
}
}
onWindowResize = () => {
this.setMaxHeight();
}
onWindowScroll = () => {
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
}
);
const content = React.cloneElement(
childrenArray[1],
{
ref: 'menuContent',
alignMenu,
maxHeight,
isOpen: isMenuOpen
}
);
return (
<TetherComponent
classes={{
element: styles.tether
}}
{...tetherOptions[alignMenu]}
>
<div
ref="menu"
className={className}
>
{button}
</div>
{
isMenuOpen &&
content
}
</TetherComponent>
);
}
}
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;