feat(FN-707): hide project selector on mobile and add to overflow menu
- Hide ProjectSelector on mobile via hidden className and CSS media query - Add project selector as menu item in mobile overflow menu - Add CSS safety net (max-width: 767px) to ensure selector stays hidden - Add comprehensive tests for mobile project selector visibility behavior
This commit is contained in:
@@ -171,8 +171,8 @@ export function Header({
|
||||
<img src="/logo.svg" alt="Fusion logo" className="header-logo" width={24} height={24} />
|
||||
<h1 className="logo">Fusion</h1>
|
||||
|
||||
{/* Project Selector - shown when 2+ projects, placed next to logo/title */}
|
||||
{projects.length > 1 && (
|
||||
{/* Project Selector - shown when 2+ projects on desktop only */}
|
||||
{!isMobile && projects.length > 1 && (
|
||||
<div className="header-project-selector">
|
||||
<ProjectSelector
|
||||
projects={projects}
|
||||
@@ -185,8 +185,8 @@ export function Header({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Back to All Projects button when viewing a specific project */}
|
||||
{currentProject && onViewAllProjects && (
|
||||
{/* Back to All Projects button when viewing a specific project (desktop only) */}
|
||||
{!isMobile && currentProject && onViewAllProjects && (
|
||||
<button
|
||||
className="header-back-button"
|
||||
onClick={onViewAllProjects}
|
||||
@@ -468,6 +468,18 @@ export function Header({
|
||||
role="menu"
|
||||
aria-label="Additional header actions"
|
||||
>
|
||||
{/* Switch Project - in overflow on mobile */}
|
||||
{projects.length > 1 && onViewAllProjects && (
|
||||
<button
|
||||
className="mobile-overflow-item"
|
||||
onClick={() => handleOverflowAction(onViewAllProjects)}
|
||||
role="menuitem"
|
||||
data-testid="overflow-project-selector-btn"
|
||||
>
|
||||
<Folder size={16} />
|
||||
<span>{currentProject ? currentProject.name : "Switch Project"}</span>
|
||||
</button>
|
||||
)}
|
||||
{/* Files - in overflow on mobile */}
|
||||
{onOpenFiles && (
|
||||
<button
|
||||
|
||||
@@ -544,6 +544,94 @@ describe("Header", () => {
|
||||
expect(onSearchChange).toHaveBeenCalledWith("test");
|
||||
});
|
||||
|
||||
it("does not render project selector trigger on mobile", () => {
|
||||
const projects = [
|
||||
{ id: "proj_1", name: "Project One", path: "/path/1", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
{ id: "proj_2", name: "Project Two", path: "/path/2", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
];
|
||||
render(
|
||||
<Header
|
||||
projects={projects}
|
||||
currentProject={projects[0]}
|
||||
onSelectProject={vi.fn()}
|
||||
onViewAllProjects={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByTestId("project-selector-trigger")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not render back-to-projects button on mobile", () => {
|
||||
const projects = [
|
||||
{ id: "proj_1", name: "Project One", path: "/path/1", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
{ id: "proj_2", name: "Project Two", path: "/path/2", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
];
|
||||
render(
|
||||
<Header
|
||||
projects={projects}
|
||||
currentProject={projects[0]}
|
||||
onViewAllProjects={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByTestId("back-to-projects-btn")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows switch project item in overflow menu on mobile when multiple projects", () => {
|
||||
const projects = [
|
||||
{ id: "proj_1", name: "Project One", path: "/path/1", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
{ id: "proj_2", name: "Project Two", path: "/path/2", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
];
|
||||
render(
|
||||
<Header
|
||||
projects={projects}
|
||||
currentProject={projects[0]}
|
||||
onSelectProject={vi.fn()}
|
||||
onViewAllProjects={vi.fn()}
|
||||
onOpenSettings={vi.fn()}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
const btn = screen.getByTestId("overflow-project-selector-btn");
|
||||
expect(btn).toBeDefined();
|
||||
// Should show the current project name
|
||||
expect(btn.textContent).toContain("Project One");
|
||||
});
|
||||
|
||||
it("overflow project selector calls onViewAllProjects when clicked", () => {
|
||||
const projects = [
|
||||
{ id: "proj_1", name: "Project One", path: "/path/1", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
{ id: "proj_2", name: "Project Two", path: "/path/2", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
];
|
||||
const onViewAllProjects = vi.fn();
|
||||
render(
|
||||
<Header
|
||||
projects={projects}
|
||||
currentProject={projects[0]}
|
||||
onSelectProject={vi.fn()}
|
||||
onViewAllProjects={onViewAllProjects}
|
||||
onOpenSettings={vi.fn()}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
fireEvent.click(screen.getByTestId("overflow-project-selector-btn"));
|
||||
expect(onViewAllProjects).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not show switch project in overflow menu with single project", () => {
|
||||
const projects = [
|
||||
{ id: "proj_1", name: "Project One", path: "/path/1", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" },
|
||||
];
|
||||
render(
|
||||
<Header
|
||||
projects={projects}
|
||||
currentProject={projects[0]}
|
||||
onViewAllProjects={vi.fn()}
|
||||
onOpenSettings={vi.fn()}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
expect(screen.queryByTestId("overflow-project-selector-btn")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows agents button in overflow menu on mobile when onOpenAgents provided", () => {
|
||||
render(<Header onOpenSettings={vi.fn()} onOpenAgents={vi.fn()} />);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
|
||||
@@ -4439,6 +4439,12 @@ body {
|
||||
padding: 12px 12px;
|
||||
}
|
||||
|
||||
/* Hide project selector and back button on mobile (belt-and-suspenders with conditional rendering) */
|
||||
.header-project-selector,
|
||||
.header-back-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Mobile header: collapsible search and overflow menu */
|
||||
.mobile-search-trigger {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user