UNPKG

@ysmood/material-ui

Version:

Material Design UI components built with React

422 lines (352 loc) 11.3 kB
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;