()
| 6 | import { Search } from "lucide-react"; |
| 7 | |
| 8 | export function SearchBar() { |
| 9 | const router = useRouter(); |
| 10 | const searchParams = useSearchParams(); |
| 11 | const [isPending, startTransition] = useTransition(); |
| 12 | |
| 13 | const handleSearch = useDebouncedCallback((term: string) => { |
| 14 | const params = new URLSearchParams(searchParams.toString()); |
| 15 | |
| 16 | if (term) { |
| 17 | params.set("search", term); |
| 18 | } else { |
| 19 | params.delete("search"); |
| 20 | } |
| 21 | |
| 22 | startTransition(() => { |
| 23 | router.push(`/?${params.toString()}`); |
| 24 | }); |
| 25 | }, 300); |
| 26 | |
| 27 | return ( |
| 28 | <div className="relative mx-auto max-w-2xl"> |
| 29 | <div className="relative"> |
| 30 | <input |
| 31 | type="text" |
| 32 | defaultValue={searchParams.get("search") ?? ""} |
| 33 | onChange={(e) => handleSearch(e.target.value)} |
| 34 | placeholder="Search bookmarks..." |
| 35 | className="w-full rounded-lg border p-3 pl-10 shadow-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500" |
| 36 | /> |
| 37 | |
| 38 | <Search className="absolute inset-y-0 left-3 my-auto h-5 w-5 text-gray-400" /> |
| 39 | |
| 40 | {isPending && ( |
| 41 | <div className="absolute right-3 top-3"> |
| 42 | <div className="h-5 w-5 animate-spin rounded-full border-b-2 border-blue-500"></div> |
| 43 | </div> |
| 44 | )} |
| 45 | </div> |
| 46 | </div> |
| 47 | ); |
| 48 | } |
nothing calls this directly
no test coverage detected