UNPKG

material-ui

Version:

Material Design UI components built with React

474 lines (385 loc) 15.2 kB
'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } var React = require('react'); var ReactDOM = require('react-dom'); var update = require('react-addons-update'); var Controllable = require('../mixins/controllable'); var StylePropable = require('../mixins/style-propable'); var AutoPrefix = require('../styles/auto-prefix'); var Transitions = require('../styles/transitions'); var KeyCode = require('../utils/key-code'); var PropTypes = require('../utils/prop-types'); var List = require('../lists/list'); var Paper = require('../paper'); var DefaultRawTheme = require('../styles/raw-themes/light-raw-theme'); var ThemeManager = require('../styles/theme-manager'); var Menu = React.createClass({ displayName: 'Menu', mixins: [StylePropable, Controllable], contextTypes: { muiTheme: React.PropTypes.object }, propTypes: { animated: React.PropTypes.bool, autoWidth: React.PropTypes.bool, desktop: React.PropTypes.bool, initiallyKeyboardFocused: React.PropTypes.bool, listStyle: React.PropTypes.object, maxHeight: React.PropTypes.number, multiple: React.PropTypes.bool, onEscKeyDown: React.PropTypes.func, onItemTouchTap: React.PropTypes.func, onKeyDown: React.PropTypes.func, openDirection: PropTypes.corners, selectedMenuItemStyle: React.PropTypes.object, width: PropTypes.stringOrNumber, zDepth: PropTypes.zDepth }, getDefaultProps: function getDefaultProps() { return { animated: false, autoWidth: true, maxHeight: null, onEscKeyDown: function onEscKeyDown() {}, onItemTouchTap: function onItemTouchTap() {}, onKeyDown: function onKeyDown() {}, openDirection: 'bottom-left', zDepth: 1 }; }, //for passing default theme context to children childContextTypes: { muiTheme: React.PropTypes.object }, getChildContext: function getChildContext() { return { muiTheme: this.state.muiTheme }; }, getInitialState: function getInitialState() { var selectedIndex = this._getSelectedIndex(this.props); return { focusIndex: selectedIndex >= 0 ? selectedIndex : 0, isKeyboardFocused: this.props.initiallyKeyboardFocused, keyWidth: this.props.desktop ? 64 : 56, muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme) }; }, componentDidEnter: function componentDidEnter() { this._animateOpen(); }, componentDidMount: function componentDidMount() { if (this.props.autoWidth) this._setWidth(); if (!this.props.animated) this._animateOpen(); this._setScollPosition(); }, componentDidUpdate: function componentDidUpdate() { if (this.props.autoWidth) this._setWidth(); }, componentWillLeave: function componentWillLeave(callback) { var _this = this; var rootStyle = ReactDOM.findDOMNode(this).style; AutoPrefix.set(rootStyle, 'transition', Transitions.easeOut('250ms', ['opacity', 'transform'])); AutoPrefix.set(rootStyle, 'transform', 'translate3d(0,-8px,0)'); rootStyle.opacity = 0; setTimeout(function () { if (_this.isMounted()) callback(); }, 250); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps, nextContext) { var selectedIndex = this._getSelectedIndex(nextProps); var newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({ focusIndex: selectedIndex >= 0 ? selectedIndex : 0, keyWidth: nextProps.desktop ? 64 : 56, muiTheme: newMuiTheme }); }, render: function render() { var _this2 = this; var _props = this.props; var animated = _props.animated; var autoWidth = _props.autoWidth; var children = _props.children; var desktop = _props.desktop; var initiallyKeyboardFocused = _props.initiallyKeyboardFocused; var listStyle = _props.listStyle; var maxHeight = _props.maxHeight; var multiple = _props.multiple; var openDirection = _props.openDirection; var selectedMenuItemStyle = _props.selectedMenuItemStyle; var style = _props.style; var value = _props.value; var valueLink = _props.valueLink; var width = _props.width; var zDepth = _props.zDepth; var other = _objectWithoutProperties(_props, ['animated', 'autoWidth', 'children', 'desktop', 'initiallyKeyboardFocused', 'listStyle', 'maxHeight', 'multiple', 'openDirection', 'selectedMenuItemStyle', 'style', 'value', 'valueLink', 'width', 'zDepth']); var openDown = openDirection.split('-')[0] === 'bottom'; var openLeft = openDirection.split('-')[1] === 'left'; var styles = { root: { //Nested div bacause the List scales x faster than //it scales y transition: animated ? Transitions.easeOut('250ms', 'transform') : null, position: 'absolute', zIndex: 10, top: openDown ? 0 : null, bottom: !openDown ? 0 : null, left: !openLeft ? 0 : null, right: openLeft ? 0 : null, transform: 'scaleX(0)', transformOrigin: openLeft ? 'right' : 'left' }, list: { display: 'table-cell', paddingBottom: desktop ? 16 : 8, paddingTop: desktop ? 16 : 8, userSelect: 'none', width: width }, menuItemContainer: { transition: animated ? Transitions.easeOut(null, 'opacity') : null, opacity: 0 }, paper: { transition: animated ? Transitions.easeOut('500ms', ['transform', 'opacity']) : null, transform: 'scaleY(0)', transformOrigin: openDown ? 'top' : 'bottom', opacity: 0, maxHeight: maxHeight, overflowY: maxHeight ? 'scroll' : null }, selectedMenuItem: { color: this.state.muiTheme.rawTheme.palette.accent1Color } }; var mergedRootStyles = this.prepareStyles(styles.root, style); var mergedListStyles = this.mergeStyles(styles.list, listStyle); //Cascade children opacity var cumulativeDelay = openDown ? 175 : 325; var cascadeChildrenCount = this._getCascadeChildrenCount(); var cumulativeDelayIncrement = Math.ceil(150 / cascadeChildrenCount); var menuItemIndex = 0; var newChildren = React.Children.map(children, function (child) { var childIsADivider = child.type.displayName === 'MenuDivider'; var childIsDisabled = child.props.disabled; var childrenContainerStyles = {}; if (animated) { var focusIndex = _this2.state.focusIndex; var transitionDelay = 0; //Only cascade the visible menu items if (menuItemIndex >= focusIndex - 1 && menuItemIndex <= focusIndex + cascadeChildrenCount - 1) { cumulativeDelay = openDown ? cumulativeDelay + cumulativeDelayIncrement : cumulativeDelay - cumulativeDelayIncrement; transitionDelay = cumulativeDelay; } childrenContainerStyles = _this2.prepareStyles(styles.menuItemContainer, { transitionDelay: transitionDelay + 'ms' }); } var clonedChild = childIsADivider ? child : childIsDisabled ? React.cloneElement(child, { desktop: desktop }) : _this2._cloneMenuItem(child, menuItemIndex, styles); if (!childIsADivider && !childIsDisabled) menuItemIndex++; return animated ? React.createElement( 'div', { style: childrenContainerStyles }, clonedChild ) : clonedChild; }); return React.createElement( 'div', { onKeyDown: this._handleKeyDown, style: mergedRootStyles }, React.createElement( Paper, { ref: 'scrollContainer', style: styles.paper, zDepth: zDepth }, React.createElement( List, _extends({}, other, { ref: 'list', style: mergedListStyles }), newChildren ) ) ); }, setKeyboardFocused: function setKeyboardFocused(keyboardFocused) { this.setState({ isKeyboardFocused: keyboardFocused }); }, _animateOpen: function _animateOpen() { var rootStyle = ReactDOM.findDOMNode(this).style; var scrollContainerStyle = ReactDOM.findDOMNode(this.refs.scrollContainer).style; var menuContainers = ReactDOM.findDOMNode(this.refs.list).childNodes; AutoPrefix.set(rootStyle, 'transform', 'scaleX(1)'); AutoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)'); scrollContainerStyle.opacity = 1; for (var i = 0; i < menuContainers.length; ++i) { menuContainers[i].style.opacity = 1; } }, _cloneMenuItem: function _cloneMenuItem(child, childIndex, styles) { var _this3 = this; var _props2 = this.props; var desktop = _props2.desktop; var selectedMenuItemStyle = _props2.selectedMenuItemStyle; var selected = this._isChildSelected(child, this.props); var selectedChildrenStyles = {}; if (selected) { selectedChildrenStyles = this.mergeStyles(styles.selectedMenuItem, selectedMenuItemStyle); } var mergedChildrenStyles = this.mergeStyles(child.props.style || {}, selectedChildrenStyles); var isFocused = childIndex === this.state.focusIndex; var focusState = 'none'; if (isFocused) { focusState = this.state.isKeyboardFocused ? 'keyboard-focused' : 'focused'; } return React.cloneElement(child, { desktop: desktop, focusState: focusState, onTouchTap: function onTouchTap(e) { _this3._handleMenuItemTouchTap(e, child); if (child.props.onTouchTap) child.props.onTouchTap(e); }, ref: isFocused ? 'focusedMenuItem' : null, style: mergedChildrenStyles }); }, _decrementKeyboardFocusIndex: function _decrementKeyboardFocusIndex() { var index = this.state.focusIndex; index--; if (index < 0) index = 0; this._setFocusIndex(index, true); }, _getCascadeChildrenCount: function _getCascadeChildrenCount() { var _props3 = this.props; var children = _props3.children; var desktop = _props3.desktop; var maxHeight = _props3.maxHeight; var count = 1; var currentHeight = desktop ? 16 : 8; var menuItemHeight = desktop ? 32 : 48; //MaxHeight isn't set - cascade all of the children if (!maxHeight) return React.Children.count(children); //Count all the children that will fit inside the //max menu height React.Children.forEach(children, function (child) { if (currentHeight < maxHeight) { var childIsADivider = child.type.displayName === 'MenuDivider'; currentHeight += childIsADivider ? 16 : menuItemHeight; count++; } }); return count; }, _getMenuItemCount: function _getMenuItemCount() { var menuItemCount = 0; React.Children.forEach(this.props.children, function (child) { var childIsADivider = child.type.displayName === 'MenuDivider'; var childIsDisabled = child.props.disabled; if (!childIsADivider && !childIsDisabled) menuItemCount++; }); return menuItemCount; }, _getSelectedIndex: function _getSelectedIndex(props) { var _this4 = this; var children = props.children; var selectedIndex = -1; var menuItemIndex = 0; React.Children.forEach(children, function (child) { var childIsADivider = child.type.displayName === 'MenuDivider'; if (_this4._isChildSelected(child, props)) selectedIndex = menuItemIndex; if (!childIsADivider) menuItemIndex++; }); return selectedIndex; }, _handleKeyDown: function _handleKeyDown(e) { switch (e.keyCode) { case KeyCode.DOWN: e.preventDefault(); this._incrementKeyboardFocusIndex(); break; case KeyCode.ESC: this.props.onEscKeyDown(e); break; case KeyCode.TAB: e.preventDefault(); if (e.shiftKey) { this._decrementKeyboardFocusIndex(); } else { this._incrementKeyboardFocusIndex(); } break; case KeyCode.UP: e.preventDefault(); this._decrementKeyboardFocusIndex(); break; } this.props.onKeyDown(e); }, _handleMenuItemTouchTap: function _handleMenuItemTouchTap(e, item) { var multiple = this.props.multiple; var valueLink = this.getValueLink(this.props); var menuValue = valueLink.value; var itemValue = item.props.value; if (multiple) { var index = menuValue.indexOf(itemValue); var newMenuValue = index === -1 ? update(menuValue, { $push: [itemValue] }) : update(menuValue, { $splice: [[index, 1]] }); valueLink.requestChange(e, newMenuValue); } else if (!multiple && itemValue !== menuValue) { valueLink.requestChange(e, itemValue); } this.props.onItemTouchTap(e, item); }, _incrementKeyboardFocusIndex: function _incrementKeyboardFocusIndex() { var index = this.state.focusIndex; var maxIndex = this._getMenuItemCount() - 1; index++; if (index > maxIndex) index = maxIndex; this._setFocusIndex(index, true); }, _isChildSelected: function _isChildSelected(child, props) { var multiple = props.multiple; var menuValue = this.getValueLink(props).value; var childValue = child.props.value; return multiple && menuValue.length && menuValue.indexOf(childValue) !== -1 || !multiple && menuValue && menuValue === childValue; }, _setFocusIndex: function _setFocusIndex(newIndex, isKeyboardFocused) { this.setState({ focusIndex: newIndex, isKeyboardFocused: isKeyboardFocused }); }, _setScollPosition: function _setScollPosition() { var desktop = this.props.desktop; var focusedMenuItem = this.refs.focusedMenuItem; var menuItemHeight = desktop ? 32 : 48; if (focusedMenuItem) { var selectedOffSet = ReactDOM.findDOMNode(focusedMenuItem).offsetTop; //Make the focused item be the 2nd item in the list the //user sees var scrollTop = selectedOffSet - menuItemHeight; if (scrollTop < menuItemHeight) scrollTop = 0; ReactDOM.findDOMNode(this.refs.scrollContainer).scrollTop = scrollTop; } }, _setWidth: function _setWidth() { var el = ReactDOM.findDOMNode(this); var listEl = ReactDOM.findDOMNode(this.refs.list); var elWidth = el.offsetWidth; var keyWidth = this.state.keyWidth; var minWidth = keyWidth * 1.5; var keyIncrements = elWidth / keyWidth; var newWidth = undefined; keyIncrements = keyIncrements <= 1.5 ? 1.5 : Math.ceil(keyIncrements); newWidth = keyIncrements * keyWidth; if (newWidth < minWidth) newWidth = minWidth; el.style.width = newWidth + 'px'; listEl.style.width = newWidth + 'px'; } }); module.exports = Menu;