feat(nexus): add TopStrip composite for layout overhaul (phase 8)
48px sticky top strip per docs/specs/2026-04-11-nexus-layout-overhaul.md §4.2. Composes ModeBreadcrumb (left) + CmdKButton and GlobalMicButton (right) inside a <header aria-label="Top bar"> landmark. Completes the frame component set for Phase 8. The next task (task 6) rewrites Layout.tsx to mount IconRail + TopStrip as the new global chrome and delete the old sidebar/ChatPanel/PropertiesPanel/ BreadcrumbBar combination. Part of Phase 8 of the Nexus layout overhaul (task 5 of 7). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bfcdf1f598
commit
c521ae4403
2 changed files with 102 additions and 0 deletions
76
ui/src/components/frame/TopStrip.test.tsx
Normal file
76
ui/src/components/frame/TopStrip.test.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { act } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { MemoryRouter } from "@/lib/router";
|
||||||
|
import { TopStrip } from "./TopStrip";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
// Same stub as IconRail / ModeBreadcrumb tests — @/lib/router's Link
|
||||||
|
// (and useLocation consumers) trigger CompanyContext resolution.
|
||||||
|
vi.mock("@/context/CompanyContext", () => ({
|
||||||
|
useCompany: () => ({
|
||||||
|
companies: [],
|
||||||
|
selectedCompany: null,
|
||||||
|
selectedCompanyId: null,
|
||||||
|
setSelectedCompanyId: () => {},
|
||||||
|
selectionSource: null,
|
||||||
|
loading: false,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("TopStrip", () => {
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
let root: ReturnType<typeof createRoot> | null = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
container = document.createElement("div");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
root = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (root) {
|
||||||
|
act(() => { root!.unmount(); });
|
||||||
|
root = null;
|
||||||
|
}
|
||||||
|
if (container.parentNode) container.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
function render(pathname: string) {
|
||||||
|
root = createRoot(container);
|
||||||
|
act(() => {
|
||||||
|
root!.render(
|
||||||
|
<MemoryRouter initialEntries={[pathname]}>
|
||||||
|
<TopStrip />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("renders the ModeBreadcrumb with derived segments", () => {
|
||||||
|
render("/NEX/assistant");
|
||||||
|
const segment = container.querySelector("[data-testid='mode-breadcrumb-segment']");
|
||||||
|
expect(segment?.textContent?.trim()).toBe("ASSISTANT");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the CmdK button", () => {
|
||||||
|
render("/NEX/assistant");
|
||||||
|
expect(container.querySelector("button[aria-label='Open command palette']")).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the global mic button", () => {
|
||||||
|
render("/NEX/assistant");
|
||||||
|
expect(container.querySelector("button[aria-label='Voice']")).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is wrapped in a header element for landmark semantics", () => {
|
||||||
|
render("/NEX/assistant");
|
||||||
|
const header = container.querySelector("header");
|
||||||
|
expect(header).not.toBeNull();
|
||||||
|
expect(header?.getAttribute("aria-label")).toBe("Top bar");
|
||||||
|
});
|
||||||
|
});
|
||||||
26
ui/src/components/frame/TopStrip.tsx
Normal file
26
ui/src/components/frame/TopStrip.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { CmdKButton } from "./CmdKButton";
|
||||||
|
import { GlobalMicButton } from "./GlobalMicButton";
|
||||||
|
import { ModeBreadcrumb } from "./ModeBreadcrumb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TopStrip — the 48px top bar composed into the global frame in Layout.tsx
|
||||||
|
* (Task 6). Renders the mode breadcrumb on the left and the ⌘K + global
|
||||||
|
* mic buttons on the right. Charcoal bottom border, pure black background,
|
||||||
|
* sticky at the top of the main column.
|
||||||
|
*
|
||||||
|
* Per docs/specs/2026-04-11-nexus-layout-overhaul.md §4.2.
|
||||||
|
*/
|
||||||
|
export function TopStrip() {
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
aria-label="Top bar"
|
||||||
|
className="sticky top-0 z-30 flex h-12 shrink-0 items-center justify-between border-b border-border bg-background px-6"
|
||||||
|
>
|
||||||
|
<ModeBreadcrumb />
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<CmdKButton />
|
||||||
|
<GlobalMicButton state="idle" />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue