UNPKG

@ysmood/material-ui

Version:

Material Design UI components built with React

296 lines (252 loc) 8.34 kB
var React = require('react'); var KeyCode = require('./utils/key-code'); var StylePropable = require('./mixins/style-propable'); var AutoPrefix = require('./styles/auto-prefix'); var Transitions = require('./styles/transitions'); var WindowListenable = require('./mixins/window-listenable'); var Overlay = require('./overlay'); var Paper = require('./paper'); var Menu = require('./menu/menu'); var LeftNav = React.createClass({ mixins: [StylePropable, WindowListenable], contextTypes: { muiTheme: React.PropTypes.object }, propTypes: { className: React.PropTypes.string, docked: React.PropTypes.bool, header: React.PropTypes.element, menuItems: React.PropTypes.array.isRequired, onChange: React.PropTypes.func, onNavOpen: React.PropTypes.func, onNavClose: React.PropTypes.func, openRight: React.PropTypes.bool, selectedIndex: React.PropTypes.number }, windowListeners: { 'keyup': '_onWindowKeyUp', 'resize': '_onWindowResize' }, getDefaultProps: function() { return { docked: true }; }, getInitialState: function() { return { open: this.props.docked, maybeSwiping: false, swiping: false }; }, componentDidMount: function() { this._updateMenuHeight(); this._enableSwipeHandling(); }, componentDidUpdate: function(prevProps, prevState) { this._updateMenuHeight(); this._enableSwipeHandling(); }, componentWillUnmount: function() { this._disableSwipeHandling(); }, toggle: function() { this.setState({ open: !this.state.open }); return this; }, close: function() { this.setState({ open: false }); if (this.props.onNavClose) this.props.onNavClose(); return this; }, open: function() { this.setState({ open: true }); if (this.props.onNavOpen) this.props.onNavOpen(); return this; }, getThemePalette: function() { return this.context.muiTheme.palette; }, getTheme: function() { return this.context.muiTheme.component.leftNav; }, getStyles: function() { var x = this._getTranslateMultiplier() * (this.state.open ? 0 : this._getMaxTranslateX()) + 'px'; var styles = { root: { height: '100%', width: this.getTheme().width, position: 'fixed', zIndex: 10, left: 0, top: 0, transform: 'translate3d(' + x + ', 0, 0)', transition: !this.state.swiping && Transitions.easeOut(), backgroundColor: this.getTheme().color, overflow: 'hidden' }, menu: { overflowY: 'auto', overflowX: 'hidden', height: '100%' }, menuItem: { height: this.context.muiTheme.spacing.desktopLeftNavMenuItemHeight, lineHeight: this.context.muiTheme.spacing.desktopLeftNavMenuItemHeight + 'px' }, rootWhenOpenRight: { left: 'auto', right: '0' } }; styles.menuItemLink = this.mergeAndPrefix(styles.menuItem, { display: 'block', textDecoration: 'none', color: this.getThemePalette().textColor }); styles.menuItemSubheader = this.mergeAndPrefix(styles.menuItem, { overflow: 'hidden' }); return styles; }, render: function() { var selectedIndex = this.props.selectedIndex; var overlay; var styles = this.getStyles(); if (!this.props.docked) { overlay = <Overlay ref="overlay" show={this.state.open} transitionEnabled={!this.state.swiping} onTouchTap={this._onOverlayTouchTap} />; } return ( <div className={this.props.className}> {overlay} <Paper ref="clickAwayableElement" zDepth={2} rounded={false} transitionEnabled={!this.state.swiping} style={this.mergeAndPrefix( styles.root, this.props.openRight && styles.rootWhenOpenRight, this.props.style)}> {this.props.header} <Menu ref="menuItems" style={this.mergeAndPrefix(styles.menu)} zDepth={0} menuItems={this.props.menuItems} menuItemStyle={this.mergeAndPrefix(styles.menuItem)} menuItemStyleLink={this.mergeAndPrefix(styles.menuItemLink)} menuItemStyleSubheader={this.mergeAndPrefix(styles.menuItemSubheader)} selectedIndex={selectedIndex} onItemTap={this._onMenuItemClick} /> </Paper> </div> ); }, _updateMenuHeight: function() { if (this.props.header) { var container = React.findDOMNode(this.refs.clickAwayableElement); var menu = React.findDOMNode(this.refs.menuItems); var menuHeight = container.clientHeight - menu.offsetTop; menu.style.height = menuHeight + 'px'; } }, _onMenuItemClick: function(e, key, payload) { if (this.props.onChange && this.props.selectedIndex !== key) { this.props.onChange(e, key, payload); } if (!this.props.docked) this.close(); }, _onOverlayTouchTap: function() { this.close(); }, _onWindowKeyUp: function(e) { if (e.keyCode == KeyCode.ESC && !this.props.docked && this.state.open) { this.close(); } }, _onWindowResize: function(e) { this._updateMenuHeight(); }, _getMaxTranslateX: function() { return this.getTheme().width + 10; }, _getTranslateMultiplier: function() { return this.props.openRight ? 1 : -1; }, _enableSwipeHandling: function() { if (this.state.open && !this.props.docked) { document.body.addEventListener('touchstart', this._onBodyTouchStart); } else { this._disableSwipeHandling(); } }, _disableSwipeHandling: function() { document.body.removeEventListener('touchstart', this._onBodyTouchStart); }, _onBodyTouchStart: function(e) { var touchStartX = e.touches[0].pageX; var touchStartY = e.touches[0].pageY; this.setState({ maybeSwiping: true, touchStartX: touchStartX, touchStartY: touchStartY }); document.body.addEventListener('touchmove', this._onBodyTouchMove); document.body.addEventListener('touchend', this._onBodyTouchEnd); document.body.addEventListener('touchcancel', this._onBodyTouchEnd); }, _onBodyTouchMove: function(e) { var currentX = e.touches[0].pageX; var currentY = e.touches[0].pageY; if (this.state.swiping) { e.preventDefault(); var translateX = Math.min( Math.max( this._getTranslateMultiplier() * (currentX - this.state.swipeStartX), 0 ), this._getMaxTranslateX() ); var leftNav = React.findDOMNode(this.refs.clickAwayableElement); leftNav.style[AutoPrefix.single('transform')] = 'translate3d(' + (this._getTranslateMultiplier() * translateX) + 'px, 0, 0)'; this.refs.overlay.setOpacity(1 - translateX / this._getMaxTranslateX()); } else if (this.state.maybeSwiping) { var dXAbs = Math.abs(currentX - this.state.touchStartX); var dYAbs = Math.abs(currentY - this.state.touchStartY); // If the user has moved his thumb ten pixels in either direction, // we can safely make an assumption about whether he was intending // to swipe or scroll. var threshold = 10; if (dXAbs > threshold && dYAbs <= threshold) { this.setState({ swiping: true, swipeStartX: currentX }); } else if (dXAbs <= threshold && dYAbs > threshold) { this._onBodyTouchEnd(); } } }, _onBodyTouchEnd: function() { var shouldClose = false; if (this.state.swiping) shouldClose = true; this.setState({ maybeSwiping: false, swiping: false }); // We have to call close() after setting swiping to false, // because only then CSS transition is enabled. if (shouldClose) this.close(); document.body.removeEventListener('touchmove', this._onBodyTouchMove); document.body.removeEventListener('touchend', this._onBodyTouchEnd); document.body.removeEventListener('touchcancel', this._onBodyTouchEnd); } }); module.exports = LeftNav;