Add a "showFirstRow" UI element
This commit is contained in:
parent
adcd899253
commit
f63387df3c
2948
package-lock.json
generated
2948
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
src/back/routes/common/show-first-row/show-first-row.css
Normal file
30
src/back/routes/common/show-first-row/show-first-row.css
Normal file
@ -0,0 +1,30 @@
|
||||
.show-first-row {
|
||||
container-type: inline-size;
|
||||
text-align: center;
|
||||
|
||||
.show-first-row__items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(var(--min-column-width), 1fr));
|
||||
justify-content: center;
|
||||
gap: var(--column-gap);
|
||||
transition: row-gap 200ms, overflow 200ms;
|
||||
overflow: hidden;
|
||||
|
||||
input:not(:checked) + & {
|
||||
grid-template-rows: 1fr repeat(calc(var(--items-count) - 1), 0px);
|
||||
row-gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.show-first-row__default-button {
|
||||
background: blue;
|
||||
color: white;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
padding: 0 20px;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
margin: 0 auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
71
src/back/routes/common/show-first-row/show-first-row.tsx
Normal file
71
src/back/routes/common/show-first-row/show-first-row.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { FlatTemplatable } from "tempstream";
|
||||
|
||||
const showFirstRowIds = (function* () {
|
||||
let i = 0;
|
||||
while (true) {
|
||||
yield i++;
|
||||
if (i == 999999999) {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
export async function showFirstRow({
|
||||
items,
|
||||
min_column_width_px = 250,
|
||||
column_gap_px = 10,
|
||||
make_show_more_button = () => (
|
||||
<span class="show-first-row__default-button">Show more</span>
|
||||
),
|
||||
add_button_to_content = (content, button) => (
|
||||
<>
|
||||
{content}
|
||||
{button}
|
||||
</>
|
||||
),
|
||||
}: {
|
||||
items: FlatTemplatable[];
|
||||
min_column_width_px?: number;
|
||||
make_show_more_button?: () => JSX.Element;
|
||||
column_gap_px?: number;
|
||||
add_button_to_content?: (content: JSX.Element, button: JSX.Element) => JSX.Element;
|
||||
}): Promise<string> {
|
||||
const id = showFirstRowIds.next().value;
|
||||
const checkbox_id = "show-first-row__checkbox--" + id;
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
"show-first-row",
|
||||
`show-first-row--id-${id}`,
|
||||
`show-first-row--items-count-${items.length}`,
|
||||
]}
|
||||
style={`--min-column-width: ${min_column_width_px}px; --items-count: ${items.length}; --column-gap: ${column_gap_px}px`}
|
||||
>
|
||||
{
|
||||
/* HTML */ `<style>
|
||||
${[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
.reverse()
|
||||
.map(
|
||||
(n) => `@container (min-width: ${
|
||||
n * min_column_width_px + (n - 1) * column_gap_px
|
||||
}px) {
|
||||
.show-first-row--id-${id}:not(:has(.show-first-row__items > *:nth-child(${n + 1})))
|
||||
.show-first-row__button {
|
||||
display: none;
|
||||
}
|
||||
}`
|
||||
)
|
||||
.join("\n")}
|
||||
</style>`
|
||||
}
|
||||
<input type="checkbox" id={checkbox_id} style="display: none" />
|
||||
{add_button_to_content(
|
||||
<div class={["show-first-row__items"]}>{items}</div>,
|
||||
<label for={checkbox_id} class={["show-first-row__button"]}>
|
||||
{make_show_more_button()}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
40
src/back/routes/demos/show-first-row.page.tsx
Normal file
40
src/back/routes/demos/show-first-row.page.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import type { Context } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import { Page } from "@sealcode/sealgen";
|
||||
import html from "../../html.js";
|
||||
import { showFirstRow } from "../common/show-first-row/show-first-row.js";
|
||||
|
||||
export const actionName = "ShowFirstRowDemo";
|
||||
|
||||
function makeBoxStyle() {
|
||||
return `width: 100%; max-width: 200px; height: ${
|
||||
100 + Math.random() * 100
|
||||
}px; margin: auto; background-color: lime; text-align: center; color: #0000009e; font-size: 40px; padding-top: 20px; box-sizing: border-box;`;
|
||||
}
|
||||
|
||||
export default new (class ShowFirstRowDemoPage 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,
|
||||
"ShowFirstRowDemo",
|
||||
<div>
|
||||
<h1>showFirstRow() demo</h1>
|
||||
<h2> 9 elements (always overflows)</h2>
|
||||
{showFirstRow({
|
||||
items: [1, 2, 3, 4, 5, 6, 7, 8, 9].map((e) => (
|
||||
<div style={makeBoxStyle()}>{e}</div>
|
||||
)),
|
||||
})}
|
||||
<h2> 3 elements (overflow depends on screen width)</h2>
|
||||
{showFirstRow({
|
||||
items: [1, 2, 3].map((e) => <div style={makeBoxStyle()}>{e}</div>),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})();
|
40
src/back/routes/demos/show-first-row.test.ts
Normal file
40
src/back/routes/demos/show-first-row.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 { ShowFirstRowDemoURL } from "../urls.js";
|
||||
import { getBrowser } from "../../test_utils/browser-creator.js";
|
||||
import type { Browser, BrowserContext, Page } from "@playwright/test";
|
||||
|
||||
describe("ShowFirstRowDemo webhint", () => {
|
||||
it("doesn't crash", async function () {
|
||||
return withProdApp(async ({ base_url, rest_api }) => {
|
||||
await rest_api.get(ShowFirstRowDemoURL);
|
||||
await webhintURL(base_url + ShowFirstRowDemoURL);
|
||||
// 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(ShowFirstRowDemoURL);
|
||||
// await webhintHTML(response);
|
||||
});
|
||||
}).timeout(VERY_LONG_TEST_TIMEOUT);
|
||||
});
|
||||
|
||||
describe("ShowFirstRowDemo", () => {
|
||||
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 + ShowFirstRowDemoURL);
|
||||
});
|
||||
}).timeout(VERY_LONG_TEST_TIMEOUT);
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user