Newer
Older
vmk-ui-kit / src / js / components / modals.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 1 day ago 3 KB Add GnConfirmDialog and confirmPopup helper
/**
 * Modals browser helper.
 *
 * Mirrors gnexus-ui-kit Modals public surface:
 *   create(id, props)
 */

function appendContent(container, content, mode = "html") {
	if(content instanceof Node) {
		container.append(content);
		return;
	}

	if(typeof content !== "undefined" && content !== null) {
		if(mode === "text") {
			container.textContent = content;
		} else {
			container.innerHTML = content;
		}
	}
}

function template(id, title, footer) {
	const modal = document.createElement("div");
	modal.className = "modal";
	modal.setAttribute("aria-hidden", "true");
	modal.id = id;

	const backdrop = document.createElement("div");
	backdrop.className = "modal-backdrop";

	const dialog = document.createElement("div");
	dialog.className = "modal-dialog";
	dialog.setAttribute("role", "dialog");
	dialog.setAttribute("aria-modal", "true");
	dialog.setAttribute("aria-labelledby", `${id}-title`);

	const panel = document.createElement("div");
	panel.className = "modal-panel";

	const header = document.createElement("header");
	header.className = "modal-header";

	const modalTitle = document.createElement("h4");
	modalTitle.className = "modal-title";
	modalTitle.id = `${id}-title`;
	modalTitle.textContent = title;

	const close = document.createElement("button");
	close.className = "btn-icon modal-close";
	close.type = "button";
	close.setAttribute("aria-label", "Close");
	close.textContent = "✕";

	const body = document.createElement("div");
	body.className = "modal-body";

	const modalFooter = document.createElement("footer");
	modalFooter.className = "modal-footer";
	appendContent(modalFooter, footer);

	header.append(modalTitle, close);
	panel.append(body, modalFooter);
	dialog.append(header, panel);
	modal.append(backdrop, dialog);

	return modal;
}

function init(modal, onready) {
	modal.show = function() {
		document.querySelector("body").append(modal);
		setTimeout(() => {
			modal.classList.add("a-show");
		}, 10);
	};

	modal.close = function() {
		modal.classList.add("a-hide");
		setTimeout(() => {
			modal.remove();
		}, 300);
	};

	modal.querySelector(".modal-close").addEventListener("click", () => {
		modal.close();
	});

	modal.querySelector(".modal-backdrop")?.addEventListener("click", () => {
		modal.close();
	});

	if(typeof onready === "function") {
		onready(modal);
	}

	return modal;
}

/**
 * Create a new modal window.
 * @param {string} id - Unique id
 * @param {object} props - { title, footer, body, bodyText, bodyHtml, actions, onready }
 * @returns {HTMLElement} Modal DOM element with show()/close()
 */
function create(id, props = {}) {
	const title = props.title || "";
	const footer = props.footer || "";

	const modal = template(id, title, footer);
	const modalBody = modal.querySelector(".modal-body");
	const modalFooter = modal.querySelector(".modal-footer");

	if(typeof props.actions === "function") {
		const actionsResult = props.actions(modal);

		if(Array.isArray(actionsResult) && actionsResult[0] instanceof Node) {
			const actions = document.createElement("div");
			actions.classList.add("actions");
			for(const actionElement of actionsResult) {
				actions.append(actionElement);
			}
			modalFooter.append(actions);
		} else if(actionsResult instanceof Node) {
			modalFooter.append(actionsResult);
		}
	}

	if(typeof props.body === "function") {
		const bodyResult = props.body(modal);
		appendContent(modalBody, bodyResult, props.bodyMode ?? "html");
	} else if(typeof props.bodyText !== "undefined") {
		appendContent(modalBody, props.bodyText, "text");
	} else if(typeof props.bodyHtml !== "undefined") {
		appendContent(modalBody, props.bodyHtml, "html");
	}

	return init(modal, props?.onready);
}

export default {
	create
};