// Package imports. import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import Overlay from 'react-overlays/lib/Overlay'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; import ComposerOptionsDropdownContent from './content'; // Utils. import { isUserTouching } from 'flavours/glitch/util/is_mobile'; import { assignHandlers } from 'flavours/glitch/util/react_helpers'; // Handlers. const handlers = { // Closes the dropdown. handleClose () { this.setState({ open: false }); }, // The enter key toggles the dropdown's open state, and the escape // key closes it. handleKeyDown ({ key }) { const { handleClose, handleToggle, } = this.handlers; switch (key) { case 'Enter': handleToggle(key); break; case 'Escape': handleClose(); break; } }, // Creates an action modal object. handleMakeModal () { const component = this; const { items, onChange, onModalOpen, onModalClose, value, } = this.props; // Required props. if (!(onChange && onModalOpen && onModalClose && items)) { return null; } // The object. return { actions: items.map( ({ name, ...rest }) => ({ ...rest, active: value && name === value, name, onClick (e) { e.preventDefault(); // Prevents focus from changing onModalClose(); onChange(name); }, onPassiveClick (e) { e.preventDefault(); // Prevents focus from changing onChange(name); component.setState({ needsModalUpdate: true }); }, }) ), }; }, // Toggles opening and closing the dropdown. handleToggle ({ target }) { const { handleMakeModal } = this.handlers; const { onModalOpen } = this.props; const { open } = this.state; // If this is a touch device, we open a modal instead of the // dropdown. if (isUserTouching()) { // This gets the modal to open. const modal = handleMakeModal(); // If we can, we then open the modal. if (modal && onModalOpen) { onModalOpen(modal); return; } } const { top } = target.getBoundingClientRect(); this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); // Otherwise, we just set our state to open. this.setState({ open: !open }); }, // If our modal is open and our props update, we need to also update // the modal. handleUpdate () { const { handleMakeModal } = this.handlers; const { onModalOpen } = this.props; const { needsModalUpdate } = this.state; // Gets our modal object. const modal = handleMakeModal(); // Reopens the modal with the new object. if (needsModalUpdate && modal && onModalOpen) { onModalOpen(modal); } }, }; // The component. export default class ComposerOptionsDropdown extends React.PureComponent { // Constructor. constructor (props) { super(props); assignHandlers(this, handlers); this.state = { needsModalUpdate: false, open: false, placement: 'bottom', }; } // Updates our modal as necessary. componentDidUpdate (prevProps) { const { handleUpdate } = this.handlers; const { items } = this.props; const { needsModalUpdate } = this.state; if (needsModalUpdate && items.find( (item, i) => item.on !== prevProps.items[i].on )) { handleUpdate(); this.setState({ needsModalUpdate: false }); } } // Rendering. render () { const { handleClose, handleKeyDown, handleToggle, } = this.handlers; const { active, disabled, title, icon, items, onChange, value, } = this.props; const { open, placement } = this.state; const computedClass = classNames('composer--options--dropdown', { active, open, top: placement === 'top', }); // The result. return (
); } } // Props. ComposerOptionsDropdown.propTypes = { active: PropTypes.bool, disabled: PropTypes.bool, icon: PropTypes.string, items: PropTypes.arrayOf(PropTypes.shape({ icon: PropTypes.string, meta: PropTypes.node, name: PropTypes.string.isRequired, on: PropTypes.bool, text: PropTypes.node, })).isRequired, onChange: PropTypes.func, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, title: PropTypes.string, value: PropTypes.string, };