UNPKG

@anterostecnologia/anteros-react-admin

Version:

Anteros components for React - Admin menu

357 lines (321 loc) 10.4 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; const CANCEL_DISTANCE_ON_SCROLL = 20; const defaultStyles = { root: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, overflow: 'hidden', }, sidebar: { zIndex: 10, position: 'absolute', top: 0, bottom: 0, transition: 'transform .3s ease-out', WebkitTransition: '-webkit-transform .3s ease-out', willChange: 'transform', overflowY: 'hidden', }, content: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, overflowY: 'scroll', WebkitOverflowScrolling: 'touch', transition: 'left .3s ease-out, right .3s ease-out', }, overlay: { zIndex: 9, position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, opacity: 0, visibility: 'hidden', transition: 'opacity .3s ease-out, visibility .3s ease-out', backgroundColor: 'rgba(0,0,0,.3)', }, dragHandle: { zIndex: 1, position: 'fixed', top: 0, bottom: 0, }, }; export default class AnterosSidebarLayout extends Component { constructor(props) { super(props); this.state = { sidebarWidth: props.menuOpened ? 350 : 60, touchIdentifier: null, touchStartX: null, touchStartY: null, touchCurrentX: null, touchCurrentY: null, dragSupported: false, }; this.overlayClicked = this.overlayClicked.bind(this); this.onTouchStart = this.onTouchStart.bind(this); this.onTouchMove = this.onTouchMove.bind(this); this.onTouchEnd = this.onTouchEnd.bind(this); this.onScroll = this.onScroll.bind(this); this.saveSidebarRef = this.saveSidebarRef.bind(this); } componentDidMount() { this.setState({ dragSupported: typeof window === 'object' && 'ontouchstart' in window, }); } componentWillReceiveProps(nextProps){ if (nextProps.menuOpened !== this.props.menuOpened){ this.setState({...this.state, sidebarWidth: nextProps.menuOpened ? 350 : 60}); } } onTouchStart(ev) { if (!this.isTouching()) { const touch = ev.targetTouches[0]; this.setState({ touchIdentifier: touch.identifier, touchStartX: touch.clientX, touchStartY: touch.clientY, touchCurrentX: touch.clientX, touchCurrentY: touch.clientY, }); } } onTouchMove(ev) { if (this.isTouching()) { for (let ind = 0; ind < ev.targetTouches.length; ind++) { if (ev.targetTouches[ind].identifier === this.state.touchIdentifier) { this.setState({ touchCurrentX: ev.targetTouches[ind].clientX, touchCurrentY: ev.targetTouches[ind].clientY, }); break; } } } } onTouchEnd() { if (this.isTouching()) { const touchWidth = this.touchSidebarWidth(); if (this.props.open && touchWidth < this.state.sidebarWidth - this.props.dragToggleDistance || !this.props.open && touchWidth > this.props.dragToggleDistance) { this.props.onSetOpen(!this.props.open); } this.setState({ touchIdentifier: null, touchStartX: null, touchStartY: null, touchCurrentX: null, touchCurrentY: null, }); } } onScroll() { if (this.isTouching() && this.inCancelDistanceOnScroll()) { this.setState({ touchIdentifier: null, touchStartX: null, touchStartY: null, touchCurrentX: null, touchCurrentY: null, }); } } inCancelDistanceOnScroll() { let cancelDistanceOnScroll; if (this.props.pullRight) { cancelDistanceOnScroll = Math.abs(this.state.touchCurrentX - this.state.touchStartX) < CANCEL_DISTANCE_ON_SCROLL; } else { cancelDistanceOnScroll = Math.abs(this.state.touchStartX - this.state.touchCurrentX) < CANCEL_DISTANCE_ON_SCROLL; } return cancelDistanceOnScroll; } isTouching() { return this.state.touchIdentifier !== null; } overlayClicked() { if (this.props.open) { this.props.onSetOpen(false); } } saveSidebarWidth() { const width = this.sidebar.offsetWidth; if (width !== this.state.sidebarWidth) { this.setState({sidebarWidth: width}); } } saveSidebarRef(node) { this.sidebar = node; } touchSidebarWidth() { if (this.props.pullRight) { if (this.props.open && window.innerWidth - this.state.touchStartX < this.state.sidebarWidth) { if (this.state.touchCurrentX > this.state.touchStartX) { return this.state.sidebarWidth + this.state.touchStartX - this.state.touchCurrentX; } return this.state.sidebarWidth; } return Math.min(window.innerWidth - this.state.touchCurrentX, this.state.sidebarWidth); } if (this.props.open && this.state.touchStartX < this.state.sidebarWidth) { if (this.state.touchCurrentX > this.state.touchStartX) { return this.state.sidebarWidth; } return this.state.sidebarWidth - this.state.touchStartX + this.state.touchCurrentX; } return Math.min(this.state.touchCurrentX, this.state.sidebarWidth); } render() { const sidebarStyle = {...defaultStyles.sidebar, ...this.props.styles.sidebar}; const contentStyle = {...defaultStyles.content, ...this.props.styles.content}; const overlayStyle = {...defaultStyles.overlay, ...this.props.styles.overlay}; const useTouch = this.state.dragSupported && this.props.touch; const isTouching = this.isTouching(); const rootProps = { className: this.props.rootClassName, style: {...defaultStyles.root, ...this.props.styles.root}, role: "navigation", }; let dragHandle; if (this.props.pullRight) { sidebarStyle.right = 0; sidebarStyle.transform = 'translateX(100%)'; sidebarStyle.WebkitTransform = 'translateX(100%)'; if (this.props.shadow) { sidebarStyle.boxShadow = '-2px 2px 4px rgba(0, 0, 0, 0.15)'; } } else { sidebarStyle.left = 0; sidebarStyle.transform = 'translateX(-100%)'; sidebarStyle.WebkitTransform = 'translateX(-100%)'; if (this.props.shadow) { sidebarStyle.boxShadow = '2px 2px 4px rgba(0, 0, 0, 0.15)'; } } if (this.props.hasRightSidebar){ rootProps.style.right = '70px'; } if (isTouching) { const percentage = this.touchSidebarWidth() / this.state.sidebarWidth; if (this.props.pullRight) { sidebarStyle.transform = `translateX(${(1 - percentage) * 100}%)`; sidebarStyle.WebkitTransform = `translateX(${(1 - percentage) * 100}%)`; } else { sidebarStyle.transform = `translateX(-${(1 - percentage) * 100}%)`; sidebarStyle.WebkitTransform = `translateX(-${(1 - percentage) * 100}%)`; } overlayStyle.opacity = percentage; overlayStyle.visibility = 'visible'; } else if (this.props.docked) { if (this.state.sidebarWidth !== 0) { sidebarStyle.transform = `translateX(0%)`; sidebarStyle.WebkitTransform = `translateX(0%)`; } if (this.props.pullRight) { contentStyle.right = this.props.visible?`${this.state.sidebarWidth}px`:'0px'; } else { contentStyle.left = this.props.visible?`${this.state.sidebarWidth}px`:'0px'; } } else if (this.props.open) { sidebarStyle.transform = `translateX(0%)`; sidebarStyle.WebkitTransform = `translateX(0%)`; overlayStyle.opacity = 1; overlayStyle.visibility = 'visible'; } if (isTouching || !this.props.transitions) { sidebarStyle.transition = 'none'; sidebarStyle.WebkitTransition = 'none'; contentStyle.transition = 'none'; overlayStyle.transition = 'none'; } if (useTouch) { if (this.props.open) { rootProps.onTouchStart = this.onTouchStart; rootProps.onTouchMove = this.onTouchMove; rootProps.onTouchEnd = this.onTouchEnd; rootProps.onTouchCancel = this.onTouchEnd; rootProps.onScroll = this.onScroll; } else { const dragHandleStyle = {...defaultStyles.dragHandle, ...this.props.styles.dragHandle}; dragHandleStyle.width = this.props.touchHandleWidth; if (this.props.pullRight) { dragHandleStyle.right = 0; } else { dragHandleStyle.left = 0; } dragHandle = ( <div style={dragHandleStyle} onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove} onTouchEnd={this.onTouchEnd} onTouchCancel={this.onTouchEnd} />); } } return ( <div {...rootProps}> <div className={this.props.sidebarClassName} style={sidebarStyle} ref={this.saveSidebarRef}> {this.props.sidebar} </div> <div className={this.props.overlayClassName} style={overlayStyle} role="presentation" tabIndex="0" onClick={this.overlayClicked} /> <div className={this.props.contentClassName} style={contentStyle}> {dragHandle} {this.props.children} </div> </div> ); } } AnterosSidebarLayout.propTypes = { children: PropTypes.node.isRequired, styles: PropTypes.shape({ root: PropTypes.object, sidebar: PropTypes.object, content: PropTypes.object, overlay: PropTypes.object, dragHandle: PropTypes.object, }), rootClassName: PropTypes.string, sidebarClassName: PropTypes.string, contentClassName: PropTypes.string, overlayClassName: PropTypes.string, sidebar: PropTypes.node.isRequired, docked: PropTypes.bool, open: PropTypes.bool, transitions: PropTypes.bool, touch: PropTypes.bool, touchHandleWidth: PropTypes.number, pullRight: PropTypes.bool, shadow: PropTypes.bool, hasRightSidebar: PropTypes.bool, dragToggleDistance: PropTypes.number, onSetOpen: PropTypes.func, defaultSidebarWidth: PropTypes.number, }; AnterosSidebarLayout.defaultProps = { docked: false, open: false, transitions: true, touch: true, touchHandleWidth: 20, hasRightSidebar: false, pullRight: false, shadow: false, dragToggleDistance: 30, onSetOpen: () => {}, styles: {}, defaultSidebarWidth: 0, };