UNPKG

dbl-components

Version:

Framework based on bootstrap 5

323 lines (293 loc) 9.51 kB
import React from "react"; import PropTypes from "prop-types"; import Offcanvas from "bootstrap/js/dist/offcanvas"; import { eventHandler, resolveRefs } from "dbl-utils"; import JsonRender from "../../json-render"; import Component from "../../component"; import { ptClasses } from "../../prop-types"; import schema from "./offcanvas.json"; /** * OffcanvasContainer component to manage and display an offcanvas UI element. * @extends Component */ export default class OffcanvasContainer extends Component { // Static property to define the class name static jsClass = "OffcanvasContainer"; static propTypes = { ...Component.propTypes, bodyClasses: ptClasses, closeClasses: ptClasses, footerClasses: ptClasses, headerClasses: ptClasses, labelClasses: ptClasses, label: PropTypes.node, labelTag: PropTypes.string, offcanvas: PropTypes.object, position: PropTypes.oneOf(["start", "end", "top", "bottom"]), showClose: PropTypes.bool, }; // Default properties for the OffcanvasContainer component static defaultProps = { ...Component.defaultProps, bodyClasses: "", closeClasses: "", footerClasses: "", headerClasses: "", labelClasses: "", labelTag: "h5", offcanvas: {}, position: "start", // Possible values: start, end, bottom, top showClose: true, }; /** * HTML tagName, This variable is used by the parent Class. * @type {string} * @SuppressWarnings unused * @override */ // NOSONAR tag = "aside"; /** * CSS classes for the offcanvas container, used by the parent Class. * @type {string} * @SuppressWarnings unused * @override */ // NOSONAR classes = "offcanvas d-flex flex-column"; // Children elements categorized by type children = { header: [], body: [], footer: [], content: [], }; /** * Constructor to initialize the component with given properties. * @param {object} props - The properties passed to the component. */ constructor(props) { super(props); this.onOffcanvasRef = this.onOffcanvasRef.bind(this); // Bootstrap events to manage offcanvas lifecycle this.bsEvents = ["show", "shown", "hide", "hidden", "hidePrevented"]; // Initial state Object.assign(this.state, { showOffcanvas: this.props.open, localClasses: "offcanvas-" + props.position, }); // Resolve schema references and initialize JsonRender this.schema = resolveRefs(schema.view, { definitions: schema.definitions, props, }); this.jsonRender = new JsonRender(props, this.mutations.bind(this)); } /** * Override componentProps to include necessary attributes and refs. Used by the parent Class. * @override * @SuppressWarnings unused */ get componentProps() { const props = this.props._props || {}; return { tabIndex: -1, id: this.name, "aria-labelledby": this.props.name + "-titleOffcanvas", ref: this.onOffcanvasRef, ...props, }; } // Lifecycle method: componentDidMount componentDidMount() { const { name } = this.props; eventHandler.subscribe("update." + name, this.onUpdateOffcanvas, this.name); this.deleteClasses( "offcanvas-start offcanvas-end offcanvas-top offcanvas-bottom" ); this.addClasses("offcanvas-" + this.props.position); } // Lifecycle method: componentWillUnmount componentWillUnmount() { const { name } = this.props; this.destroy(); eventHandler.unsubscribe("update." + name, this.name); } /** * Event handler for offcanvas events. * @param {Event} e - The event object. */ onEvent = (e) => { const { name } = this.props; eventHandler.dispatch(name, { [name]: e.type.split(".")[0] }); }; /** * Event handler for updating the offcanvas visibility. * @param {object} param - The update parameters. * @param {boolean} param.open - Whether to show or hide the offcanvas. */ onUpdateOffcanvas = ({ open: showOffcanvas }) => { if (showOffcanvas !== undefined) { if (showOffcanvas) return this.setState({ showOffcanvas }); if (!this.offcanvas) { this.offcanvas = Offcanvas.getInstance(this.ref.current); } this.offcanvas?.hide(); setTimeout(() => this.setState({ showOffcanvas }), 350); return; } }; /** * Destroy the offcanvas instance. */ destroy = () => { if (this.offcanvas) { this.offcanvas.dispose(); this.offcanvas = null; } this.setState({ showOffcanvas: false }); }; /** * Callback to initialize the offcanvas reference. * @param {HTMLElement} refOriginal - The original reference element. */ onOffcanvasRef = (refOriginal) => { if (refOriginal) { this.ref.current = refOriginal; const ref = refOriginal; this.offcanvas = Offcanvas.getOrCreateInstance(ref, this.props.offcanvas); this.bsEvents.forEach((event) => { ref.addEventListener(event + ".bs.offcanvas", this.onEvent, false); }); ref.addEventListener("hidden.bs.offcanvas", this.destroy, false); if (this.state.showOffcanvas) this.offcanvas.show(); } }; /** * Get the header content. * @returns {Array} - The header content. */ get headerContent() { return !!this.children.header.length && this.children.header; } /** * Get the body content. * @returns {Array} - The body content. */ get bodyContent() { return !!this.children.body.length && this.children.body; } /** * Get the footer content. * @returns {Array} - The footer content. */ get footerContent() { return !!this.children.footer.length && this.children.footer; } /** * Get the content for the offcanvas. * @returns {Array} - The offcanvas content. */ get contentOffcanvas() { return !!this.children.content.length && this.children.content; } /** * Method to categorize and render children elements. Used by the parent Class * @override * @param {Array|object} children - The children elements to categorize and render. * @SuppressWarnings unused * @returns {JSX.Element} - The rendered content. */ content(children = this.props.children) { this.children = (Array.isArray(children) ? children : [children]).reduce( (reducer, child) => { if (!child) return reducer; // Categorize content based on type: header, body, footer, or general content if (["string", "number", "boolean"].includes(typeof child)) { reducer.body.push(child); return reducer; } const childCondition = !child.props?.style?.["--component-name"]; const childConf = (childCondition ? child : child.props.children).props; const container = (childConf && reducer[childConf.container]) || reducer.content; container.push(child); return reducer; }, { header: [], body: [], footer: [], content: [] } ); return this.jsonRender.buildContent(this.schema); } /** * Render the offcanvas container. * @returns {JSX.Element} - The rendered offcanvas container. */ render() { return this.state.showOffcanvas ? super.render() : React.createElement(React.Fragment); } /** * Function to apply specific mutations based on the name and configuration. * @param {string} name - The name to determine which mutation to apply. * @param {object} conf - The configuration object for the mutation. * @returns {(boolean|object)} - Returns an object with mutation properties or false if no mutation is applied. */ mutations(name, conf) { const rn = name.replace(this.props.name + "-", ""); switch (rn) { case "headerOffcanvas": { return { active: this.props.showClose || !!this.props.label || !!this.headerContent, classes: [conf.classes, this.props.headerClasses], }; } case "titleOffcanvas": { return { active: !!this.props.label, tag: this.props.labelTag, classes: [conf.classes, this.props.labelClasses], content: this.props.label, }; } case "closeOffcanvas": { return { active: this.props.showClose, classes: [conf.classes, this.props.closeClasses], }; } case "contentHO": { return { active: !!this.headerContent, tag: React.Fragment, content: this.headerContent, }; } case "bodyOffcanvas": { return { active: !!this.bodyContent, classes: [conf.classes, this.props.bodyClasses], content: this.bodyContent, }; } case "footerOffcanvas": { return { active: !!this.footerContent, classes: [conf.classes, this.props.footerClasses], content: this.footerContent, }; } case "contentOffcanvas": { return { active: !!this.contentOffcanvas, tag: React.Fragment, content: this.contentOffcanvas, }; } default: break; } return false; } }