fix(ui): phone Explorer always renders, closed tray leaves tab order

- Narrow viewports pass desktopMode="full" so a desktop-collapsed
  Explorer still opens from the phone toggle.
- The closed nav tray is inert, so its links are not focusable.
- The phone toggle's label follows its state (Show/Hide ...).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VjcGdgibKqU8DAkS1hrZhg
This commit is contained in:
Claude committed 2026-11-03 04:32:48 +00:00
1 parent ee7a7eff9d
commit 1ac376521e
2 files changed
+16 -9

No files matched your search

+13 -9
View File
@@ -71,14 +71,17 @@ interface DiscoveryPlacementContextValue {
}
// Accessible names for the mobile button that opens the Explorer panel.
const MOBILE_EXPLORER_LABEL: Partial<Record<ExplorerSurface, string>> = {
"for-you": "Show inbox filters",
project: "Show project tree",
specs: "Show library",
settings: "Show settings sections",
topology: "Show rig tree",
// Accessible names for the mobile button that toggles the Explorer panel.
const MOBILE_EXPLORER_NOUN: Partial<Record<ExplorerSurface, string>> = {
"for-you": "inbox filters",
project: "project tree",
specs: "library",
settings: "settings sections",
topology: "rig tree",
};
function mobileExplorerLabel(surface: ExplorerSurface, open: boolean): string {
return `${open ? "Hide" : "Show"} ${MOBILE_EXPLORER_NOUN[surface] ?? "side panel"}`;
}
export const DrawerSelectionContext = createContext<DrawerSelectionContextValue>({
selection: null,
@@ -357,7 +360,7 @@ function AppShellInner({ children }: AppShellProps) {
<button
type="button"
data-testid="mobile-explorer-toggle"
aria-label={MOBILE_EXPLORER_LABEL[surface] ?? "Show side panel"}
aria-label={mobileExplorerLabel(surface, mobileExplorerOpen)}
aria-expanded={mobileExplorerOpen}
onClick={() => {
setExplorerOpen(false);
@@ -403,6 +406,7 @@ function AppShellInner({ children }: AppShellProps) {
)}
<div
data-testid="mobile-rail-tray"
inert={!explorerOpen}
className={cn(
"fixed top-12 left-0 bottom-0 z-30 bg-background transition-transform duration-200 ease-tactical lg:hidden",
"w-72 max-w-[80vw] flex flex-col pb-[calc(3.5rem+env(safe-area-inset-bottom))]",
@@ -435,7 +439,7 @@ function AppShellInner({ children }: AppShellProps) {
onClose={() => setMobileExplorerOpen(false)}
selection={selectionState}
onSelect={setSelection}
desktopMode={desktopExplorerOpen ? "full" : "hidden"}
desktopMode={desktopExplorerOpen || !isWideLayout ? "full" : "hidden"}
surface={surface}
onDesktopToggle={() => setDesktopExplorerOpen((open) => !open)}
overlayMode={isTopologyOverlay ? "overlay" : "opaque"}
+3
View File
@@ -287,6 +287,9 @@ describe("AppShell — Phase 2 chrome", () => {
fireEvent.click(panelToggle);
expect(tray().className).toContain("-translate-x-full");
expect(explorer().className).not.toContain("-translate-x-full");
expect(panelToggle.getAttribute("aria-label")).toBe("Hide inbox filters");
// A closed tray leaves the tab order.
expect(tray().hasAttribute("inert")).toBe(true);
});
// C06 sidebar: nav items keep a >=44px tap target on mobile