FN-7409: Restore board search reopen button
Restore the board/list search affordance after users dismiss an active desktop search. - Keep the non-mobile open-search button available once a closed search has an empty query. - Cover empty-close and parent-cleared populated search flows in Header tests. - Add a patch changeset for the published Fusion package. Files changed: .changeset/restore-board-search-trigger.md | 7 ++++ packages/dashboard/app/components/Header.tsx | 13 +++++--- .../app/components/__tests__/Header.test.tsx | 38 ++++++++++++++++++---- 3 files changed, 46 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7409 Fusion-Task-Lineage: 9cb4ff80-8490-43be-8d18-a3f292603ecb Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/restore-board-search-trigger.md
Normal file
7
.changeset/restore-board-search-trigger.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Restore the board search button after closing the search panel.
|
||||||
|
category: fix
|
||||||
|
dev: Keeps the desktop/tablet Header search reopen affordance visible after empty-query dismissal and parent clear.
|
||||||
@@ -230,14 +230,17 @@ export function Header({
|
|||||||
// Keep mobile search open if there's an active search query
|
// Keep mobile search open if there's an active search query
|
||||||
const shouldShowMobileSearch = isMobileSearchOpen || searchQuery.length > 0;
|
const shouldShowMobileSearch = isMobileSearchOpen || searchQuery.length > 0;
|
||||||
|
|
||||||
// Non-mobile search: toggled open OR has active query, but not if explicitly closed
|
|
||||||
const shouldShowNonMobileSearch = (isNonMobileSearchOpen || searchQuery.length > 0) && !isNonMobileSearchExplicitlyClosed;
|
|
||||||
// Show toggle when search is available, NOT currently shown, NOT explicitly closed, AND query is empty
|
|
||||||
const canShowNonMobileSearchToggle = (view === "board" || view === "list") && !isMobile && onSearchChange && !isNonMobileSearchExplicitlyClosed && searchQuery.length === 0;
|
|
||||||
const canShowNonMobileSearch = (view === "board" || view === "list") && !isMobile && onSearchChange;
|
const canShowNonMobileSearch = (view === "board" || view === "list") && !isMobile && onSearchChange;
|
||||||
|
// Non-mobile search: toggled open OR has active query, but not if explicitly closed.
|
||||||
|
const shouldShowNonMobileSearch = (isNonMobileSearchOpen || searchQuery.length > 0) && !isNonMobileSearchExplicitlyClosed;
|
||||||
|
/*
|
||||||
|
FNXC:BoardSearch 2026-07-01-23:38:
|
||||||
|
Closing board/list search must suppress the populated floating panel until App clears searchQuery, then immediately restore the Open search affordance. Keep the explicit-close state out of the empty-query toggle gate so an open-but-empty dismissal cannot strand the header without a search trigger.
|
||||||
|
*/
|
||||||
|
const canShowNonMobileSearchToggle = Boolean(canShowNonMobileSearch && !shouldShowNonMobileSearch && searchQuery.length === 0);
|
||||||
const showBoardBranchFilters = view === "board";
|
const showBoardBranchFilters = view === "board";
|
||||||
|
|
||||||
// Reset explicit close flag when query becomes empty (so toggle reappears)
|
// Reset explicit close flag when query becomes empty (so active-query reopen behavior is ready for the next search).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchQuery === "") {
|
if (searchQuery === "") {
|
||||||
setIsNonMobileSearchExplicitlyClosed(false);
|
setIsNonMobileSearchExplicitlyClosed(false);
|
||||||
|
|||||||
@@ -875,6 +875,7 @@ describe("Header", () => {
|
|||||||
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
||||||
fireEvent.click(screen.getByLabelText("Close search"));
|
fireEvent.click(screen.getByLabelText("Close search"));
|
||||||
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
||||||
|
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clears search query when close button is clicked", () => {
|
it("clears search query when close button is clicked", () => {
|
||||||
@@ -885,6 +886,33 @@ describe("Header", () => {
|
|||||||
expect(onSearchChange).toHaveBeenCalledWith("");
|
expect(onSearchChange).toHaveBeenCalledWith("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("restores the board search open button after closing a populated query and parent clear", () => {
|
||||||
|
const onSearchChange = vi.fn();
|
||||||
|
const { rerender } = renderHeader({ onSearchChange, view: "board", searchQuery: "blocked" });
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue("blocked")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText("Close search"));
|
||||||
|
expect(onSearchChange).toHaveBeenCalledWith("");
|
||||||
|
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
||||||
|
|
||||||
|
mockMatchMedia("desktop");
|
||||||
|
rerender(
|
||||||
|
<Header
|
||||||
|
onOpenSettings={noop}
|
||||||
|
onOpenGitHubImport={noop}
|
||||||
|
onSearchChange={onSearchChange}
|
||||||
|
view="board"
|
||||||
|
searchQuery=""
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
||||||
|
expect(screen.getAllByRole("button", { name: "Open search" })).toHaveLength(1);
|
||||||
|
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps search open when searchQuery is non-empty", () => {
|
it("keeps search open when searchQuery is non-empty", () => {
|
||||||
renderHeader({ onSearchChange: vi.fn(), view: "board", searchQuery: "test" });
|
renderHeader({ onSearchChange: vi.fn(), view: "board", searchQuery: "test" });
|
||||||
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
||||||
@@ -933,18 +961,14 @@ describe("Header", () => {
|
|||||||
expect(wrapper!.querySelector("header.header")).not.toBeNull();
|
expect(wrapper!.querySelector("header.header")).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toggling search twice reopens the search (use close button to dismiss)", () => {
|
it("hides the open toggle while search is open and restores it after close", () => {
|
||||||
renderHeader({ onSearchChange: vi.fn(), view: "board" });
|
renderHeader({ onSearchChange: vi.fn(), view: "board" });
|
||||||
fireEvent.click(screen.getByTestId("desktop-header-search-btn"));
|
fireEvent.click(screen.getByTestId("desktop-header-search-btn"));
|
||||||
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
||||||
// Second toggle click reopens search since first close was via toggle
|
expect(screen.queryByTestId("desktop-header-search-btn")).toBeNull();
|
||||||
// (toggle always opens, use close button to dismiss)
|
|
||||||
fireEvent.click(screen.getByTestId("desktop-header-search-btn"));
|
|
||||||
// Search stays open because toggle only opens
|
|
||||||
expect(screen.getByPlaceholderText("Search tasks...")).toBeDefined();
|
|
||||||
// Use close button to dismiss
|
|
||||||
fireEvent.click(screen.getByLabelText("Close search"));
|
fireEvent.click(screen.getByLabelText("Close search"));
|
||||||
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
expect(screen.queryByPlaceholderText("Search tasks...")).toBeNull();
|
||||||
|
expect(screen.getAllByTestId("desktop-header-search-btn")).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports search toggle flow on list view", () => {
|
it("supports search toggle flow on list view", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user