UNPKG

d2-ui

Version:
358 lines (303 loc) 8.81 kB
import React from 'react'; import ReactDOM from 'react-dom'; import StylePropable from '../mixins/style-propable'; import Events from '../utils/events'; import PropTypes from '../utils/prop-types'; import Menu from '../menus/menu'; import getMuiTheme from '../styles/getMuiTheme'; import Popover from '../popover/popover'; import warning from 'warning'; const IconMenu = React.createClass({ propTypes: { /** * This is the point on the icon where the menu * targetOrigin will stick to. * Options: * vertical: [top, middle, bottom] * horizontal: [left, center, right]. */ anchorOrigin: PropTypes.origin, /** * Should be used to pass `MenuItem` components. */ children: React.PropTypes.node, /** * The css class name of the root element. */ className: React.PropTypes.string, /** * If true, menu will close after an item is touchTapped. */ closeOnItemTouchTap: React.PropTypes.bool, /** * This is the IconButton to render. This button will open the menu. */ iconButtonElement: React.PropTypes.element.isRequired, /** * The style object to use to override underlying icon style. */ iconStyle: React.PropTypes.object, /** * The style object to use to override underlying menu style. */ menuStyle: React.PropTypes.object, /** * Fired when a menu item is touchTapped. */ onItemTouchTap: React.PropTypes.func, /** * Fired when keyobard focuses on element. */ onKeyboardFocus: React.PropTypes.func, /** * Fired when mouse is pressed on element. */ onMouseDown: React.PropTypes.func, /** * Fired when mouse enters the element. */ onMouseEnter: React.PropTypes.func, /** * Fired when mouse leaves the element. */ onMouseLeave: React.PropTypes.func, /** * Fired when mouse is lifted inside the element. */ onMouseUp: React.PropTypes.func, /** * Callback function that is fired when the open state * of the menu is requested to be changed. The provided * open argument determines whether the menu is requested * to be opened or closed. Also, the reason argument states * why the menu got closed or opened. It can be 'keyboard', * 'iconTap' for open action and 'enter', 'escape', 'itemTap', * 'clickAway' for close action. */ onRequestChange: React.PropTypes.func, /** * Fired when element is touch tapped. */ onTouchTap: React.PropTypes.func, /** * Controls whether the IconMenu is opened or not. */ open: React.PropTypes.bool, /** * Override the inline-styles of the root element. */ style: React.PropTypes.object, /** * This is the point on the menu which will stick to the menu * origin. * Options: * vertical: [top, middle, bottom] * horizontal: [left, center, right]. */ targetOrigin: PropTypes.origin, /** * Sets the delay in milliseconds before closing the * menu when an item is clicked. */ touchTapCloseDelay: React.PropTypes.number, }, contextTypes: { muiTheme: React.PropTypes.object, }, //for passing default theme context to children childContextTypes: { muiTheme: React.PropTypes.object, }, mixins: [ StylePropable, ], getDefaultProps() { return { closeOnItemTouchTap: true, open: null, onItemTouchTap: () => {}, onKeyboardFocus: () => {}, onMouseDown: () => {}, onMouseLeave: () => {}, onMouseEnter: () => {}, onMouseUp: () => {}, onTouchTap: () => {}, onRequestChange: () => {}, anchorOrigin: { vertical: 'top', horizontal: 'left', }, targetOrigin: { vertical: 'top', horizontal: 'left', }, touchTapCloseDelay: 200, }; }, getInitialState() { if (process.env.NODE_ENV !== 'production') { this._warningIfNeeded(); } return { muiTheme: this.context.muiTheme || getMuiTheme(), iconButtonRef: this.props.iconButtonElement.props.ref || 'iconButton', menuInitiallyKeyboardFocused: false, open: false, }; }, getChildContext() { return { muiTheme: this.state.muiTheme, }; }, //to update theme inside state whenever a new theme is passed down //from the parent / owner using context componentWillReceiveProps(nextProps, nextContext) { if (process.env.NODE_ENV !== 'production') { this._warningIfNeeded(); } let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); if (nextProps.open === true || nextProps.open === false) { this.setState({open: nextProps.open}); } }, componentWillUnmount() { if (this._timeout) clearTimeout(this._timeout); }, _warningIfNeeded() { if (this.props.hasOwnProperty('open')) { warning(this.props.hasOwnProperty('closeOnItemTouchTap'), 'closeOnItemTouchTap has been deprecated in favor of open, onRequestChange'); } }, isOpen() { return this.state.open; }, close(reason, isKeyboard) { if (!this.state.open) { return; } if (this.props.open !== null) { this.props.onRequestChange(false, reason); } this.setState({open: false}, () => { //Set focus on the icon button when the menu close if (isKeyboard) { let iconButton = this.refs[this.state.iconButtonRef]; ReactDOM.findDOMNode(iconButton).focus(); iconButton.setKeyboardFocus(); } }); }, open(reason, event) { if (this.props.open !== null) { this.props.onRequestChange(true, reason); return this.setState({ menuInitiallyKeyboardFocused: Events.isKeyboard(event), anchorEl: event.currentTarget, }); } this.setState({ open: true, menuInitiallyKeyboardFocused: Events.isKeyboard(event), anchorEl: event.currentTarget, }); event.preventDefault(); }, _handleItemTouchTap(event, child) { if (this.props.closeOnItemTouchTap) { const isKeyboard = Events.isKeyboard(event); this._timeout = setTimeout(() => { if (!this.isMounted()) { return; } this.close(isKeyboard ? 'enter' : 'itemTap', isKeyboard); }, this.props.touchTapCloseDelay); } this.props.onItemTouchTap(event, child); }, _handleMenuEscKeyDown(event) { this.close('escape', event); }, render() { let { anchorOrigin, className, closeOnItemTouchTap, iconButtonElement, iconStyle, onItemTouchTap, onKeyboardFocus, onMouseDown, onMouseLeave, onMouseEnter, onMouseUp, onTouchTap, menuStyle, style, targetOrigin, ...other, } = this.props; const {open, anchorEl} = this.state; let styles = { root: { display: 'inline-block', position: 'relative', }, menu: { position: 'relative', }, }; let mergedRootStyles = this.mergeStyles(styles.root, style); let mergedMenuStyles = this.mergeStyles(styles.menu, menuStyle); let iconButton = React.cloneElement(iconButtonElement, { onKeyboardFocus: this.props.onKeyboardFocus, iconStyle: this.mergeStyles(iconStyle, iconButtonElement.props.iconStyle), onTouchTap: (e) => { this.open(Events.isKeyboard(e) ? 'keyboard' : 'iconTap', e); if (iconButtonElement.props.onTouchTap) iconButtonElement.props.onTouchTap(e); }, ref: this.state.iconButtonRef, }); let menu = ( <Menu {...other} animateOpen={true} initiallyKeyboardFocused={this.state.menuInitiallyKeyboardFocused} onEscKeyDown={this._handleMenuEscKeyDown} onItemTouchTap={this._handleItemTouchTap} zDepth={0} style={mergedMenuStyles} > {this.props.children} </Menu> ); return ( <div className={className} onMouseDown={onMouseDown} onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} onMouseUp={onMouseUp} onTouchTap={onTouchTap} style={this.prepareStyles(mergedRootStyles)} > {iconButton} <Popover anchorOrigin={anchorOrigin} targetOrigin={targetOrigin} open={open} anchorEl={anchorEl} childContextTypes={this.constructor.childContextTypes} useLayerForClickAway={false} onRequestClose={this.close} context={this.context} > {menu} </Popover> </div> ); }, }); export default IconMenu;