d2-ui
Version:
358 lines (303 loc) • 8.81 kB
JSX
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;