UNPKG

wix-style-react

Version:
322 lines (270 loc) • 8.36 kB
import React, { Children } from 'react'; import PropTypes from 'prop-types'; import SideMenu from '../core/SideMenu'; import SlideAnimation, { SlideDirection, } from '../../Animations/SlideAnimation'; import styles from './DrillView.scss'; const isAnchorTag = function(item) { return item.type === 'a'; }; class SideMenuDrill extends React.Component { constructor(props) { super(props); const state = { menus: {}, currentMenuId: this.props.menuKey, previousMenuId: null, showMenuA: true, slideDirection: SlideDirection.in, }; this.processChildren({ props: this.props }, state); this.state = state; this.isAnimating = false; } componentWillReceiveProps(nextProps) { const state = { menus: {}, }; this.processChildren({ props: nextProps }, state); this.setState(state); } setSelectedItemMenu(selectedItemMenuId, state) { // initial selected menu if (!this.state) { Object.assign(state, { currentMenuId: selectedItemMenuId, selectedItemMenuId, }); return; } // returning to an already selected menu item (force nav) if (this.lastClickedMenuKey === selectedItemMenuId) { this.navigateToMenu(selectedItemMenuId, SlideDirection.in); this.lastClickedMenuKey = null; } if (this.state.selectedItemMenuId === selectedItemMenuId) { return; } this.setState({ selectedItemMenuId }); if (this.state.currentMenuId !== selectedItemMenuId) { this.navigateToMenu( selectedItemMenuId, this.getSlideDirectionTo(selectedItemMenuId), ); } } getSlideDirectionTo(selectedItemMenuId) { const { currentMenuId, menus } = this.state; if (!menus[currentMenuId] || !menus[selectedItemMenuId]) { return SlideDirection.in; } return menus[currentMenuId].level < menus[selectedItemMenuId].level ? SlideDirection.in : SlideDirection.out; } navigateToMenu(nextMenuId, slideDirection) { const previousMenuId = this.state.currentMenuId; const showMenuA = !this.state.showMenuA; if (nextMenuId === previousMenuId) { return; } this.setState({ currentMenuId: nextMenuId, previousMenuId, showMenuA, slideDirection, }); } clickFirstClickableChild(item, event) { let found = false; if (item.props.onClick && !item.props.disabled) { item.props.onClick(event); return true; } else if (isAnchorTag(item)) { return false; } Children.forEach(item.props.children, child => { if (!found && child.props) { found = this.clickFirstClickableChild(child, event); } }); return found; } selectFirstLinkChild(menu, event) { let found = false; Children.forEach(menu.props.children, child => { if (!found && child.type === SideMenuDrill.Link) { this.clickFirstClickableChild(child, event); found = true; } if (!found && child.props && child.props.children) { this.selectFirstLinkChild(child, event); } }); } alterMenu(menu, childrenClone, parentMenuKey, isActive) { const defaultSubMenProps = { isOpen: false, onSelectHandler: event => { this.lastClickedMenuKey = menu.props.menuKey; this.selectFirstLinkChild(menu, event); if (menu.props.onSelectHandler) { menu.props.onSelectHandler.apply(menu, [event]); } }, onBackHandler: event => { this.navigateToMenu(parentMenuKey, SlideDirection.out); if (menu.props.onBackHandler) { menu.props.onBackHandler.apply(menu, [event]); } }, isActive, }; return React.cloneElement(menu, defaultSubMenProps, childrenClone); } cloneSubMenu(menu, state, parentMenuKey, childrenClone) { const isMenuActive = state.menus[menu.props.menuKey].isActive; if (isMenuActive && state.menus[parentMenuKey]) { state.menus[parentMenuKey].isActive = true; } const menuClone = this.alterMenu( menu, childrenClone, parentMenuKey, isMenuActive, ); state.menus[menuClone.props.menuKey].component = menuClone; return menuClone; } cloneChild(menu, state, parentMenuKey, childrenClone) { if (menu.type === SideMenuDrill.Link && menu.props.isActive) { this.setSelectedItemMenu(parentMenuKey, state); state.menus[parentMenuKey].isActive = true; } if (menu.props.menuKey) { return this.cloneSubMenu(menu, state, parentMenuKey, childrenClone); } return React.cloneElement(menu, {}, childrenClone); } processChildren(menu, state, parentMenuKey, level = 0) { const childrenClone = Children.map(menu.props.children, child => { if (child && child.props && child.props.children) { const menuKey = menu.props.menuKey || parentMenuKey; if (!state.menus[menuKey]) { state.menus[menuKey] = { isActive: false, component: null, level }; } return this.processChildren(child, state, menuKey, level + 1); } return child; }); return this.cloneChild(menu, state, parentMenuKey, childrenClone, level); } renderNavigation(menu) { if (!menu) { return null; } if (menu.props.menuKey === this.props.menuKey) { // Render root items return menu.props.children; } // Render open SubMenu return React.cloneElement(menu, { isOpen: true }); } renderMenu(menu) { const navigationMenu = this.renderNavigation(menu); return ( navigationMenu && ( <div data-hook="drill-view-panel" className={styles.drillViewPanel}> {navigationMenu} </div> ) ); } shouldComponentUpdate() { if (this.isAnimating) { this.queuedUpdate = true; } return !this.isAnimating; } animationStart() { this.isAnimating = true; } animationComplete() { this.isAnimating = false; if (this.queuedUpdate) { this.queuedUpdate = false; this.forceUpdate(); } } getAnimationHandlers() { const enterHandlers = {}; const exitHandlers = {}; const enterPromise = new Promise( resolve => (enterHandlers.onEnter = resolve), ); const enteredPromise = new Promise( resolve => (enterHandlers.onEntered = resolve), ); const exitPromise = new Promise(resolve => (exitHandlers.onExit = resolve)); const exitedPromise = new Promise( resolve => (exitHandlers.onExited = resolve), ); Promise.race([enterPromise, exitPromise]).then(() => this.animationStart()); Promise.all([enteredPromise, exitedPromise]).then(() => this.animationComplete(), ); return { enterHandlers, exitHandlers }; } render() { const { menus, currentMenuId, previousMenuId, showMenuA, slideDirection, } = this.state; const menuAId = showMenuA ? currentMenuId : previousMenuId; const menuBId = showMenuA ? previousMenuId : currentMenuId; const menuA = menuAId && menus[menuAId].component; const menuB = menuBId && menus[menuBId].component; const { enterHandlers, exitHandlers } = this.getAnimationHandlers(); const menuAHandlers = showMenuA ? enterHandlers : exitHandlers; const menuBHandlers = showMenuA ? exitHandlers : enterHandlers; return ( <SideMenu dataHook={this.props.dataHook} inFlex={this.props.inFlex}> <div className={styles.drillViewContainer}> <SlideAnimation direction={slideDirection} animateAppear={false} isVisible={showMenuA} {...menuAHandlers} > {this.renderMenu(menuA)} </SlideAnimation> <SlideAnimation direction={slideDirection} animateAppear={false} isVisible={!showMenuA} {...menuBHandlers} > {this.renderMenu(menuB)} </SlideAnimation> </div> {this.props.stickyFooter} </SideMenu> ); } } SideMenuDrill.defaultProps = { inFlex: false, menuKey: 'root', }; SideMenuDrill.propTypes = { dataHook: PropTypes.string, inFlex: PropTypes.bool, menuKey: PropTypes.string, children: PropTypes.node, stickyFooter: PropTypes.node, }; export default SideMenuDrill;