Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F10360777
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/back/routes/common/fullscreen-menu/fullscreen-menu.css b/src/back/routes/common/fullscreen-menu/fullscreen-menu.css
new file mode 100644
index 0000000..f8f2ada
--- /dev/null
+++ b/src/back/routes/common/fullscreen-menu/fullscreen-menu.css
@@ -0,0 +1,57 @@
+.fullscreen-menu {
+ --fullscreen-menu-padding: 1rem;
+ --fullscreen-menu-height: 100vh;
+ --fulscreen-menu-inner-height: calc(
+ var(--fullscreen-menu-height) - 2 * var(--fullscreen-menu-padding)
+ );
+ position: absolute;
+ background-color: white;
+ padding: var(--fullscreen-menu-padding);
+ padding-right: 0; /* for scrollbar to be close to the screen edge */
+ height: var(--fullscreen-menu-height);
+ overflow: hidden;
+ width: 100vw;
+ left: 0;
+ top: 0;
+ border: 0;
+ box-sizing: border-box;
+ display: grid;
+ grid-template-rows: minmax(var(--fulscreen-menu-inner-height), 1fr);
+ grid-template-columns: 1fr;
+
+ /* These need to be overwritten upon activating the dialog */
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transform: scale(0.9);
+
+ transition: opacity 200ms, transform 200ms, visibility 0ms 200ms;
+ box-shadow: 1px 1px 11px #0000006e;
+}
+
+.fullscreen-menu__content {
+ grid-row: 1/2;
+ grid-column: 1/2;
+ display: flex;
+}
+
+.fullscreen-menu__panel-container {
+ display: grid;
+}
+
+.fullscreen-menu__panel {
+ opacity: 0;
+ pointer-events: none;
+ visibility: hidden;
+ transform: translateX(50vw);
+ transition: opacity 200ms, transform 200ms, visibility 0ms 200ms;
+
+ grid-row: 1/2;
+ grid-column: 1/2;
+
+ max-height: calc(var(--fulscreen-menu-inner-height) - 3rem);
+ overflow-y: auto;
+ width: 100%;
+ padding-right: var(--fullscreen-menu-padding);
+ box-sizing: border-box;
+}
diff --git a/src/back/routes/common/fullscreen-menu/fullscreen-menu.tsx b/src/back/routes/common/fullscreen-menu/fullscreen-menu.tsx
new file mode 100644
index 0000000..be7cd02
--- /dev/null
+++ b/src/back/routes/common/fullscreen-menu/fullscreen-menu.tsx
@@ -0,0 +1,107 @@
+/* eslint-disable @typescript-eslint/consistent-type-assertions */
+import type { FlatTemplatable } from "tempstream";
+import { TempstreamJSX } from "tempstream";
+
+type Styles = {
+ menu_open: string;
+ menu_closed: string;
+ body_when_open: string;
+ body_when_closed: string;
+ panel_open: string;
+ panel_closed: string;
+};
+
+const default_styles: Styles = {
+ menu_open: `opacity: 1; visibility: visible; pointer-events: all; transform: scale(1); transition: opacity 200ms, transform 200ms, visibility 0ms 0ms;`,
+ menu_closed: "",
+ body_when_open: "overflow: hidden;",
+ body_when_closed: "",
+ panel_open:
+ "opacity: 1; transform: translateX(0); visibility: visible; pointer-events: all; transition: opacity 200ms, transform 200ms, visibility 0ms 0ms;",
+ panel_closed: "",
+};
+
+export function fullscreenMenu({
+ id: menu_id,
+ panels,
+ default_panel,
+ styles = {},
+ inner_wrapper = (menu_id, content) => (
+ <div style="width: 100%;">
+ <div>
+ <label for={`${menu_id}--visible`}>Close with checkbox label</label>
+ </div>
+ <div>{content}</div>
+ </div>
+ ),
+ outer_wrapper = (_menu_id: string, content: FlatTemplatable) => content,
+}: {
+ id: string;
+ panels: Record<string, FlatTemplatable>;
+ default_panel: string;
+ styles?: Partial<Styles>;
+ outer_wrapper?: (menu_id: string, inner: FlatTemplatable) => FlatTemplatable;
+ inner_wrapper?: (menu_id: string, inner: FlatTemplatable) => FlatTemplatable;
+}) {
+ const full_styles = { ...default_styles, ...styles };
+ return outer_wrapper(
+ menu_id,
+ <div class={`fullscreen-menu fullscreen-menu--${menu_id}`} id={menu_id}>
+ <input
+ type="checkbox"
+ id={`${menu_id}--visible`}
+ autocomplete="off"
+ style="display: none"
+ />
+ {
+ /* HTML */ `<style>
+
+ body:has(#${menu_id}--visible:checked) .fullscreen-menu--${menu_id},
+ .fullscreen-menu--${menu_id}[open]{
+ ${full_styles.menu_open}
+ }
+
+ body:has(#${menu_id}--visible:checked),
+ body:has(.fullscreen-menu--${menu_id}[open]) {
+ ${full_styles.body_when_open}
+ }
+ </style>`
+ }
+ <div>
+ {Object.keys(panels).map((panel_id) => (
+ <span>
+ <input
+ type="radio"
+ name={`${menu_id}__active_panel`}
+ value={panel_id}
+ checked={panel_id == default_panel}
+ id={`${menu_id}__activate--${panel_id}`}
+ autocomplete="off"
+ style="display: none"
+ />
+ {`<style>
+ body:has(#${menu_id}--visible:checked) .fullscreen-menu:has(input[value="${panel_id}"]:checked) .fullscreen-menu__panel--${panel_id},
+ body:has(.fullscreen-menu--${menu_id}[open]) .fullscreen-menu:has(input[value="${panel_id}"]:checked) .fullscreen-menu__panel--${panel_id} {
+ ${full_styles.panel_open}
+ }
+ </style>`}
+ </span>
+ ))}
+ </div>
+ <div class="fullscreen-menu__content">
+ {inner_wrapper(
+ menu_id,
+ <div class="fullscreen-menu__panel-container">
+ {Object.entries(panels).map(([panel_id, panel_content]) => (
+ <div
+ class={`fullscreen-menu__panel fullscreen-menu__panel--${panel_id}`}
+ >
+ {panel_content}
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+}
diff --git a/src/back/routes/fullscreen-menu-demo.page.tsx b/src/back/routes/fullscreen-menu-demo.page.tsx
new file mode 100644
index 0000000..053dfd1
--- /dev/null
+++ b/src/back/routes/fullscreen-menu-demo.page.tsx
@@ -0,0 +1,88 @@
+import type { Context } from "koa";
+import { TempstreamJSX } from "tempstream";
+import { Page } from "@sealcode/sealgen";
+import html from "../html.js";
+import { fullscreenMenu } from "./common/fullscreen-menu/fullscreen-menu.js";
+
+export const actionName = "FullscreenMenuDemo";
+
+export default new (class FullscreenMenuDemoPage extends Page {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async canAccess(_: Context) {
+ return { canAccess: true, message: "" };
+ }
+
+ async render(ctx: Context) {
+ return html(
+ ctx,
+ "FullscreenMenuDemo",
+ <div>
+ {
+ /* HTML */ `<style>
+ .fullscreen-menu__panel--panel_1 {
+ transform: translateX(-50vw); /* move that one to the left */
+ }
+
+ .fullscreen-menu label {
+ cursor: pointer;
+ font-weight: bold;
+ }
+
+ .fullscreen-menu__panel {
+ font-size: 20px;
+ display: flex;
+ flex-flow: column;
+ row-gap: 20px;
+ }
+ </style>`
+ }
+ {fullscreenMenu({
+ id: "demo",
+ panels: {
+ panel_1: (
+ <div>
+ PANEL 1. Click{" "}
+ <label for="demo__activate--panel_2">here</label> to go to
+ panel 2<div>And here's some description</div>
+ Go to{" "}
+ <label for="demo__activate--panel_3">
+ Very tall panel 3
+ </label>
+ </div>
+ ),
+ panel_2: (
+ <div>
+ PANEL 2
+ <div>
+ <label for="demo__activate--panel_1">←Go back</label>
+ </div>
+ </div>
+ ),
+ panel_3: (
+ <div style="display: flex; flex-flow: column; row-gap: 20px;">
+ <label for="demo__activate--panel_1">←Go back</label>
+ {[
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30,
+ ].map((n) => (
+ <div>{n}</div>
+ ))}
+ </div>
+ ),
+ },
+ default_panel: "panel_1",
+ })}
+ <div style="display: flex; flex-flow: column; row-gap: 10px;">
+ <label for="demo--visible">Toggle menu via checkbox label</label>
+ <button onclick="demo.showModal()" type="button">
+ Toggle menu with javascript
+ </button>
+ </div>
+ <div style="height: 150vh">
+ This page is taaaaall - for testing reasons
+ </div>
+ </div>
+ );
+ }
+})();
diff --git a/src/back/routes/fullscreen-menu-demo.test.ts b/src/back/routes/fullscreen-menu-demo.test.ts
new file mode 100644
index 0000000..1ca3532
--- /dev/null
+++ b/src/back/routes/fullscreen-menu-demo.test.ts
@@ -0,0 +1,40 @@
+import { withProdApp } from "../test_utils/with-prod-app.js";
+import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "../test_utils/webhint.js";
+import { FullscreenMenuDemoURL } from "./urls.js";
+import { getBrowser } from "../test_utils/browser-creator.js";
+import type { Browser, BrowserContext, Page } from "@playwright/test";
+
+describe("FullscreenMenuDemo webhint", () => {
+ it("doesn't crash", async function () {
+ return withProdApp(async ({ base_url, rest_api }) => {
+ await rest_api.get(FullscreenMenuDemoURL);
+ await webhintURL(base_url + FullscreenMenuDemoURL);
+ // alternatively you can use webhintHTML for faster but less precise scans
+ // or for scanning responses of requests that use some form of authorization:
+ // const response = await rest_api.get(FullscreenMenuDemoURL);
+ // await webhintHTML(response);
+ });
+ }).timeout(VERY_LONG_TEST_TIMEOUT);
+});
+
+describe("FullscreenMenuDemo", () => {
+ let page: Page;
+ let browser: Browser;
+ let context: BrowserContext;
+
+ beforeEach(async () => {
+ browser = await getBrowser();
+ context = await browser.newContext();
+ page = await context.newPage();
+ });
+
+ afterEach(async () => {
+ await context.close();
+ });
+
+ it("works as expected", async function () {
+ return withProdApp(async ({ base_url }) => {
+ await page.goto(base_url + FullscreenMenuDemoURL);
+ });
+ }).timeout(VERY_LONG_TEST_TIMEOUT);
+});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 8, 08:38 (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1033175
Default Alt Text
(9 KB)
Attached To
Mode
rREWRITE Configurable rewriter
Attached
Detach File
Event Timeline
Log In to Comment