UNPKG

framework-entersol-web

Version:

Framework based on bootstrap 5

265 lines (236 loc) 8 kB
import React, { createRef } from "react"; import { NavLink } from "react-router-dom"; import Collapse from "bootstrap/js/dist/collapse"; import parseReact from "html-react-parser"; import eventHandler from "../functions/event-handler"; import JsonRender from "../json-render"; import Component from "../component"; import Icons from "../media/icons"; import Action from "../actions/action"; export class ToggleTextNavigation extends Action { static jsClass = 'ToggleTextNavigation'; content() { return <Icons icon={this.props.icon} /> } } export default class Navigation extends Component { static jsClass = 'Navigation'; static defaultProps = { ...Component.defaultProps, menu: [], caretIcons: ['angle-up', 'angle-down'], navLink: true, activeClassName: 'active' } tag = 'nav'; constructor(props) { super(props); Object.assign(this.state, { carets: {}, open: typeof this.props.open !== 'boolean' || this.props.open, localClasses: 'nav label-show' }) this.collapses = createRef({}); this.jsonRender = new JsonRender(props); this.hide = this.hide.bind(this); } componentDidMount() { if (this.props.toggle) eventHandler.subscribe(this.props.toggle, this.onToggleBtn.bind(this)); this.unlisten = this.props.history.listen(this.onChangeRoute.bind(this)); this.findFirstActive(this.props.menu); } componentDidUpdate(prevProps, prevState) { if (typeof this.props.open === 'boolean' && prevProps.open !== this.props.open) { this.toggleText(this.props.open); } } componentWillUnmount() { if (this.props.toggle) eventHandler.unsubscribe(this.props.toggle); this.unlisten(); if (this.collapses.current) Object.entries(this.collapses.current).forEach(([key, itemControl]) => { itemControl.submenuOpen = false; itemControl.collapse?.dispose(); itemControl.ref.removeEventListener('hidden.bs.collapse', this.hide); delete this.collapses.current[key]; }); this.collapses.current = {}; } findFirstActive(menu, parent) { let founded; menu.find(item => { item.parent = parent; if (this.props.location.pathname === item.path) { this.onNavigate(null, item); this.onChangeRoute(this.props.location); founded = item; return true; } else if (item.menu) { founded = this.findFirstActive(item.menu, item); return !!founded; } return false; }); return founded; } onChangeRoute(location, action) { this.pathname = location.pathname; eventHandler.dispatch(this.props.name, { pathname: this.pathname, item: this.activeItem }); } onToggleBtn() { this.toggleText(); } toggleText(open = !this.state.open) { this.setState({ open, localClasses: open ? 'nav label-show' : 'nav label-collapsed' }, () => eventHandler.dispatch(this.props.name, { pathname: this.pathname, item: this.activeItem, open: this.state.open })); } collapseRef(ref, item) { if (!ref) return; if (!this.collapses.current) this.collapses.current = {}; if (this.collapses.current[item.name]) return; this.collapses.current[item.name] = { ref, item, submenuOpen: false } } onToggleSubmenu(e, item) { e.stopPropagation(); e.nativeEvent.stopPropagation(); e.nativeEvent.preventDefault(); const itemControl = this.collapses.current[item.name]; if (!itemControl.collapse) { itemControl.ref.removeEventListener('hidden.bs.collapse', this.hide); itemControl.collapse = Collapse.getOrCreateInstance(itemControl.ref, { autoClose: false }); itemControl.ref.addEventListener('hidden.bs.collapse', this.hide); } if (!itemControl.submenuOpen) { itemControl.submenuOpen = true; this.state.carets[item.name] = this.props.caretIcons[0]; this.setState({ carets: this.state.carets }, () => itemControl.collapse.show()); } else { //Se oculta todo en el evento de ocultar itemControl.collapse.hide(); } } hide(e) { const itemName = e.target.id.split('-collapse')[0]; const itemControl = this.collapses.current[itemName]; itemControl.submenuOpen = false; this.state.carets[itemName] = this.props.caretIcons[1]; this.setState({ carets: this.state.carets }); } hasAnActive(menuItem) { if (!menuItem.parent) return menuItem.name; menuItem.parent.hasAnActive = true; return this.hasAnActive(menuItem.parent); } onNavigate(e, activeItem) { this.activeItem = activeItem; this.flatItems.forEach(i => { i.active = false; i.hasAnActive = false; }); activeItem.active = true; this.hasAnActive(activeItem); } link = (item, i, parent) => { const { caretIcons, linkClasses, navLink, activeClassName } = this.props; const { carets, open } = this.state; carets[item.name] = carets[item.name] || caretIcons[1]; this.flatItems.push(item); item.parent = parent; const iconStyle = { style: { fill: 'currentColor' } }; const innerNode = <span> {item.content ? (open ? this.jsonRender.buildContent(item.content[0]) : this.jsonRender.buildContent(item.content[1]) ) : <> <Icons icon={item.icon} className="mx-2" {...iconStyle} {...(item.iconProps || {})} /> {open && <span className="label">{this.jsonRender.buildContent(item.label)}</span>} </> } </span> const className = (() => { const r = [linkClasses, item.classes]; if (!item.path) r.push('cursor-pointer'); if (item.hasAnActive) r.push('has-an-active'); if (navLink) r.unshift('nav-link'); if (!!item.menu?.length) r.push('has-submenu'); return r; })().join(' '); const propsLink = item.path ? { id: item.name + '-link', className, onClick: (e) => [!!item.menu?.length && this.onToggleSubmenu(e, item), this.onNavigate(e, item)], to: item.path, activeClassName, strict: item.strict, exact: item.exact, style: {} } : { id: item.name + '-link', className, onClick: !!item.menu?.length ? (e) => this.onToggleSubmenu(e, item) : null, style: {} } const styleWrapCaret = { } if (!!item.menu?.length && open) { styleWrapCaret.position = "relative"; propsLink.style.paddingRight = "2.3rem"; } return (<React.Fragment key={i + '-' + item.path}> <div {...(item.itemProps || {})} > <div style={styleWrapCaret}> { item.path ? <NavLink {...propsLink}> {innerNode} </NavLink> : <span {...propsLink} > {innerNode} </span > } { !!item.menu?.length && open && <span className="position-absolute top-50 end-0 translate-middle-y caret-icon p-1 cursor-pointer" onClick={e => this.onToggleSubmenu(e, item)}> <Icons icon={carets[item.name]} {...iconStyle} inline={false} style={{ width: "1.8rem", padding: '.5rem' }} className="rounded-circle" /> </span> } </div> { !!item.menu?.length && <div ref={(ref) => this.collapseRef(ref, item)} id={item.name + '-collapse'} className="collapse"> { //renderear solo cuando este abierto this.state.carets[item.name] === this.props.caretIcons[0] && item.menu.map((m, i) => this.link(m, i, item)) } </div> } </div> </React.Fragment>); } // TODO: agregar submenu dropdown // y submenu collapsable content(children = this.props.children) { this.flatItems = []; return (<> {this.props.menu.map((m, i) => this.link(m, i))} {children} </>); } }