import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { withTranslation, Trans } from '@/components/Common/Translate'; import Link from 'next/link'; import Router, { withRouter } from 'next/router'; import { Collapse, Badge } from 'reactstrap'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as actions from '../../store/actions/actions'; import SidebarUserBlock from './SidebarUserBlock'; import Menu from './Menu.js'; // Helper to check for parrent of an given elements const parents = (element, selector) => { if (typeof selector !== 'string') { return null; } const parents = []; let ancestor = element.parentNode; while ( ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== 3 /*NODE_TEXT*/ ) { if (ancestor.matches(selector)) { parents.push(ancestor); } ancestor = ancestor.parentNode; } return parents; }; // Helper to get outerHeight of a dom element const outerHeight = (elem, includeMargin) => { const style = getComputedStyle(elem); const margins = includeMargin ? parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10) : 0; return elem.offsetHeight + margins; }; /** Component to display headings on sidebar */ const SidebarItemHeader = ({ item }) => (
  • {item.heading}
  • ); /** Normal items for the sidebar */ const SidebarItem = ({ item, isActive, className, onMouseEnter }) => (
  • {item.label && ( {item.label.value} )} {item.icon && } {item.name}
  • ); /** Build a sub menu with items inside and attach collapse behavior */ const SidebarSubItem = ({ item, isActive, handler, children, isOpen, onMouseEnter }) => (
  • {item.label && ( {item.label.value} )} {item.icon && } {item.name}
      {children}
  • ); /** Component used to display a header on menu when using collapsed/hover mode */ const SidebarSubHeader = ({ item }) =>
  • {item.name}
  • ; const SidebarBackdrop = ({ closeFloatingNav }) => (
    ); const FloatingNav = ({ item, target, routeActive, isFixed, closeFloatingNav }) => { let asideContainer = document.querySelector('.aside-container'); let asideInner = asideContainer.firstElementChild; /*('.aside-inner')*/ let sidebar = asideInner.firstElementChild; /*('.sidebar')*/ let mar = parseInt(getComputedStyle(asideInner)['padding-top'], 0) + parseInt(getComputedStyle(asideContainer)['padding-top'], 0); let itemTop = target.parentElement.offsetTop + mar - sidebar.scrollTop; let vwHeight = document.body.clientHeight; const setPositionStyle = el => { if (!el) return; el.style.position = isFixed ? 'fixed' : 'absolute'; el.style.top = itemTop + 'px'; el.style.bottom = outerHeight(el, true) + itemTop > vwHeight ? 0 : 'auto'; }; return (
      {item.submenu.map((subitem, i) => ( ))}
    ); }; /** The main sidebar component */ class Sidebar extends Component { state = { collapse: {}, showSidebarBackdrop: false, currentFloatingItem: null, currentFloatingItemTarget: null, pathname: this.props.router.pathname }; componentDidMount() { // prepare the flags to handle menu collapsed states this.buildCollapseList(); // Listen for routes changes in order to hide the sidebar on mobile Router.events.on('routeChangeStart', this.handleRouteChange); Router.events.on('routeChangeComplete', this.handleRouteComplete); // Attach event listener to automatically close sidebar when click outside document.addEventListener('click', this.closeSidebarOnExternalClicks); } handleRouteComplete = (pathname) => { this.setState({ pathname }) } handleRouteChange = () => { this.closeFloatingNav(); this.closeSidebar(); }; componentWillUnmount() { document.removeEventListener('click', this.closeSidebarOnExternalClicks); Router.events.off('routeChangeStart', this.handleRouteChange); Router.events.off('routeChangeComplete', this.handleRouteComplete); } closeSidebar = () => { this.props.actions.toggleSetting('asideToggled'); }; closeSidebarOnExternalClicks = e => { // don't check if sidebar not visible if (!this.props.settings.asideToggled) return; if ( !parents(e.target, '.aside-container').length && // if not child of sidebar !parents(e.target, '.topnavbar-wrapper').length && // if not child of header !e.target.matches('#user-block-toggle') && // user block toggle anchor !e.target.parentElement.matches('#user-block-toggle') // user block toggle icon ) { this.closeSidebar(); } }; /** prepare initial state of collapse menus.*/ buildCollapseList = () => { let collapse = {}; Menu.filter(({ heading }) => !heading).forEach(({ name, path, submenu }) => { collapse[name] = this.routeActive(submenu ? submenu.map(({ path }) => path) : path); }); this.setState({ collapse }); }; routeActive = paths => { const currpath = this.state.pathname; paths = Array.isArray(paths) ? paths : [paths]; return paths.some(p => (p === '/' ? currpath === p : currpath.indexOf(p) > -1)); }; toggleItemCollapse = stateName => () => { for (let c in this.state.collapse) { if (this.state.collapse[c] === true && c !== stateName) this.setState({ collapse: { [c]: false } }); } this.setState({ collapse: { [stateName]: !this.state.collapse[stateName] } }); }; getSubRoutes = item => item.submenu.map(({ path }) => path); /** map menu config to string to determine which element to render */ itemType = item => { if (item.heading) return 'heading'; if (!item.submenu) return 'menu'; if (item.submenu) return 'submenu'; }; shouldUseFloatingNav = () => { return ( this.props.settings.isCollapsed || this.props.settings.isCollapsedText || this.props.settings.asideHover ); }; showFloatingNav = item => e => { if (this.shouldUseFloatingNav()) this.setState({ currentFloatingItem: item, currentFloatingItemTarget: e.currentTarget, showSidebarBackdrop: true }); }; closeFloatingNav = () => { this.setState({ currentFloatingItem: null, currentFloatingItemTarget: null, showSidebarBackdrop: false }); }; render() { return ( <> {this.state.showSidebarBackdrop && ( )} ); } } Sidebar.propTypes = { actions: PropTypes.object, settings: PropTypes.object }; const mapStateToProps = state => ({ settings: state.settings }); const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(actions, dispatch) }); export default connect( mapStateToProps, mapDispatchToProps )(withRouter(withTranslation(Sidebar)));