Newer
Older
vmk-ui-kit / src / vue / utils.js
import { h } from "vue";
import VmkIcon from "./components/Icon.js";

export const variants = new Set([
	"primary",
	"secondary",
	"accent",
	"success",
	"warning",
	"danger",
	"error",
	"info"
]);

export function cx(...items) {
	return items
		.flatMap(item => {
			if (!item) {
				return [];
			}

			if (Array.isArray(item)) {
				return item;
			}

			if (typeof item === "object") {
				return Object.entries(item)
					.filter(([, enabled]) => enabled)
					.map(([name]) => name);
			}

			return [item];
		})
		.filter(Boolean)
		.join(" ");
}

export function normalizeVariant(value, fallback = "primary") {
	return variants.has(value) ? value : fallback;
}

const SIZE_MAP = {
	xxs: "btn-xxs",
	xs: "btn-xs",
	sm: "btn-sm",
	md: "btn-md",
	lg: "btn-lg"
};

export function normalizeSize(value, fallback = "btn-md") {
	return SIZE_MAP[value] || fallback;
}

export const inputVariants = new Set(["error", "success", "warning"]);

export function normalizeInputState(value, fallback = "") {
	return inputVariants.has(value) ? value : fallback;
}

export function eventValue(event) {
	const target = event.target;
	if (target.type === "checkbox") {
		return target.checked;
	}
	return target.value;
}

/**
 * Keep keyboard focus inside a modal/drawer container when Tab is pressed.
 */
export function trapFocus(event, container) {
	if (!container || event.key !== "Tab") return;

	const focusable = Array.from(
		container.querySelectorAll(
			'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
		)
	).filter(el => el.offsetParent !== null);

	if (focusable.length === 0) {
		event.preventDefault();
		return;
	}

	const first = focusable[0];
	const last = focusable[focusable.length - 1];
	const active = document.activeElement;

	if (event.shiftKey) {
		if (active === first || !container.contains(active)) {
			event.preventDefault();
			last.focus();
		}
	} else {
		if (active === last || !container.contains(active)) {
			event.preventDefault();
			first.focus();
		}
	}
}

const PHOSPHOR_TO_VMK = {
	spinner: "essentials/loading",
	loader: "essentials/loading",
	loading: "essentials/loading",
	"arrow-right": "arrows/arrow/right",
	"arrow-left": "arrows/arrow/left",
	"arrow-up": "arrows/arrow/up",
	"arrow-down": "arrows/arrow/down",
	"chevron-right": "arrows/chevron/right",
	"chevron-left": "arrows/chevron/left",
	"chevron-up": "arrows/chevron/up",
	"chevron-down": "arrows/chevron/down",
	"magnifying-glass": "essentials/zoom/search",
	search: "essentials/zoom/search",
	"warning-circle": "essentials/alert/circle",
	plus: "essentials/plus",
	minus: "essentials/minus",
	x: "essentials/close-cross",
	close: "essentials/close-cross",
	check: "essentials/check",
	user: "essentials/user",
	trash: "essentials/trash",
	copy: "documents-&-safety/copy",
	"chart-line-up": "banking-&-finance/chart/line",
	"chart-line": "banking-&-finance/chart/line",
	"check-circle": "essentials/check/circle",
	"chat-circle": "messages/message/circle/3",
	"chat": "messages/message/circle/3",
	"message": "messages/message/circle/3",
	"lock-key": "documents-&-safety/lock",
	lock: "documents-&-safety/lock",
	"sign-in": "essentials/enter",
	login: "essentials/enter",
	"sign-out": "essentials/exit",
	logout: "essentials/exit",
	pencil: "software/pencil/circle",
	"pencil-simple": "software/pencil/circle",
	edit: "software/edit/1",
	house: "buildings-&-landmarks/house/1",
	home: "buildings-&-landmarks/house/1",
	list: "essentials/list-fill",
	"credit-card": "banking-&-finance/credit-card",
	bell: "essentials/notification/1",
	"cloud-arrow-up": "software/cloud-upload",
	"cloud-upload": "software/cloud-upload",
	package: "e-commerce/box-stack",
	box: "e-commerce/box-stack",
	gear: "essentials/gear",
	settings: "essentials/gear",
	information: "essentials/information/circle",
	info: "essentials/information/circle",
	alert: "essentials/alert-sign",
	warning: "essentials/alert-sign",
	danger: "essentials/alert/circle",
	error: "essentials/alert/circle",
	success: "essentials/check/circle"
};

const PHOSPHOR_PREFIX_RE = /^ph[-\s]/;

export function iconNode(icon, extraClass = "") {
	if (!icon) {
		return null;
	}

	let name = icon.trim();

	// Accept legacy Phosphor prefixes (ph-plus, ph bold ph-spinner, etc).
	if (PHOSPHOR_PREFIX_RE.test(name)) {
		name = name.slice(3).trim();
	}

	// If the name already looks like a VMK key, use it directly.
	if (!name.includes("/")) {
		name = PHOSPHOR_TO_VMK[name] || `essentials/${name}`;
	}

	return h(VmkIcon, {
		name,
		class: extraClass
	});
}