dbl-components
Version:
Framework based on bootstrap 5
277 lines (251 loc) • 8.29 kB
JavaScript
import React, { createRef } from "react";
import PropTypes from "prop-types";
import { Dropdown } from "bootstrap";
import { NavLink } from "react-router-dom";
import { eventHandler } from "dbl-utils";
import Icons from "../media/icons";
import JsonRender from "../json-render";
import Component from "../component";
export class DropdownItem extends Component {
static jsClass = 'DropdownItem';
static propTypes = {
...NavLink.propTypes,
...Component.propTypes,
_component: PropTypes.elementType,
badge: PropTypes.node,
badgeClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
icon: PropTypes.string,
label: PropTypes.node,
value: PropTypes.node,
}
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, badge, badgeClasses } = this.props;
return React.createElement(React.Fragment, {},
icon && React.createElement(React.Fragment, {}, React.createElement(Icons, { icon }), ' '),
label,
badge && React.createElement(React.Fragment, {},
' ',
React.createElement('small', { className: ['badge ms-auto', badgeClasses].flat().join(' ') },
badge
)
),
children
)
}
}
export default class DropdownButtonContainer extends Component {
static jsClass = 'DropdownButtonContainer';
static propTypes = {
...Component.propTypes,
allowClose: PropTypes.bool,
btnClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
disabled: PropTypes.bool,
dropdown: PropTypes.any,
dropdownClass: PropTypes.bool,
dropdownClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
itemClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
label: PropTypes.node,
menu: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.node,
PropTypes.shape({
...DropdownItem.propTypes
}), PropTypes.string
])),
mutations: PropTypes.func,
value: PropTypes.node,
zIndex: PropTypes.number
};
static defaultProps = {
...Component.defaultProps,
itemClasses: '',
dropdownClasses: '',
dropdownClass: true,
btnClasses: '',
dropdown: {},
zIndex: 1000
};
/**
* @override classes
*/
classes = 'btn-group';
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
});
const { mutations, ...propsJ } = props;
this.jsonRender = new JsonRender(propsJ, mutations);
}
componentDidMount() {
this.events.forEach(e => eventHandler.subscribe(...e));
}
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);
}
}
refBtn(ref) {
if (ref) {
this.btn.current = ref;
const btn = ref;
this.bsDropdown = Dropdown.getOrCreateInstance(btn, {
autoClose: true,
reference: 'parent',
display: 'dynamic',
...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);
}
}
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();
setTimeout(() =>
this.bsDropdown.update()
, 400)
});
}
}
dropdownRender(children) {
const { menu, allowClose, itemClasses, dropdownClasses } = this.props;
const cn = ['dropdown-menu', dropdownClasses].flat().join(' ');
const menuBuilded = this.state.open && Array.isArray(menu)
? Object.entries(menu).map(([key, item]) => {
if (React.isValidElement(item)) return item;
item.name = item.name || `${this.props.name}.${key}`;
if (item === 'divider') item = {
name: 'divider.' + key,
tag: 'div',
divider: true,
classes: "dropdown-divider"
};
return this.jsonRender.buildContent({
classes: itemClasses,//si item trae classes que se sobreescriban
tag: 'div',
...item,
badge: this.jsonRender.buildContent(item.badge),
component: 'DropdownItem',
});
}) : [];
return React.createElement('div',
{
className: cn, style: { minWidth: '100%', "--bs-dropdown-zindex": this.props.zIndex },
onClick: allowClose ? null : (e) => e.stopPropagation(), 'aria-labelledby': this.trigger
},
...[this.state.open && menuBuilded].flat(),
this.state.open && children
);
}
content(children = this.props.children) {
const { btnClasses, label, value, disabled, dropdownClass } = this.props;
const cn = ['btn', btnClasses];
if (dropdownClass !== false) cn.push('dropdown-toggle');
return React.createElement(React.Fragment, {},
React.createElement('button',
{
className: cn.flat().join(' '),
'data-bs-toggle': "dropdown",
disabled,
id: this.trigger,
onClick: this.onToggleDrop.bind(this),
ref: (ref) => this.refBtn(ref),
type: "button"
},
label || value,
),
this.dropdownRender(children)
);
}
}