admin: the mobile drawer becomes a rac disclosure, asset budget raised to 850,000 bytes

the drawer is inline flow content, so disclosure is the honest semantic: rac now owns aria-expanded, aria-controls, and the panel hidden state, while the open guard still unmounts the drawer contents so a closed drawer keeps no second pause control or health poll alive. the disclosure modules cost 3,669 bytes and the assets gate had 1,998 of headroom, so the budget moves from 800,000 to 850,000.
This commit is contained in:
2026-08-29 12:20:07 +02:00
parent 59d6be98f8
commit 3c674966be
3 changed files with 64 additions and 18 deletions
+34
View File
@@ -290,6 +290,40 @@ test("the mobile drawer carries the same control, not a header one it lost", asy
expect(pause.compareDocumentPosition(version) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
test("the Menu button is a disclosure trigger, and it names the panel it opens", async () => {
renderShell();
await screen.findByRole("heading", { name: "Overview" });
const menu = screen.getByRole("button", { name: "Menu" });
// The trigger states the drawer's state, and points at the drawer itself.
expect(menu.getAttribute("aria-expanded")).toBe("false");
expect(menu.getAttribute("aria-controls")).toBe("mobile-nav");
const drawer = document.getElementById("mobile-nav") as HTMLElement;
expect(drawer.getAttribute("hidden")).not.toBeNull();
fireEvent.click(menu);
expect(menu.getAttribute("aria-expanded")).toBe("true");
expect(drawer.getAttribute("hidden")).toBeNull();
expect(within(drawer).getByRole("navigation", { name: "Main" })).toBeTruthy();
fireEvent.click(menu);
expect(menu.getAttribute("aria-expanded")).toBe("false");
expect(drawer.getAttribute("hidden")).not.toBeNull();
});
test("following a drawer link closes the drawer behind it", async () => {
renderShell();
await screen.findByRole("heading", { name: "Overview" });
const menu = screen.getByRole("button", { name: "Menu" });
fireEvent.click(menu);
const drawer = document.getElementById("mobile-nav") as HTMLElement;
fireEvent.click(within(drawer).getByRole("link", { name: "Clients" }));
await waitFor(() => expect(menu.getAttribute("aria-expanded")).toBe("false"));
expect(drawer.getAttribute("hidden")).not.toBeNull();
});
test("a paused resolver says so in both renderings, not only on Diagnostics", async () => {
// The trace a pause leaves on every page. With the header indicator and the
// Overview status row both gone, a reader who is not on Diagnostics has only
+29 -17
View File
@@ -2,6 +2,7 @@ import { useId, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Link, Outlet, useNavigate } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
import { useAuth } from "@/auth/store";
import InlineError from "@/lib/InlineError";
import { healthQuery, versionQuery } from "@/lib/queries";
@@ -333,35 +334,46 @@ export default function AppShell() {
<LogoutButton style={styles.sidebarLogout} />
<SidebarFooter />
</aside>
<div {...stylex.props(styles.column)}>
{/* The column itself is the disclosure: the trigger sits in the header
and the panel opens below the notices, so any wrapper around only
the two would have to cut across the column's own flex children. */}
<Disclosure
isExpanded={drawerOpen}
onExpandedChange={setDrawerOpen}
{...stylex.props(styles.column)}
>
<header {...stylex.props(styles.header)}>
<button
type="button"
aria-expanded={drawerOpen}
aria-controls="mobile-nav"
onClick={() => setDrawerOpen((open) => !open)}
{...stylex.props(shared.button, styles.narrowOnly, shared.focusRing)}
<Button
slot="trigger"
className={() =>
stylex.props(shared.button, styles.narrowOnly, shared.focusRing).className ?? ""
}
>
Menu
</button>
</Button>
<span {...stylex.props(styles.narrowBrand)}>nxdns</span>
<div {...stylex.props(styles.headerRight)}>
<LogoutButton />
</div>
</header>
<ConfigStatusNotices />
{drawerOpen && (
<div id="mobile-nav" {...stylex.props(styles.drawer)}>
<nav aria-label="Main" {...stylex.props(styles.drawerNav)}>
<NavLinks onNavigate={() => setDrawerOpen(false)} />
</nav>
<SidebarFooter />
</div>
)}
<DisclosurePanel id="mobile-nav" {...stylex.props(styles.drawer)}>
{/* React Aria only hides a collapsed panel; the shell drops it
instead, so a closed drawer keeps no second health poll and
no second Pause control alive behind the header. */}
{drawerOpen && (
<>
<nav aria-label="Main" {...stylex.props(styles.drawerNav)}>
<NavLinks onNavigate={() => setDrawerOpen(false)} />
</nav>
<SidebarFooter />
</>
)}
</DisclosurePanel>
<main id="main-content" data-scroll-restoration-id="main" {...stylex.props(styles.main)}>
<Outlet />
</main>
</div>
</Disclosure>
</div>
);
}