mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
- Update README.md to recommend `npm ci` and include test steps. - Update CONTRIBUTING.md with explicit development setup instructions. - Add `.devcontainer/devcontainer.json` for reproducible Node 20 environment. - Add audit report in `ai/reports/onboarding-20260118.md`.
272 lines
9.3 KiB
HTML
272 lines
9.3 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="z-overlay-modal bg-overlay/60 fixed inset-0 hidden items-center justify-center 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="docs-popover-scenarios__scroll-spacer space-y-lg">
|
|
<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>
|