Add tabs helper function
This commit is contained in:
parent
07b115e2b7
commit
ef82ae7bc6
22
src/back/routes/common/tabs/tabs.css
Normal file
22
src/back/routes/common/tabs/tabs.css
Normal 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;
|
||||
}
|
||||
}
|
70
src/back/routes/common/tabs/tabs.tsx
Normal file
70
src/back/routes/common/tabs/tabs.tsx
Normal 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>
|
||||
);
|
||||
}
|
48
src/back/routes/tabs-demo.page.tsx
Normal file
48
src/back/routes/tabs-demo.page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
})();
|
40
src/back/routes/tabs-demo.test.ts
Normal file
40
src/back/routes/tabs-demo.test.ts
Normal 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);
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user