feat(tables): a glyph pager with first/last, and tablet-width columns step aside

The pager is four named arrows round the page box (first, previous,
next, last). A table hides a tabletHidden column below 64rem of its own
width, as narrowHidden does below 42rem, through one shared helper that
also hides the column's <col>. The Channels table drops Prio, About and
Length there: at 1024 the channel name had 60 px.
This commit is contained in:
2026-09-27 07:12:51 +02:00
parent d3e1d8219c
commit 955c23d321
9 changed files with 128 additions and 37 deletions
+46 -10
View File
@@ -111,27 +111,63 @@ describe("Pager · the page box follows the page without an effect", () => {
});
});
// E28 S3f3: on a phone the row ran 18px past a 375 screen. The arrows keep their names (a screen
// reader still hears Previous / Next) and only the visible words step aside below `sm`; the size label
// stays one line rather than a word per line.
describe("Pager · on a phone", () => {
it("hides only the visible words of Previous / Next, never their names", () => {
// Glyphs only (the owner, 2026-09-27): First / Previous / Next / Last are four arrows that carry their
// names as the accessible name and the tooltip, and show no words at any width (the words once pushed
// a phone row 18px past a 375 screen). The size label stays one line rather than a word per line.
describe("Pager · four named arrows", () => {
function renderAt(page: number, onPageChange = vi.fn()) {
render(
<Pager
page={1}
page={page}
size={10}
total={120}
pageSizeOptions={[10, 25]}
onPageChange={() => {}}
onPageChange={onPageChange}
onSizeChange={() => {}}
sizeLabel="Rows per page"
/>,
);
for (const name of ["datatable.pager.prev", "datatable.pager.next"]) {
return onPageChange;
}
const NAMES = ["first", "prev", "next", "last"].map((k) => `datatable.pager.${k}`);
it("draws each as a glyph with its name as the tooltip, and no visible word", () => {
renderAt(5);
for (const name of NAMES) {
const btn = screen.getByRole("button", { name });
const word = [...btn.querySelectorAll("span")].find((s) => s.textContent === name)!;
expect(word.className.split(/\s+/)).toContain("max-sm:sr-only");
expect(btn.getAttribute("title")).toBe(name);
expect(btn.textContent).toBe("");
expect(btn.querySelector("svg")).not.toBeNull();
}
expect(screen.getByText("Rows per page").className).toContain("whitespace-nowrap");
});
it("jumps to the first, previous, next and last page", () => {
const onPageChange = renderAt(5);
for (const name of NAMES) fireEvent.click(screen.getByRole("button", { name }));
expect(onPageChange.mock.calls).toEqual([[0], [4], [6], [11]]);
});
it("turns off the pair that points past an edge", () => {
renderAt(0);
const off = (k: string) =>
(screen.getByRole("button", { name: `datatable.pager.${k}` }) as HTMLButtonElement)
.disabled ||
screen.getByRole("button", { name: `datatable.pager.${k}` }).getAttribute("aria-disabled") ===
"true";
expect([off("first"), off("prev"), off("next"), off("last")]).toEqual([
true,
true,
false,
false,
]);
cleanup();
renderAt(11);
expect([off("first"), off("prev"), off("next"), off("last")]).toEqual([
false,
false,
true,
true,
]);
});
});