From 45a9ceae25060aa37e5a7b662c5820950426c75b Mon Sep 17 00:00:00 2001 From: npeter83 Date: Thu, 8 Oct 2026 02:52:32 +0200 Subject: [PATCH] fix(ui): the header search has room on a tablet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On touch the 44 px key cap and Go filled a 44 px pill edge to edge and the placeholder was cut. The key cap is left out on touch (⌘K still works), the pill grows to 52 px so a ring shows round Go, the box's floor counts the controls beside the text, and Go drops to its icon by the column's width, not the screen's. --- frontend/src/components/PaletteLauncher.tsx | 5 +-- .../src/components/SearchBar.dom.test.tsx | 31 +++++++++++++++++-- frontend/src/components/SearchBar.tsx | 15 ++++++--- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/PaletteLauncher.tsx b/frontend/src/components/PaletteLauncher.tsx index 7de92f69..b486e42a 100644 --- a/frontend/src/components/PaletteLauncher.tsx +++ b/frontend/src/components/PaletteLauncher.tsx @@ -28,7 +28,8 @@ function useCap(): string { // words matched nothing. /** The key cap at the end of a search field. A button, so the pointer and a screen reader reach it - * too; left out of a phone's narrow header, where there is no keyboard to press it on. */ + * too; left out of a phone's narrow header and of every touch screen, where there is (usually) no + * keyboard to press it on — on a tablet it was a 44 px box crowding the field. ⌘K still works. */ export function PaletteKeycap() { const { t } = useTranslation(); const cap = useCap(); @@ -37,7 +38,7 @@ export function PaletteKeycap() { onClick={openPalette} aria-label={`${t("palette.open")} (${cap})`} title={`${t("palette.open")} (${cap})`} - className="inline-flex h-6 shrink-0 items-center justify-center rounded-md border border-border px-1.5 font-mono text-3xs touch-target text-muted transition hover:border-accent/50 hover:text-fg @max-[640px]/appmain:hidden" + className="inline-flex h-6 shrink-0 items-center justify-center rounded-md border border-border px-1.5 font-mono text-3xs touch-target text-muted transition hover:border-accent/50 hover:text-fg pointer-coarse:hidden @max-[640px]/appmain:hidden" > {cap} diff --git a/frontend/src/components/SearchBar.dom.test.tsx b/frontend/src/components/SearchBar.dom.test.tsx index 0021719f..3b734d82 100644 --- a/frontend/src/components/SearchBar.dom.test.tsx +++ b/frontend/src/components/SearchBar.dom.test.tsx @@ -4,8 +4,8 @@ // collapse — the box must WRAP to its own line (not shrink the input to ~0) when the content column // (@container/appmain) is narrow. jsdom does no layout, but the class presence is the contract these // rules depend on, so a refactor that drops them fails here instead of only on a hand-resized window. -import { describe, expect, it, vi } from "vitest"; -import { render } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, render } from "@testing-library/react"; import SearchBar from "./SearchBar"; vi.mock("react-i18next", async (importOriginal) => { @@ -13,14 +13,39 @@ vi.mock("react-i18next", async (importOriginal) => { return { ...actual, useTranslation: () => ({ t: (key: string) => key }) }; }); +afterEach(cleanup); + describe("SearchBar responsive wrapper", () => { it("carries the container-query classes that make it wrap instead of collapsing", () => { const { container } = render( {}} placeholder="Search…" />); const cls = (container.firstElementChild as HTMLElement).className; // A usable min-width floor once the column has room → the box wraps via flex-wrap, never ~0: - expect(cls).toContain("@min-[420px]/appmain:min-w-[15rem]"); + expect(cls).toContain("@min-[420px]/appmain:min-w-[18rem]"); // On a narrow column it takes a full line of its own and sorts last (small pills share the row above): expect(cls).toContain("@max-[640px]/appmain:basis-full"); expect(cls).toContain("@max-[640px]/appmain:order-last"); }); }); + +// On a tablet the 44 px key cap and Go sat on the edge of a 44 px pill and crowded the text out. +describe("SearchBar on a touch screen", () => { + it("drops the key cap, and grows the pill past the 44 px Go so a ring shows round it", () => { + const { container, getByRole } = render( + {}} placeholder="Search…" onGo={() => {}} />, + ); + expect(getByRole("button", { name: /palette.open/ }).className).toContain( + "pointer-coarse:hidden", + ); + const pill = container.querySelector("input")!.closest("[class*='h-10']")!; + expect(pill.className).toContain("pointer-coarse:h-13"); + }); + + it("shortens Go to its icon by the column's width, not the screen's", () => { + const { getByText } = render( + {}} onGo={() => {}} goIcon={} goLabel="Go far" />, + ); + const label = getByText("Go far"); + expect(label.className).toContain("@min-[900px]/appmain:inline"); + expect(label.className).not.toMatch(/(^|\s)md:inline/); + }); +}); diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index f63765ac..c617b389 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -72,14 +72,18 @@ export default function SearchBar({ // overflow on a truly tiny column; once the column has room (≥420px) a `min-w-[15rem]` floor makes // the box WRAP to its own line via the header's flex-wrap instead of shrinking the input to ~0; on // a narrow column (≤640px) it also takes a full line of its own (basis-full) and sorts LAST, so the - // small pills (module name + Plex scope toggle / feed sync chip) share the line above it. + // small pills (module name + Plex scope toggle / feed sync chip) share the line above it. The floor + // is the input's room plus what sits beside it (the icon, the key cap, Go): at 15rem the box kept + // only ~8rem for the text and a tablet cut the placeholder ("Search your subscriptior"). return ( -
- {/* `free` + its own padding: the Go button sits 6px in from the pill's end, not a field's 12. */} +
+ {/* `free` + its own padding: the Go button sits 6px in from the pill's end, not a field's 12. + On touch the pill grows past the 44 px Go so a ring of the pill shows round it — a 44 Go in a + 44 pill sat on its edge. */}
{leading ?? } @@ -116,7 +120,8 @@ export default function SearchBar({ title={goTitle ?? goLabel ?? t("header.goTip")} > {goIcon} - + {/* By the column, not the screen: a tablet's 768–1024 px window has a tight column. */} + {goLabel ?? t("header.go")}