Files

317 lines
9.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
hydrateCart,
subscribe,
getSnapshot,
formatPrice,
} from "/js/cart.js";
function qs(sel, root = document) {
return root.querySelector(sel);
}
function qsa(sel, root = document) {
return [...root.querySelectorAll(sel)];
}
function escapeHtml(s) {
return String(s)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
}
export function initHeader() {
hydrateCart();
const header = qs(".site-header");
const mega = qs(".mega-panel");
const backdrop = qs(".mega-backdrop");
const drawer = qs(".mobile-drawer");
const badge = qs("[data-cart-count]");
const cartMenu = qs("[data-cart-menu]");
const cartTrigger = qs("[data-cart-trigger]");
const cartPreview = qs("[data-cart-preview]");
const cartList = qs("[data-cart-preview-list]");
const cartEmpty = qs("[data-cart-preview-empty]");
const cartFoot = qs("[data-cart-preview-foot]");
const cartTotal = qs("[data-cart-preview-total]");
const fineHover =
typeof window.matchMedia === "function" &&
window.matchMedia("(hover: hover) and (pointer: fine)").matches;
function syncBadge(snap) {
if (!badge) return;
badge.textContent = String(snap.itemCount);
badge.classList.toggle("is-visible", snap.hydrated && snap.itemCount > 0);
}
function renderCartPreview(snap) {
if (!cartList || !cartEmpty || !cartFoot || !cartTotal) return;
const empty = !snap.items.length;
cartEmpty.hidden = !empty;
cartFoot.hidden = empty;
cartList.hidden = empty;
cartTotal.textContent = formatPrice(snap.subtotal);
if (empty) {
cartList.innerHTML = "";
return;
}
cartList.innerHTML = snap.items
.slice(0, 8)
.map((item) => {
const href = `/products/${escapeHtml(item.slug)}/`;
const img = item.image
? `<img src="${escapeHtml(item.image)}" alt="" width="52" height="66" loading="lazy" />`
: `<div class="cart-preview-ph" aria-hidden="true"></div>`;
return `<a class="cart-preview-line" href="${href}">
${img}
<div class="cart-preview-meta">
<p class="cart-preview-name">${escapeHtml(item.name)}</p>
<p class="cart-preview-qty">× ${item.quantity.toLocaleString("fa-IR")}</p>
</div>
<p class="cart-preview-price">${formatPrice(item.price * item.quantity)}</p>
</a>`;
})
.join("");
}
function setCartPreviewOpen(open) {
if (!cartMenu || !cartPreview || !cartTrigger) return;
cartMenu.classList.toggle("is-open", open);
cartPreview.classList.toggle("is-open", open);
if (open) cartPreview.removeAttribute("hidden");
else cartPreview.setAttribute("hidden", "");
cartTrigger.setAttribute("aria-expanded", open ? "true" : "false");
}
function closeCartPreview() {
setCartPreviewOpen(false);
}
function openCartPreview() {
setCartPreviewOpen(true);
}
const sideNav = qs(".side-nav");
const sideNavToggle = qs("[data-side-nav-toggle]");
const SIDE_NAV_KEY = "kikomiko-side-nav-collapsed";
function setSideNavCollapsed(collapsed) {
if (!sideNav || !sideNavToggle) return;
sideNav.classList.toggle("is-collapsed", collapsed);
sideNavToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
const label = collapsed ? "نمایش منو" : "پنهان کردن منو";
sideNavToggle.setAttribute("aria-label", label);
sideNavToggle.setAttribute("title", label);
try {
localStorage.setItem(SIDE_NAV_KEY, collapsed ? "1" : "0");
} catch {
/* ignore */
}
if (collapsed) closeMega();
}
function hideDesktopMenu() {
closeMega();
setSideNavCollapsed(true);
}
subscribe((snap) => {
syncBadge(snap);
renderCartPreview(snap);
});
const initial = getSnapshot();
syncBadge(initial);
renderCartPreview(initial);
if (cartMenu && cartTrigger && cartPreview) {
cartTrigger.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const open = cartMenu.classList.contains("is-open");
if (open) closeCartPreview();
else {
closeMega();
openCartPreview();
}
});
if (fineHover) {
let leaveTimer = 0;
cartMenu.addEventListener("mouseenter", () => {
window.clearTimeout(leaveTimer);
openCartPreview();
});
cartMenu.addEventListener("mouseleave", () => {
leaveTimer = window.setTimeout(closeCartPreview, 180);
});
}
document.addEventListener("click", (e) => {
if (!(e.target instanceof Element)) return;
if (!cartMenu.contains(e.target)) closeCartPreview();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeCartPreview();
});
}
function closeMega() {
mega?.classList.remove("is-open");
mega?.removeAttribute("data-active");
backdrop?.classList.remove("is-open");
backdrop?.setAttribute("hidden", "");
header?.classList.remove("is-open");
qsa("[data-mega-key]").forEach((b) => b.classList.remove("is-active"));
qsa("[data-mega-pane]").forEach((pane) => {
pane.hidden = true;
});
// Restore home highlight when on home
const home = qs('.side-nav-link[href="/"]');
if (home && (location.pathname === "/" || location.pathname === "/fa")) {
home.classList.add("is-active");
}
}
function openMega(key) {
if (!mega) return;
closeCartPreview();
// Keep the side panel open while browsing mega categories
if (sideNav?.classList.contains("is-collapsed")) {
setSideNavCollapsed(false);
}
mega.setAttribute("data-active", key);
mega.classList.add("is-open");
header?.classList.add("is-open");
backdrop?.classList.add("is-open");
backdrop?.removeAttribute("hidden");
qsa("[data-mega-pane]").forEach((pane) => {
pane.hidden = pane.getAttribute("data-mega-pane") !== key;
});
qsa(".side-nav-link").forEach((b) => b.classList.remove("is-active"));
qsa("[data-mega-key]").forEach((b) =>
b.classList.toggle("is-active", b.getAttribute("data-mega-key") === key)
);
}
qsa("[data-mega-key]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const key = btn.getAttribute("data-mega-key");
if (!key) return;
const open = mega?.getAttribute("data-active") === key;
if (open) {
closeMega();
return;
}
openMega(key);
});
});
// Close mega immediately when any link inside it is chosen
// (categories, view-all, featured products, collection title).
mega?.addEventListener(
"click",
(e) => {
const link = e.target instanceof Element ? e.target.closest("a[href]") : null;
if (!link || !mega.contains(link)) return;
hideDesktopMenu();
},
true
);
// Home / offers links in side nav should close mega + collapse panel
qsa(".side-nav-link[href]").forEach((link) => {
link.addEventListener("click", () => hideDesktopMenu());
});
// Highlight active offers item
if (location.pathname.startsWith("/offers")) {
qsa(".side-nav-link").forEach((el) => el.classList.remove("is-active"));
qs(
'.side-nav-link[href="/offers/"], .side-nav-link[href="/offers"]'
)?.classList.add("is-active");
}
// Do not use mouseleave — it fights with clicks on mega links.
backdrop?.addEventListener("click", closeMega);
qs("[data-mega-close]")?.addEventListener("click", closeMega);
function closeDrawer(e) {
e?.preventDefault?.();
e?.stopPropagation?.();
drawer?.classList.remove("is-open");
drawer?.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
}
function openDrawer(e) {
e?.preventDefault?.();
drawer?.classList.add("is-open");
drawer?.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
closeMega();
closeCartPreview();
}
qs("[data-menu-open]")?.addEventListener("click", openDrawer);
qsa("[data-menu-close]").forEach((el) => {
el.addEventListener("click", closeDrawer);
});
qsa("[data-mobile-expand]").forEach((btn) => {
btn.addEventListener("click", () => {
const id = btn.getAttribute("data-mobile-expand");
const panel = qs(`[data-mobile-sub="${id}"]`);
const cat = btn.closest(".mobile-cat");
const open = panel?.classList.contains("is-open");
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
if (!open) {
panel?.classList.add("is-open");
cat?.classList.add("is-expanded");
}
});
});
// Highlight current route in the mobile drawer
const path = window.location.pathname.replace(/\/+$/, "") || "/";
qsa(".mobile-nav a[href]").forEach((a) => {
const href = (a.getAttribute("href") || "").replace(/\/+$/, "") || "/";
if (href === path || (href !== "/" && path.startsWith(href))) {
a.setAttribute("aria-current", "page");
a.classList.add("is-active");
}
});
try {
if (localStorage.getItem(SIDE_NAV_KEY) === "1") {
setSideNavCollapsed(true);
}
} catch {
/* ignore */
}
sideNavToggle?.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
setSideNavCollapsed(!sideNav?.classList.contains("is-collapsed"));
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeMega();
closeDrawer();
closeCartPreview();
}
});
}