UNPKG

framework-entersol-web

Version:

Framework based on bootstrap 5

212 lines (190 loc) 5.96 kB
import React, { createRef } from "react"; import { Dropdown } from "bootstrap"; import { NavLink } from "react-router-dom"; import eventHandler from "../functions/event-handler"; import Component from "../component"; import Icons from "../media/icons"; export class DropdownItem extends Component { static jsClass = 'DropdownItem'; static defaultProps = { ...Component.defaultProps, badgeClasses: 'rounded-pill bg-danger' } classes = 'dropdown-item d-flex align-items-center'; constructor(props) { super(props); if (typeof props.to === 'string') this.tag = NavLink; else if (typeof props.href === 'string') this.tag = 'a'; else this.tag = 'button'; this.eventHandlers = { onClick: this.onClick } } onClick = (e) => { eventHandler.dispatch(this.props.name, { [this.props.name]: { value: this.props.value } }); } get componentProps() { if (this.tag === NavLink || this.tag === 'a') { const { activeClassName, activeStyle, exact, strict, isActive, ariaCurrent, to, replace, innerRef, _component, href, target } = this.props; return { activeClassName, activeStyle, exact, strict, isActive, 'aria-current': ariaCurrent, to, replace, innerRef, component: _component, href, target } } else return { ...super.componentProps, type: 'button' }; } content(children = this.props.children) { const { label, icon, value, badgeClasses } = this.props; return <> {icon && <><Icons icon={icon} />&nbsp;</>} {label} {typeof value === 'number' && <>&nbsp;<small className={['badge ms-auto', badgeClasses].join(' ')}>{value}</small></>} {children} </> } } export default class DropdownButtonContainer extends Component { static jsClass = 'DropdownButtonContainer'; static defaultProps = { ...Component.defaultProps, itemClasses: '', dropdownClasses: '', btnClasses: '', dropdown: {} } classes = 'dropdown'; constructor(props) { super(props); this.btn = createRef(); this.trigger = props.name + 'Btn'; this.style.width = 'fit-content'; this.onBsEvents = this.onBsEvents.bind(this); this.events = [ ['update.' + props.name, this.onUpdate.bind(this)] ]; Object.assign(this.state, { open: false }); } componentDidMount() { this.events.forEach(e => eventHandler.subscribe(...e)); if (this.btn.current) { const btn = this.btn.current; const last = Dropdown.getInstance(btn); if (last) last.dispose(); this.bsDropdown = new Dropdown(btn, { autoClose: true, reference: 'toggle', ...this.props.dropdown, }); btn.removeEventListener('hide.bs.dropdown', this.onBsEvents); btn.removeEventListener('hidden.bs.dropdown', this.onBsEvents); btn.removeEventListener('show.bs.dropdown', this.onBsEvents); btn.removeEventListener('shown.bs.dropdown', this.onBsEvents); btn.addEventListener('hide.bs.dropdown', this.onBsEvents); btn.addEventListener('hidden.bs.dropdown', this.onBsEvents); btn.addEventListener('show.bs.dropdown', this.onBsEvents); btn.addEventListener('shown.bs.dropdown', this.onBsEvents); } } componentWillUnmount() { this.events.forEach(e => eventHandler.unsubscribe(e[0])); if (this.bsDropdown) { this.bsDropdown.dispose(); this.bsDropdown = false; } if (this.btn.current) { const btn = this.btn.current; btn.removeEventListener('hide.bs.dropdown', this.onBsEvents); btn.removeEventListener('hidden.bs.dropdown', this.onBsEvents); btn.removeEventListener('show.bs.dropdown', this.onBsEvents); btn.removeEventListener('shown.bs.dropdown', this.onBsEvents); } } onUpdate({ open }) { if (open !== undefined) this.bsDropdown[open ? 'show' : 'hide'](); } onBsEvents(evt) { const evtType = evt.type.split('.')[0]; eventHandler.dispatch(this.props.name, { [this.props.name]: { open: evt.type.includes('shown'), event: evtType, menu: this.props.menu } }); if (evtType === 'hidden') { this.setState({ open: false }); } } onToggleDrop(evt) { evt.stopPropagation(); if (this.state.open) { this.bsDropdown.hide(); } else { this.setState({ open: true }, () => { this.bsDropdown.show(); }); } } dropdownRender(children) { const { menu, allowClose, itemClasses, dropdownClasses } = this.props; const cn = ['dropdown-menu', dropdownClasses].join(' '); return <div className={cn} style={{ minWidth: '100%' }} onClick={allowClose ? null : (e) => e.stopPropagation()} aria-labelledby={this.trigger}> {this.state.open && menu?.map((item, i) => { if (item === 'divider') return <div className="dropdown-divider" key={item.name || i} /> item.classes = item.classes || itemClasses; return (React.isValidElement(item) ? <React.Fragment key={item.name || i}>{item}</React.Fragment> : <DropdownItem {...item} key={item.name || i} />) }) } {this.state.open && children} </div> } content(children = this.props.children) { const { btnClasses, label, value, disabled } = this.props; const cn = ['btn', btnClasses]; if (this.props.dropdownClass !== false) cn.push('dropdown-toggle'); return <> <button className={cn.join(' ')} data-bs-toggle="dropdown" disabled={disabled} id={this.trigger} onClick={this.onToggleDrop.bind(this)} ref={this.btn} type="button" > {label || value} </button> {this.dropdownRender(children)} </> } }