Files
bitvid/docs/popover-scenarios.html
T

253 lines
9.1 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Popover Scenarios | bitvid</title>
<link rel="stylesheet" href="../css/tailwind.generated.css" />
</head>
<body class="min-h-screen bg-page text-text" data-ds="new">
<main class="mx-auto flex max-w-6xl flex-col gap-xl px-lg py-xl">
<header class="space-y-sm">
<h1 class="text-2xl font-semibold">Popover positioning scenarios</h1>
<p class="max-w-3xl text-sm text-muted">
Interactive harness that exercises the popover engine near the edges of the
viewport, inside modals, and within scroll containers. Playwright scenarios
rely on the data attributes surfaced here.
</p>
</header>
<section class="space-y-md" data-test-section="grid">
<div class="space-y-xs">
<h2 class="text-xl font-semibold">Grid edge cases</h2>
<p class="text-sm text-muted">
The rightmost card hugs the viewport edge to verify collision handling.
</p>
</div>
<div class="grid gap-lg sm:grid-cols-2 lg:grid-cols-3">
<article class="card space-y-sm p-md">
<h3 class="text-lg font-semibold">Top left</h3>
<p class="text-sm text-muted">Baseline placement without collisions.</p>
<button
class="btn-ghost"
type="button"
data-test-trigger="grid-top-left"
aria-label="Open top-left menu"
>
Menu
</button>
</article>
<article class="card space-y-sm p-md" data-test-card="grid-bottom-right">
<h3 class="text-lg font-semibold">Bottom right</h3>
<p class="text-sm text-muted">
Forces the panel to flip when it would overflow the viewport.
</p>
<div class="flex justify-end">
<button
class="btn-ghost"
type="button"
data-test-trigger="grid-bottom-right"
aria-label="Open bottom-right menu"
>
Menu
</button>
</div>
</article>
<article class="card space-y-sm p-md">
<h3 class="text-lg font-semibold">Center</h3>
<p class="text-sm text-muted">Acts as a control for viewport padding.</p>
<div class="flex justify-center">
<button
class="btn-ghost"
type="button"
data-test-trigger="grid-center"
aria-label="Open center menu"
>
Menu
</button>
</div>
</article>
</div>
</section>
<section class="space-y-md" data-test-section="modal">
<div class="space-y-xs">
<h2 class="text-xl font-semibold">Video modal integration</h2>
<p class="text-sm text-muted">
Mimics the production video modal so we can verify popovers respect the overlay root.
</p>
</div>
<button class="btn w-40" type="button" data-test-open-modal>
Open modal
</button>
<div
class="fixed inset-0 z-overlay-modal hidden items-center justify-center bg-overlay/60 backdrop-blur"
role="dialog"
aria-modal="true"
aria-labelledby="testModalTitle"
data-test-modal
>
<div class="bv-modal__panel flex max-h-modal-shell flex-col gap-md p-lg">
<header class="flex items-center justify-between gap-sm">
<div>
<p class="text-xs uppercase tracking-wide text-muted">Now playing</p>
<h3 id="testModalTitle" class="text-lg font-semibold">
Edge-of-viewport sample
</h3>
</div>
<button class="btn-ghost" type="button" data-test-close-modal>
Close
</button>
</header>
<div class="space-y-md">
<p class="text-sm text-muted">
Interact with the more menu to verify popovers inside scroll-locked contexts.
</p>
<div class="flex justify-end">
<button
class="btn-ghost"
type="button"
data-test-trigger="modal-menu"
aria-label="Open modal menu"
>
Menu
</button>
</div>
</div>
</div>
</div>
</section>
<section class="space-y-md" data-test-section="scroll">
<div class="space-y-xs">
<h2 class="text-xl font-semibold">Scroll container</h2>
<p class="text-sm text-muted">
Ensures auto-update keeps popovers aligned when the trigger lives in overflow regions.
</p>
</div>
<div
class="h-64 overflow-auto rounded-xl border border-border bg-surface-alt p-md"
data-test-scroll-region
>
<div class="space-y-lg" style="height: 720px">
<p class="text-sm text-muted">
Scroll to the bottom to find the trigger used in automated tests.
</p>
<div class="mt-96 flex justify-center">
<button
class="btn"
type="button"
data-test-trigger="scroll-bottom"
aria-label="Open scroll container menu"
>
Open menu
</button>
</div>
</div>
</div>
</section>
</main>
<script type="module">
import createPopover from "../js/ui/overlay/popoverEngine.js";
const ACTIVE_LABELS = ["Play", "Save", "Share", "Open in new tab"];
function buildMenu({ container, trigger, id }) {
const panel = container.ownerDocument.createElement("div");
panel.className = "popover__panel card w-64 max-w-popover-safe space-y-sm";
panel.dataset.testPanel = id;
const heading = container.ownerDocument.createElement("h4");
heading.className = "text-sm font-semibold text-text";
heading.textContent = trigger?.getAttribute("aria-label") || "Menu";
panel.appendChild(heading);
const menu = container.ownerDocument.createElement("div");
menu.className = "menu space-y-1";
ACTIVE_LABELS.forEach((label, index) => {
const item = container.ownerDocument.createElement("button");
item.className = "menu__item justify-between";
item.type = "button";
item.setAttribute("role", "menuitem");
item.textContent = label;
item.dataset.index = String(index);
menu.appendChild(item);
});
panel.appendChild(menu);
container.appendChild(panel);
return panel;
}
function attachPopover(trigger, { id }) {
const popover = createPopover(
trigger,
({ container }) => buildMenu({ container, trigger, id }),
{ document }
);
trigger.addEventListener("click", async (event) => {
event.preventDefault();
await popover.open();
});
return popover;
}
const triggers = [
{ selector: "[data-test-trigger=grid-top-left]", id: "grid-top-left" },
{ selector: "[data-test-trigger=grid-bottom-right]", id: "grid-bottom-right" },
{ selector: "[data-test-trigger=grid-center]", id: "grid-center" },
{ selector: "[data-test-trigger=modal-menu]", id: "modal-menu" },
{ selector: "[data-test-trigger=scroll-bottom]", id: "scroll-bottom" }
];
const popovers = new Map();
triggers.forEach(({ selector, id }) => {
const trigger = document.querySelector(selector);
if (trigger) {
popovers.set(id, attachPopover(trigger, { id }));
}
});
const modal = document.querySelector("[data-test-modal]");
const openModalBtn = document.querySelector("[data-test-open-modal]");
const closeModalBtn = document.querySelector("[data-test-close-modal]");
if (openModalBtn && modal) {
openModalBtn.addEventListener("click", () => {
modal.classList.remove("hidden");
modal.removeAttribute("hidden");
document.documentElement.classList.add("modal-open");
document.body.classList.add("modal-open");
});
}
if (closeModalBtn && modal) {
closeModalBtn.addEventListener("click", () => {
modal.classList.add("hidden");
modal.setAttribute("hidden", "");
document.documentElement.classList.remove("modal-open");
document.body.classList.remove("modal-open");
const modalPopover = popovers.get("modal-menu");
modalPopover?.close();
});
}
window.__popoverTestHarness = {
popovers,
open(id) {
return popovers.get(id)?.open();
},
close(id) {
return popovers.get(id)?.close();
}
};
</script>
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
</body>
</html>