FN-6846: move header search after workflow slot
Move the desktop and tablet search toggle to follow the workflow portal slot.\n\n- Reorder the non-mobile header search button after the workflow portal mount point.\n- Preserve the mobile search trigger position and behavior.\n- Add header tests covering empty, populated, tablet, and mobile slot behavior.\n\nFiles changed:\n packages/dashboard/app/components/Header.tsx | 29 ++++++++-------\n .../app/components/__tests__/Header.test.tsx | 42 ++++++++++++++++++++--\n 2 files changed, 56 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-6846 Fusion-Task-Lineage: 294100b1-76ae-4c46-b4b3-e4c83e4a55f8
This commit is contained in:
@@ -928,19 +928,6 @@ export function Header({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Desktop/Tablet Search Toggle - show icon when search is available but hidden */}
|
||||
{canShowNonMobileSearchToggle && (
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={handleNonMobileSearchToggle}
|
||||
title={t("header.openSearch", "Open search")}
|
||||
aria-label={t("header.openSearch", "Open search")}
|
||||
data-testid="desktop-header-search-btn"
|
||||
>
|
||||
<Search size={16} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Usage button on mobile when mobile bottom nav is active */}
|
||||
{isMobile && hideFullNav && onOpenUsage && (
|
||||
<button
|
||||
@@ -961,6 +948,22 @@ export function Header({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/**
|
||||
* FNXC:Header 2026-06-21-00:00:
|
||||
* Desktop and tablet header search must render after the workflow portal slot so a populated WorkflowSwitcher appears left of the search icon while preserving the mobile search trigger's existing position and behavior.
|
||||
*/}
|
||||
{canShowNonMobileSearchToggle && (
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={handleNonMobileSearchToggle}
|
||||
title={t("header.openSearch", "Open search")}
|
||||
aria-label={t("header.openSearch", "Open search")}
|
||||
data-testid="desktop-header-search-btn"
|
||||
>
|
||||
<Search size={16} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* View Toggle - always inline, even on mobile */}
|
||||
{!hideFullNav && !hideHeaderViewNav && onChangeView && (
|
||||
<div className="view-toggle">
|
||||
|
||||
@@ -1076,6 +1076,39 @@ describe("Header", () => {
|
||||
expect(screen.getByTestId("desktop-header-search-btn")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders the desktop search toggle after the empty workflow portal slot", () => {
|
||||
renderHeader({ onSearchChange: vi.fn(), onChangeView: noop, view: "board", leftSidebarNavActive: true }, "desktop");
|
||||
const workflowSlot = screen.getByTestId("header-workflow-slot");
|
||||
const searchToggle = screen.getByTestId("desktop-header-search-btn");
|
||||
|
||||
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||
expect(workflowSlot.compareDocumentPosition(searchToggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps the desktop search toggle after a populated workflow portal slot", () => {
|
||||
renderHeader({ onSearchChange: vi.fn(), onChangeView: noop, view: "board", leftSidebarNavActive: true }, "desktop");
|
||||
const workflowSlot = screen.getByTestId("header-workflow-slot");
|
||||
const workflowSwitcher = document.createElement("button");
|
||||
workflowSwitcher.type = "button";
|
||||
workflowSwitcher.dataset.testid = "mock-workflow-switcher";
|
||||
workflowSwitcher.textContent = "Coding workflow";
|
||||
workflowSlot.appendChild(workflowSwitcher);
|
||||
const searchToggle = screen.getByTestId("desktop-header-search-btn");
|
||||
|
||||
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||
expect(workflowSlot.compareDocumentPosition(searchToggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
expect(workflowSwitcher.compareDocumentPosition(searchToggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps the tablet search toggle after the workflow portal slot", () => {
|
||||
renderHeader({ onSearchChange: vi.fn(), onChangeView: noop, view: "board", leftSidebarNavActive: true }, "tablet");
|
||||
const workflowSlot = screen.getByTestId("header-workflow-slot");
|
||||
const searchToggle = screen.getByTestId("desktop-header-search-btn");
|
||||
|
||||
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||
expect(workflowSlot.compareDocumentPosition(searchToggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render search toggle when view is 'agents'", () => {
|
||||
renderHeader({ onSearchChange: vi.fn(), view: "agents" });
|
||||
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
|
||||
@@ -1371,9 +1404,14 @@ describe("Header", () => {
|
||||
it("can open mobile search when mobileNavEnabled is true", () => {
|
||||
renderHeader({ view: "board", searchQuery: "", onSearchChange: vi.fn(), onChangeView: noop }, "mobile");
|
||||
// Should show the trigger button
|
||||
expect(screen.getByTestId("mobile-header-search-btn")).toBeDefined();
|
||||
// Expanded search should not be visible initially
|
||||
const mobileSearchTrigger = screen.getByTestId("mobile-header-search-btn");
|
||||
expect(mobileSearchTrigger).toBeDefined();
|
||||
expect(screen.queryByTestId("header-workflow-slot")).toBeNull();
|
||||
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
|
||||
// Expanded search should not be visible initially, then opens from the unchanged mobile trigger.
|
||||
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
||||
fireEvent.click(mobileSearchTrigger);
|
||||
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
||||
});
|
||||
|
||||
it("closes mobile search and clears query when close button clicked with mobileNavEnabled", () => {
|
||||
|
||||
Reference in New Issue
Block a user