JavaScript 58 views

AI Generated Snippet

A code snippet generated by AI.

By TWC Team • Jan 31, 2026

Code

/**
 * Revealing Card Modal (Vanilla JS): click a card to open an accessible modal with details.
 * Usage: add [data-modal-card] elements with data-title/data-content; call initRevealingCardModal().
 */

(() => {
  const SELECTORS = {
    card: "[data-modal-card]",
    modal: "#card-modal",
    title: "#card-modal-title",
    body: "#card-modal-body",
    close: "[data-modal-close]",
  };

  const escapeHtml = (s = "") =>
    String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));

  const ensureModal = () => {
    const existing = document.querySelector(SELECTORS.modal);
    if (existing) return existing;

    const modal = document.createElement("div");
    modal.id = "card-modal";
    modal.setAttribute("role", "dialog");
    modal.setAttribute("aria-modal", "true");
    modal.setAttribute("aria-labelledby", "card-modal-title");
    modal.hidden = true;
    modal.style.cssText =
      "position:fixed;inset:0;display:grid;place-items:center;background:rgba(0,0,0,.55);padding:16px;z-index:9999;";

    modal.innerHTML = `
      <div style="width:min(560px,100%);background:#fff;border-radius:12px;padding:18px 18px 14px;box-shadow:0 10px 30px rgba(0,0,0,.25);">
        <div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;">
          <h2 id="card-modal-title" style="margin:0;font:600 18px/1.2 system-ui,-apple-system,Segoe UI,Roboto;">Details</h2>
          <button type="button" data-modal-close aria-label="Close" style="border:0;background:transparent;font-size:22px;line-height:1;cursor:pointer;">×</button>
        </div>
        <div id="card-modal-body" style="margin-top:10px;font:400 14px/1.55 system-ui,-apple-system,Segoe UI,Roboto;color:#222;"></div>
      </div>
    `;
    document.body.appendChild(modal);
    return modal;
  };

  const fetchContentIfNeeded = async (card) => {
    const { content = "", contentUrl = "" } = card.dataset;
    if (!contentUrl) return content;
    try {
      const res = await fetch(contentUrl, { headers: { Accept: "text/plain" } });
      if (!res.ok) throw new Error(`Fetch failed (${res.status})`);
      return await res.text();
    } catch (err) {
      console.error(err);
      return content || "Sorry, details are unavailable right now.";
    }
  };

  const initRevealingCardModal = ({ root = document } = {}) => {
    const modal = ensureModal();
    const titleEl = modal.querySelector(SELECTORS.title);
    const bodyEl = modal.querySelector(SELECTORS.body);
    const closeBtn = modal.querySelector(SELECTORS.close);

    let lastActiveEl = null;

    const setModalOpen = (open) => {
      modal.hidden = !open;
      document.documentElement.style.overflow = open ? "hidden" : "";
      if (!open && lastActiveEl) lastActiveEl.focus?.();
    };

    const openFromCard = async (card) => {
      lastActiveEl = document.activeElement;
      const { title = "Details" } = card.dataset;

      titleEl.textContent = title;
      bodyEl.textContent = "Loading…";
      setModalOpen(true);
      closeBtn.focus?.();

      const content = await fetchContentIfNeeded(card);
      bodyEl.innerHTML = escapeHtml(content).replace(/\n/g, "<br>");
    };

    // Event delegation for performance: one listener handles all cards, even dynamically added ones.
    root.addEventListener("click", (e) => {
      const card = e.target.closest(SELECTORS.card);
      if (card) openFromCard(card);
    });

    // Close interactions: button, backdrop click, and Escape key.
    closeBtn.addEventListener("click", () => setModalOpen(false));
    modal.addEventListener("click", (e) => {
      if (e.target === modal) setModalOpen(false);
    });
    window.addEventListener("keydown", (e) => {
      if (!modal.hidden && e.key === "Escape") setModalOpen(false);
    });

    return { openFromCard, close: () => setModalOpen(false) };
  };

  // Example usage:
  // <div data-modal-card data-title="Card 1" data-content="Details about Card 1">...</div>
  // <div data-modal-card data-title="Card 2" data-content-url="/details/card-2.txt">...</div>
  window.initRevealingCardModal = initRevealingCardModal;
})();
Back to Snippets