diff --git a/apps/desktop/src/App.test.tsx b/apps/desktop/src/App.test.tsx index c039dfba..69ad467f 100644 --- a/apps/desktop/src/App.test.tsx +++ b/apps/desktop/src/App.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { App } from "./App"; @@ -205,6 +205,28 @@ describe("App", () => { expect(screen.getByRole("button", { name: /^Workspace$/i })).toBeTruthy(); expect(screen.getByRole("button", { name: /^Import$/i })).toBeTruthy(); expect(screen.getByRole("button", { name: /^Export$/i })).toBeTruthy(); + const primaryNav = screen.getByRole("navigation", { name: /primary rehearsal views/i }); + const activePrimaryNavButton = within(primaryNav).getByRole("button", { name: "Workspace" }); + expect(activePrimaryNavButton).toHaveAttribute("aria-current", "page"); + for (const name of ["Import", "Export"]) { + const navButton = within(primaryNav).getByRole("button", { name }); + expect(navButton).toHaveAttribute("aria-disabled", "true"); + expect(navButton).toHaveAttribute("title", "Coming soon"); + expect(navButton).not.toBeDisabled(); + } + fireEvent.click(within(primaryNav).getByRole("button", { name: "Import" })); + expect(activePrimaryNavButton).toHaveAttribute("aria-current", "page"); + const compactNav = screen.getByRole("navigation", { name: /compact rehearsal views/i }); + const activeCompactNavButton = within(compactNav).getByRole("button", { name: "Workspace compact view" }); + expect(activeCompactNavButton).toHaveAttribute("aria-current", "page"); + for (const name of ["Import", "Export"]) { + const navButton = within(compactNav).getByRole("button", { name: `${name} compact view` }); + expect(navButton).toHaveAttribute("aria-disabled", "true"); + expect(navButton).toHaveAttribute("title", "Coming soon"); + expect(navButton).not.toBeDisabled(); + } + fireEvent.click(within(compactNav).getByRole("button", { name: "Import compact view" })); + expect(activeCompactNavButton).toHaveAttribute("aria-current", "page"); expect(screen.getByText(/^Tempo$/i)).toBeTruthy(); expect(screen.getByText(/^Key$/i)).toBeTruthy(); expect(screen.getByText(/Local-first/i)).toBeTruthy(); diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 24f5fb09..e8692aee 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent, type ReactNode } from "react"; import { AudioWaveform, CircleHelp, @@ -67,6 +67,12 @@ const NAV_ITEMS = [ const BRAND_BAR_HEIGHTS = ["h-3", "h-5", "h-7", "h-4", "h-6"] as const; +/** Documented. */ +function blockInactiveNavActivation(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); +} + /** Documented. */ function progressMessage( t: ReturnType, @@ -498,8 +504,8 @@ export function App() { type="button" aria-current={active ? "page" : undefined} aria-disabled={active ? undefined : true} - disabled={!active} title={active ? undefined : "Coming soon"} + onClick={active ? undefined : blockInactiveNavActivation} className={`flex min-h-11 w-full items-center gap-3 rounded-xl px-3 text-left text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white shadow-[0_12px_30px_rgba(37,99,235,0.32)]" @@ -560,8 +566,8 @@ export function App() { aria-current={active ? "page" : undefined} aria-label={`${label} compact view`} aria-disabled={active ? undefined : true} - disabled={!active} title={active ? undefined : "Coming soon"} + onClick={active ? undefined : blockInactiveNavActivation} className={`inline-flex min-h-10 shrink-0 items-center gap-2 rounded-xl px-3 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${ active ? "bg-blue-600/70 text-white" : "cursor-not-allowed text-slate-500 opacity-70" }`}