Newer
Older
gnexus-ui-kit / src / vue / components / GnNavigationShell.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 14 hours ago 3 KB Add Vue navigation overlay adapters
import { defineComponent, h, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { iconNode } from "../utils.js";
import GnNavList from "./GnNavList.js";

let shellId = 0;

export default defineComponent({
	name: "GnNavigationShell",
	props: {
		brand: { type: String, default: "GNexus UI Kit" },
		logoSrc: { type: String, default: "/assets/imgs/gnexus-mark.svg" },
		current: { type: String, default: "" },
		title: { type: String, default: "Sections" },
		subtitle: { type: String, default: "Navigation" },
		footerLeft: { type: String, default: "" },
		footerRight: { type: String, default: "" },
		items: { type: Array, default: () => [] }
	},
	emits: ["select"],
	setup(props, { emit, slots }) {
		const open = ref(false);
		const drawerId = `gn-nav-drawer-${++shellId}`;
		const drawerRef = ref(null);
		let previousFocus = null;
		const close = () => {
			open.value = false;
		};
		const toggle = () => {
			open.value = !open.value;
		};
		const onKeydown = event => {
			if(event.key === "Escape") {
				event.preventDefault();
				close();
			}
		};

		watch(open, isOpen => {
			if(isOpen) {
				previousFocus = document.activeElement;
				document.body.classList.add("nav-drawer-open");
				document.addEventListener("keydown", onKeydown);
				nextTick(() => drawerRef.value?.focus());
			} else {
				document.body.classList.remove("nav-drawer-open");
				document.removeEventListener("keydown", onKeydown);
				previousFocus?.focus?.();
				previousFocus = null;
			}
		});

		onBeforeUnmount(() => {
			document.body.classList.remove("nav-drawer-open");
			document.removeEventListener("keydown", onKeydown);
		});

		return () => [
			h("header", { class: "nav-topbar" }, [
				h("button", {
					class: "nav-topbar-toggle",
					type: "button",
					"aria-controls": drawerId,
					"aria-expanded": open.value ? "true" : "false",
					onClick: toggle
				}, [
					iconNode("ph-sidebar-simple"),
					h("span", {}, "Menu")
				]),
				h("div", { class: "nav-topbar-brand" }, [
					props.logoSrc && h("img", { src: props.logoSrc, alt: "", "aria-hidden": "true" }),
					h("span", {}, slots.brand?.() || props.brand)
				]),
				h("div", { class: "nav-topbar-current" }, slots.current?.() || props.current)
			]),
			h("div", { class: "nav-drawer-backdrop", onClick: close }),
			h("aside", {
				ref: drawerRef,
				class: ["nav-drawer", { "is-open": open.value }],
				id: drawerId,
				"aria-label": "Navigation",
				"aria-hidden": open.value ? "false" : "true",
				tabindex: "-1"
			}, [
				h("header", { class: "nav-drawer-header" }, [
					h("div", {}, [
						h("div", { class: "nav-drawer-title" }, slots.title?.() || props.title),
						h("div", { class: "nav-drawer-subtitle" }, slots.subtitle?.() || props.subtitle)
					]),
					h("button", {
						class: "nav-drawer-close",
						type: "button",
						"aria-label": "Close navigation",
						onClick: close
					}, [iconNode("ph-x")])
				]),
				h("nav", { class: "nav-drawer-body" }, [
					slots.default?.({ close }) || h(GnNavList, {
						items: props.items,
						onSelect: item => {
							emit("select", item);
							close();
						}
					})
				]),
				(slots.footer || props.footerLeft || props.footerRight) && h("footer", { class: "nav-drawer-footer" },
					slots.footer?.() || [
						h("span", {}, props.footerLeft),
						h("span", {}, props.footerRight)
					]
				)
			]),
			slots.content?.()
		];
	}
});