diff --git a/packages/website/e2e/plugins.spec.ts b/packages/website/e2e/plugins.spec.ts index d2066bca61..92a48d0c84 100644 --- a/packages/website/e2e/plugins.spec.ts +++ b/packages/website/e2e/plugins.spec.ts @@ -104,6 +104,22 @@ test("keeps the directory's ranking window when searching", async ({ page }) => ); }); +test("clears the search with the clear button", async ({ page }) => { + await page.goto("/plugins/all"); + const searchbox = page.getByRole("searchbox", { name: "Search plugins" }); + const clear = page.getByRole("button", { name: "Clear search" }); + await expect(clear).toHaveCount(0); + + await searchPlugins(page, "graphite"); + await expect(page).toHaveURL(/\/plugins\/all\?q=graphite$/); + await clear.click(); + await expect(page).toHaveURL(/\/plugins\/all$/); + await expect(searchbox).toHaveValue(""); + await expect(searchbox).toBeFocused(); + await expect(clear).toHaveCount(0); + await expect(page.getByRole("heading", { level: 1, name: /^All plugins/ })).toBeVisible(); +}); + test("replaces history while typing a search", async ({ page }) => { await page.goto("/"); await page.goto("/plugins/all"); diff --git a/packages/website/src/plugins/plugin-search.tsx b/packages/website/src/plugins/plugin-search.tsx index a6bd398a19..042e5e81e5 100644 --- a/packages/website/src/plugins/plugin-search.tsx +++ b/packages/website/src/plugins/plugin-search.tsx @@ -1,18 +1,20 @@ import { useNavigate } from "@tanstack/react-router"; -import { Search } from "lucide-react"; -import { type ChangeEvent, type FormEvent, useCallback, useState } from "react"; +import { Search, X } from "lucide-react"; +import { type ChangeEvent, type FormEvent, useCallback, useRef, useState } from "react"; import { type BrowseQuery, browseHref, DEFAULT_WINDOW } from "./links"; +const ICON_CLASS = "h-3.5 w-3.5 text-extra-muted-foreground"; + /** * Search box for the plugin pages. Typing replaces the URL with the browse page for the term, * keeping `scope`'s category and sort. Without JavaScript the form submits the same URL. */ export function PluginSearch({ scope, className }: { scope: BrowseQuery; className?: string }) { const navigate = useNavigate(); + const input = useRef(null); const [term, setTerm] = useState(scope.q ?? ""); - const handleChange = useCallback( - (event: ChangeEvent) => { - const next = event.target.value; + const search = useCallback( + (next: string) => { setTerm(next); void navigate({ href: browseHref({ ...scope, q: next.trim() ? next : undefined }), @@ -21,6 +23,14 @@ export function PluginSearch({ scope, className }: { scope: BrowseQuery; classNa }, [navigate, scope], ); + const handleChange = useCallback( + (event: ChangeEvent) => search(event.target.value), + [search], + ); + const handleClear = useCallback(() => { + search(""); + input.current?.focus(); + }, [search]); const handleSubmit = useCallback((event: FormEvent) => event.preventDefault(), []); return (
)} -