feat(FN-1291): add board/list toggle sub-buttons to MobileNavBar

- Add board and list view toggle sub-buttons to MobileNavBar component
- Add CSS styles for toggle sub-buttons with active/hover states
- Update MobileNavBar tests to cover new toggle sub-buttons
- Update mobile feature access regression tests for new nav items
This commit is contained in:
gsxdsm
2026-04-08 20:17:07 -07:00
parent 47a035fde1
commit a9bbb25fd0
4 changed files with 112 additions and 32 deletions

View File

@@ -91,26 +91,23 @@ describe("Mobile Feature Access Regression Guard", () => {
it("list view is accessible via mobile nav bar", () => {
const props = createDefaultMobileNavProps();
const { rerender } = render(<MobileNavBar {...props} view="board" />);
render(<MobileNavBar {...props} view="board" />);
const firstTab = screen.getByTestId("mobile-nav-tab-board");
expect(firstTab.textContent).toMatch(/Board|List/);
const listTab = screen.getByTestId("mobile-nav-tab-list");
expect(listTab.textContent).toContain("List");
fireEvent.click(firstTab);
expect(props.onChangeView).toHaveBeenCalledWith("board");
rerender(<MobileNavBar {...props} view="list" />);
expect(screen.getByTestId("mobile-nav-tab-board").textContent).toContain("List");
fireEvent.click(listTab);
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("board view is accessible via mobile nav bar", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} view="board" />);
const firstTab = screen.getByTestId("mobile-nav-tab-board");
expect(firstTab.textContent).toContain("Board");
const boardTab = screen.getByTestId("mobile-nav-tab-board");
expect(boardTab.textContent).toContain("Board");
fireEvent.click(firstTab);
fireEvent.click(boardTab);
expect(props.onChangeView).toHaveBeenCalledWith("board");
});

View File

@@ -7,6 +7,7 @@ import {
GitBranch,
Grid3X3,
LayoutGrid,
List,
Lightbulb,
Mail,
MoreHorizontal,
@@ -123,8 +124,6 @@ export function MobileNavBar({
return null;
}
const boardTabLabel = view === "list" ? "List" : "Board";
const isBoardOrList = view === "board" || view === "list";
const planningHandler = activePlanningSessionCount > 0 && onResumePlanning ? onResumePlanning : onOpenPlanning;
return (
@@ -134,17 +133,30 @@ export function MobileNavBar({
role="tablist"
aria-label="Primary navigation"
>
<button
type="button"
className={`mobile-nav-tab${isBoardOrList ? " mobile-nav-tab--active" : ""}`}
data-testid="mobile-nav-tab-board"
role="tab"
aria-selected={isBoardOrList}
onClick={() => onChangeView("board")}
>
<LayoutGrid />
<span className="mobile-nav-tab-label">{boardTabLabel}</span>
</button>
<div className="mobile-nav-view-toggle">
<button
type="button"
className={`mobile-nav-view-toggle-btn${view === "board" ? " mobile-nav-view-toggle-btn--active" : ""}`}
data-testid="mobile-nav-tab-board"
role="tab"
aria-selected={view === "board"}
onClick={() => onChangeView("board")}
>
<LayoutGrid />
<span>Board</span>
</button>
<button
type="button"
className={`mobile-nav-view-toggle-btn${view === "list" ? " mobile-nav-view-toggle-btn--active" : ""}`}
data-testid="mobile-nav-tab-list"
role="tab"
aria-selected={view === "list"}
onClick={() => onChangeView("list")}
>
<List />
<span>List</span>
</button>
</div>
<button
type="button"

View File

@@ -50,29 +50,54 @@ describe("MobileNavBar", () => {
mockViewport("mobile");
});
it("renders four tab buttons", () => {
it("renders five tab buttons (board + list + agents + activity + more)", () => {
render(<MobileNavBar {...createDefaultProps()} />);
expect(screen.getByTestId("mobile-nav-tab-board")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-agents")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-activity")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-more")).toBeDefined();
});
it("active tab is highlighted", () => {
it("active tab is highlighted for agents", () => {
render(<MobileNavBar {...createDefaultProps()} view="agents" />);
expect(screen.getByTestId("mobile-nav-tab-agents").className).toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-board").className).not.toContain("mobile-nav-tab--active");
});
it("board/list label changes based on current view", () => {
it("board sub-button calls onChangeView with 'board'", () => {
const props = createDefaultProps();
const { rerender } = render(<MobileNavBar {...props} view="board" />);
expect(screen.getByText("Board")).toBeDefined();
render(<MobileNavBar {...props} view="list" />);
rerender(<MobileNavBar {...props} view="list" />);
expect(screen.getByText("List")).toBeDefined();
fireEvent.click(screen.getByTestId("mobile-nav-tab-board"));
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
it("list sub-button calls onChangeView with 'list'", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="board" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-list"));
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("board sub-button is active when view is 'board'", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" />);
expect(screen.getByTestId("mobile-nav-tab-board").className).toContain("mobile-nav-view-toggle-btn--active");
expect(screen.getByTestId("mobile-nav-tab-list").className).not.toContain("mobile-nav-view-toggle-btn--active");
});
it("list sub-button is active when view is 'list'", () => {
render(<MobileNavBar {...createDefaultProps()} view="list" />);
expect(screen.getByTestId("mobile-nav-tab-list").className).toContain("mobile-nav-view-toggle-btn--active");
expect(screen.getByTestId("mobile-nav-tab-board").className).not.toContain("mobile-nav-view-toggle-btn--active");
});
it("board sub-button is not active when view is 'agents'", () => {
render(<MobileNavBar {...createDefaultProps()} view="agents" />);
expect(screen.getByTestId("mobile-nav-tab-board").className).not.toContain("mobile-nav-view-toggle-btn--active");
expect(screen.getByTestId("mobile-nav-tab-list").className).not.toContain("mobile-nav-view-toggle-btn--active");
});
it("shows activity badge with combined planning + mailbox count", () => {

View File

@@ -25863,6 +25863,52 @@ body[data-color-theme="terminal"][data-theme="light"]::before {
-webkit-tap-highlight-color: transparent;
}
/* Board/List view toggle — two sub-buttons sharing first tab slot */
.mobile-nav-view-toggle {
flex: 1;
display: flex;
align-items: stretch;
justify-content: center;
min-height: 56px;
}
.mobile-nav-view-toggle-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
padding: 6px 0;
background: none;
border: none;
color: var(--text-muted);
font-size: 10px;
line-height: 1.2;
cursor: pointer;
transition: color 0.15s ease;
-webkit-tap-highlight-color: transparent;
}
.mobile-nav-view-toggle-btn:active {
color: var(--text-primary);
}
.mobile-nav-view-toggle-btn--active {
color: var(--accent);
}
.mobile-nav-view-toggle-btn svg {
width: 18px;
height: 18px;
}
.mobile-nav-view-toggle-btn span {
font-size: 10px;
line-height: 1.2;
white-space: nowrap;
}
.mobile-nav-tab:active {
color: var(--text-primary);
}