Add tabs helper function

This commit is contained in:
Kuba Orlik 2024-05-21 21:25:50 +02:00
parent 07b115e2b7
commit ef82ae7bc6
4 changed files with 180 additions and 0 deletions

View File

@ -0,0 +1,22 @@
.tabs {
& > nav input[type="radio"] {
display: none;
}
nav {
display: flex;
flex-flow: row nowrap;
gap: 8px;
.tabs__tab-label {
label {
cursor: pointer;
font-size: 16px;
user-select: none;
}
}
}
.tabs__tab {
display: none;
}
}

View File

@ -0,0 +1,70 @@
import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream";
const ids = (function* () {
let i = 0;
while (true) {
yield i++;
}
})();
export function tabs({
tabs,
default_tab,
tab_bar,
remember_tab = false,
active_navbar_tab_style = "",
}: {
tabs: { id: string; label?: string; content: FlatTemplatable }[];
default_tab: string;
tab_bar?: FlatTemplatable;
remember_tab?: boolean;
active_navbar_tab_style?: string;
}) {
const tab_section_id = ids.next().value;
return (
<section class={`tabs tabs__${tab_section_id}`}>
{tab_bar || (
<nav>
{tabs.map(({ id, label }) => (
<div class="tabs__tab-label">
<label>
<input
type="radio"
id={`tabs__${tab_section_id}__tab__${id}`}
name={`tabs__${tab_section_id}`}
checked={id == default_tab}
autocomplete={remember_tab ? false : "off"}
/>
{label || id}
{
/* HTML */ `<style>
.tabs__${tab_section_id} nav label:has(input:checked) {
${active_navbar_tab_style}
}
</style>`
}
</label>
</div>
))}
</nav>
)}
{tabs.map(({ id, content }) => {
const tab_id = `tabs__${tab_section_id}__tab__${id}`;
return (
<div class={`tabs__tab tabs__${tab_section_id}__tab ` + tab_id}>
{
/* HTML */ `<style>
body:has(#tabs__${tab_section_id}__tab__${id}:checked)
.${tab_id} {
display: block;
}
</style>`
}
{content}
</div>
);
})}
</section>
);
}

View File

@ -0,0 +1,48 @@
import type { Context } from "koa";
import { TempstreamJSX } from "tempstream";
import { Page } from "@sealcode/sealgen";
import html from "../html.js";
import { tabs } from "./common/tabs/tabs.js";
export const actionName = "TabsDemo";
export default new (class TabsDemoPage 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,
"TabsDemo",
<div>
{tabs({
default_tab: "tab1",
active_navbar_tab_style:
"font-weight: bold; text-decoration: underline",
tabs: [
{
id: "tab1",
label: "First tab",
content: (
<div>
<h2>This is tab 1</h2>
</div>
),
},
{
id: "tab2",
label: "Second tab",
content: (
<div>
<h2>And this is tab 2</h2>
</div>
),
},
],
})}
</div>
);
}
})();

View File

@ -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 { TabsDemoURL } from "./urls.js";
import { getBrowser } from "../test_utils/browser-creator.js";
import type { Browser, BrowserContext, Page } from "@playwright/test";
describe("TabsDemo webhint", () => {
it("doesn't crash", async function () {
return withProdApp(async ({ base_url, rest_api }) => {
await rest_api.get(TabsDemoURL);
await webhintURL(base_url + TabsDemoURL);
// 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(TabsDemoURL);
// await webhintHTML(response);
});
}).timeout(VERY_LONG_TEST_TIMEOUT);
});
describe("TabsDemo", () => {
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 + TabsDemoURL);
});
}).timeout(VERY_LONG_TEST_TIMEOUT);
});