@ysmood/material-ui
Version:
Material Design UI components built with React
422 lines (352 loc) • 11.3 kB
JSX
var React = require('react');
var CssEvent = require('../utils/css-event');
var Dom = require('../utils/dom');
var KeyLine = require('../utils/key-line');
var StylePropable = require('../mixins/style-propable');
var Transitions = require('../styles/transitions');
var ClickAwayable = require('../mixins/click-awayable');
var Paper = require('../paper');
var MenuItem = require('./menu-item');
var LinkMenuItem = require('./link-menu-item');
var SubheaderMenuItem = require('./subheader-menu-item');
/***********************
* Nested Menu Component
***********************/
var NestedMenuItem = React.createClass({
mixins: [ClickAwayable, StylePropable],
contextTypes: {
muiTheme: React.PropTypes.object
},
propTypes: {
index: React.PropTypes.number.isRequired,
text: React.PropTypes.string,
menuItems: React.PropTypes.array.isRequired,
zDepth: React.PropTypes.number,
disabled: React.PropTypes.bool,
onItemClick: React.PropTypes.func,
onItemTap: React.PropTypes.func,
menuItemStyle: React.PropTypes.object,
},
getDefaultProps: function() {
return {
disabled: false
};
},
getInitialState: function() {
return { open: false }
},
componentClickAway: function() {
this._closeNestedMenu();
},
componentDidMount: function() {
this._positionNestedMenu();
},
componentDidUpdate: function() {
this._positionNestedMenu();
},
getSpacing: function() {
return this.context.muiTheme.spacing;
},
render: function() {
var styles = this.mergeAndPrefix({
position: 'relative'
}, this.props.style);
var iconCustomArrowDropRight = {
marginRight: this.getSpacing().desktopGutterMini * -1,
color: this.context.muiTheme.component.dropDownMenu.accentColor
};
var {
index,
menuItemStyle,
...other
} = this.props;
return (
<div ref="root" style={styles} onMouseEnter={this._openNestedMenu} onMouseLeave={this._closeNestedMenu}>
<MenuItem
index={index}
style={menuItemStyle}
disabled={this.props.disabled}
iconRightStyle={iconCustomArrowDropRight}
iconRightClassName="muidocs-icon-custom-arrow-drop-right"
onTouchTap={this._onParentItemClick}>
{this.props.text}
</MenuItem>
<Menu {...other}
ref="nestedMenu"
menuItems={this.props.menuItems}
onItemClick={this._onMenuItemClick}
onItemTap={this._onMenuItemTap}
hideable={true}
visible={this.state.open}
zDepth={this.props.zDepth + 1} />
</div>
);
},
_positionNestedMenu: function() {
var el = React.findDOMNode(this);
var nestedMenu = React.findDOMNode(this.refs.nestedMenu);
nestedMenu.style.left = el.offsetWidth + 'px';
},
_openNestedMenu: function() {
if (!this.props.disabled) this.setState({ open: true });
},
_closeNestedMenu: function() {
this.setState({ open: false });
},
_toggleNestedMenu: function() {
if (!this.props.disabled) this.setState({ open: !this.state.open });
},
_onParentItemClick: function() {
this._toggleNestedMenu();
},
_onMenuItemClick: function(e, index, menuItem) {
if (this.props.onItemClick) this.props.onItemClick(e, index, menuItem);
this._closeNestedMenu();
},
_onMenuItemTap: function(e, index, menuItem) {
if (this.props.onItemTap) this.props.onItemTap(e, index, menuItem);
this._closeNestedMenu();
}
});
/****************
* Menu Component
****************/
var Menu = React.createClass({
mixins: [StylePropable],
contextTypes: {
muiTheme: React.PropTypes.object
},
propTypes: {
autoWidth: React.PropTypes.bool,
onItemTap: React.PropTypes.func,
onItemClick: React.PropTypes.func,
onToggle: React.PropTypes.func,
menuItems: React.PropTypes.array.isRequired,
selectedIndex: React.PropTypes.number,
hideable: React.PropTypes.bool,
visible: React.PropTypes.bool,
zDepth: React.PropTypes.number,
menuItemStyle: React.PropTypes.object,
menuItemStyleSubheader: React.PropTypes.object,
menuItemStyleLink: React.PropTypes.object,
menuItemClassName: React.PropTypes.string,
menuItemClassNameSubheader: React.PropTypes.string,
menuItemClassNameLink: React.PropTypes.string,
},
getInitialState: function() {
return { nestedMenuShown: false }
},
getDefaultProps: function() {
return {
autoWidth: true,
hideable: false,
visible: true,
zDepth: 1,
};
},
componentDidMount: function() {
var el = React.findDOMNode(this);
//Set the menu width
this._setKeyWidth(el);
//Save the initial menu item height for later
this._initialMenuItemHeight = el.offsetHeight / Math.max(1, this.props.menuItems.length);
//Show or Hide the menu according to visibility
this._renderVisibility();
},
componentDidUpdate: function(prevProps) {
if (this.props.visible !== prevProps.visible) this._renderVisibility();
},
getTheme: function() {
return this.context.muiTheme.component.menu
},
getSpacing: function() {
return this.context.muiTheme.spacing;
},
getStyles: function() {
var styles = {
root: {
backgroundColor: this.getTheme().containerBackgroundColor,
paddingTop: this.getSpacing().desktopGutterMini,
paddingBottom: this.getSpacing().desktopGutterMini,
transition: Transitions.easeOut(null, 'height')
},
subheader: {
paddingLeft: this.context.muiTheme.component.menuSubheader.padding,
paddingRight: this.context.muiTheme.component.menuSubheader.padding
},
hideable: {
opacity: (this.props.visible) ? 1 : 0,
overflow: 'hidden',
position: 'absolute',
top: 0,
zIndex: 1
}
};
return styles;
},
render: function() {
var styles = this.getStyles();
return (
<Paper
ref="paperContainer"
zDepth={this.props.zDepth}
style={this.mergeAndPrefix(
styles.root,
this.props.hideable && styles.hideable,
this.props.style)}>
{this._getChildren()}
</Paper>
);
},
_getChildren: function() {
var children = [],
menuItem,
itemComponent,
isSelected,
isDisabled;
var styles = this.getStyles();
//This array is used to keep track of all nested menu refs
this._nestedChildren = [];
for (var i=0; i < this.props.menuItems.length; i++) {
menuItem = this.props.menuItems[i];
isSelected = i === this.props.selectedIndex;
isDisabled = (menuItem.disabled === undefined) ? false : menuItem.disabled;
let {
icon,
data,
attribute,
number,
toggle,
onClick,
...other
} = menuItem;
switch (menuItem.type) {
case MenuItem.Types.LINK:
itemComponent = (
<LinkMenuItem
key={i}
index={i}
text={menuItem.text}
disabled={isDisabled}
className={this.props.menuItemClassNameLink}
style={this.props.menuItemStyleLink}
payload={menuItem.payload}
target={menuItem.target}/>
);
break;
case MenuItem.Types.SUBHEADER:
itemComponent = (
<SubheaderMenuItem
key={i}
index={i}
className={this.props.menuItemClassNameSubheader}
style={this.mergeAndPrefix(styles.subheader)}
firstChild={i === 0}
text={menuItem.text} />
);
break;
case MenuItem.Types.NESTED:
let {
ref,
key,
index,
zDepth,
...other
} = this.props;
itemComponent = (
<NestedMenuItem
{...other}
ref={i}
key={i}
index={i}
text={menuItem.text}
disabled={isDisabled}
menuItems={menuItem.items}
menuItemStyle={this.props.menuItemStyle}
zDepth={this.props.zDepth}
onItemClick={this._onNestedItemClick}
onItemTap={this._onNestedItemClick} />
);
this._nestedChildren.push(i);
break;
default:
itemComponent = (
<MenuItem
{...other}
selected={isSelected}
key={i}
index={i}
icon={menuItem.icon}
data={menuItem.data}
className={this.props.menuItemClassName}
style={this.props.menuItemStyle}
attribute={menuItem.attribute}
number={menuItem.number}
toggle={menuItem.toggle}
onToggle={this.props.onToggle}
disabled={isDisabled}
onClick={this._onItemClick}
onTouchTap={this._onItemTap}>
{menuItem.text}
</MenuItem>
);
}
children.push(itemComponent);
}
return children;
},
_setKeyWidth: function(el) {
var menuWidth = this.props.autoWidth ?
KeyLine.getIncrementalDim(el.offsetWidth) + 'px' :
'100%';
//Update the menu width
Dom.withoutTransition(el, function() {
el.style.width = menuWidth;
});
},
_getCurrentHeight: function() {
var totalItens = Math.max(1, this.props.menuItems.length);
var newHeight = this._initialMenuItemHeight * totalItens;
return newHeight + KeyLine.Desktop.GUTTER_LESS;
},
_renderVisibility: function() {
var el;
if (this.props.hideable) {
el = React.findDOMNode(this);
var container = React.findDOMNode(this.refs.paperContainer);
if (this.props.visible) {
//Open the menu
el.style.transition = Transitions.easeOut();
el.style.height = this._getCurrentHeight() + 'px';
//Set the overflow to visible after the animation is done so
//that other nested menus can be shown
CssEvent.onTransitionEnd(el, function() {
//Make sure the menu is open before setting the overflow.
//This is to accout for fast clicks
if (this.props.visible) container.style.overflow = 'visible';
}.bind(this));
} else {
//Close the menu
el.style.height = '0px';
//Set the overflow to hidden so that animation works properly
container.style.overflow = 'hidden';
}
}
},
_onNestedItemClick: function(e, index, menuItem) {
if (this.props.onItemClick) this.props.onItemClick(e, index, menuItem);
},
_onNestedItemTap: function(e, index, menuItem) {
if (this.props.onItemTap) this.props.onItemTap(e, index, menuItem);
},
_onItemClick: function(e, index) {
if (this.props.onItemClick) this.props.onItemClick(e, index, this.props.menuItems[index]);
},
_onItemTap: function(e, index) {
if (this.props.onItemTap) this.props.onItemTap(e, index, this.props.menuItems[index]);
},
_onItemToggle: function(e, index, toggled) {
if (this.props.onItemToggle) this.props.onItemToggle(e, index, this.props.menuItems[index], toggled);
}
});
module.exports = Menu;