feat(ui): E31 S4b — the admin pages behind one rail row, Configuration as a sectioned list, Statistics as tiles and bars

- NavRailGroup: one "Admin" rail row opening a panel of the admin pages (links, so a
  middle-click still opens a tab); the rail fits 133% zoom without a hidden scroll
- sysconfig.SECTIONS orders the config groups under four headings (every group in exactly
  one, test-held); the page shows them as Settings' side list (Tabs `group` headings);
  the lone registration group reads "Registration" under "Access"
- StatTiles / StatTile / StatBars: the Statistics overview's figures as tiles, the
  per-operation quota use as horizontal bars (a zero draws none)
This commit is contained in:
2026-10-02 00:07:00 +02:00
parent 25905aa786
commit be156fc52b
21 changed files with 753 additions and 223 deletions
@@ -7,8 +7,8 @@ import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { Bell } from "lucide-react";
import { NavRailAction, NavRailItem } from "./NavRail";
import { Activity, Bell, Shield, Users } from "lucide-react";
import { NavRailAction, NavRailGroup, NavRailItem } from "./NavRail";
vi.mock("../../lib/hints", () => ({ hintsEnabled: () => true, subscribeHints: () => () => {} }));
@@ -123,3 +123,86 @@ describe("NavRailAction", () => {
expect(src).not.toMatch(/<IconButton[\s>]/);
});
});
// E31 (the owner's pick): the five admin rows sat below the fold at 133% zoom behind a fade alone, so
// they are one "Admin" row opening a panel of them. The panel's pages stay links, as the rail's are.
describe("NavRailGroup", () => {
const pages = (active: string | null, onSelect = vi.fn()) =>
[
{ id: "scheduler", icon: Activity, label: "Scheduler" },
{ id: "users", icon: Users, label: "Users" },
].map((p) => ({ ...p, href: `/?page=${p.id}`, active: p.id === active, onSelect }));
it("is one row, current while any of its pages is, opening a panel of links", () => {
render(
<NavRailGroup
icon={Shield}
label="Administration"
shortLabel="Admin"
showLabel={false}
pages={pages("users")}
/>,
);
const row = screen.getByRole("button", { name: "Administration" });
expect(row.getAttribute("aria-current")).toBe("page");
expect(screen.queryByRole("link")).toBeNull();
fireEvent.click(row);
// jsdom never positions the panel, so it stays visibility:hidden and nameless to the role query.
const panel = screen.getByRole("dialog", { hidden: true });
expect(panel.getAttribute("aria-label")).toBe("Administration");
const links = Array.from(panel.querySelectorAll("a"));
expect(links.map((a) => [a.textContent, a.getAttribute("href")])).toEqual([
["Scheduler", "/?page=scheduler"],
["Users", "/?page=users"],
]);
expect(links.map((a) => a.getAttribute("aria-current"))).toEqual([null, "page"]);
});
it("is not current while none of its pages is", () => {
render(
<NavRailGroup
icon={Shield}
label="Administration"
shortLabel="Admin"
showLabel
pages={pages(null)}
/>,
);
const row = screen.getByRole("button", { name: "Admin" });
expect(row.getAttribute("aria-current")).toBeNull();
expect(row.className).not.toContain("bg-accent/15");
});
it("a plain click goes to the page and closes the panel; a middle or Ctrl click is the browser's", () => {
const onSelect = vi.fn();
render(
<NavRailGroup
icon={Shield}
label="Administration"
shortLabel="Admin"
showLabel={false}
pages={pages(null, onSelect)}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Administration" }));
const link = () =>
screen.getByRole("dialog", { hidden: true }).querySelector<HTMLAnchorElement>("a")!;
const leftToBrowser: boolean[] = [];
const record = (e: Event) => {
leftToBrowser.push(!e.defaultPrevented);
e.preventDefault();
};
window.addEventListener("click", record);
try {
fireEvent.click(link(), { button: 1 });
fireEvent.click(link(), { button: 0, ctrlKey: true });
expect(onSelect).not.toHaveBeenCalled();
fireEvent.click(link(), { button: 0 });
} finally {
window.removeEventListener("click", record);
}
expect(leftToBrowser).toEqual([true, true, false]);
expect(onSelect).toHaveBeenCalledTimes(1);
expect(screen.queryByRole("dialog", { hidden: true })).toBeNull();
});
});