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:
@@ -14,7 +14,7 @@ import { readdirSync, statSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const BUDGET_BYTES = 800_000;
|
||||
const BUDGET_BYTES = 850_000;
|
||||
|
||||
const distDir = join(dirname(dirname(fileURLToPath(import.meta.url))), "dist", "assets");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
<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 && (
|
||||
<div id="mobile-nav" {...stylex.props(styles.drawer)}>
|
||||
<>
|
||||
<nav aria-label="Main" {...stylex.props(styles.drawerNav)}>
|
||||
<NavLinks onNavigate={() => setDrawerOpen(false)} />
|
||||
</nav>
|
||||
<SidebarFooter />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
<main id="main-content" data-scroll-restoration-id="main" {...stylex.props(styles.main)}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user