64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
import { Icon } from "@iconify/react";
|
|
|
|
export function SearchBar({
|
|
placeholder = "Search…",
|
|
paramName = "_q",
|
|
}: {
|
|
placeholder?: string;
|
|
paramName?: string;
|
|
}) {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const searchParams = useSearchParams();
|
|
const [value, setValue] = useState(searchParams.get(paramName) ?? "");
|
|
|
|
// Sync when URL changes externally
|
|
useEffect(() => {
|
|
setValue(searchParams.get(paramName) ?? "");
|
|
}, [searchParams, paramName]);
|
|
|
|
const push = useCallback(
|
|
(q: string) => {
|
|
const sp = new URLSearchParams(searchParams.toString());
|
|
if (q.trim()) {
|
|
sp.set(paramName, q.trim());
|
|
} else {
|
|
sp.delete(paramName);
|
|
}
|
|
sp.set("_page", "1");
|
|
router.push(`${pathname}?${sp.toString()}`);
|
|
},
|
|
[router, pathname, searchParams, paramName]
|
|
);
|
|
|
|
// Debounce: push URL 300ms after typing stops
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
const current = searchParams.get(paramName) ?? "";
|
|
if (value.trim() !== current) push(value);
|
|
}, 300);
|
|
return () => clearTimeout(timer);
|
|
}, [value]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
return (
|
|
<div className="relative w-full min-w-0 sm:w-56">
|
|
<Icon
|
|
icon="mdi:magnify"
|
|
className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
|
|
aria-hidden
|
|
/>
|
|
<input
|
|
type="search"
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
placeholder={placeholder}
|
|
className="h-10 w-full min-w-0 rounded-md border border-input bg-background pl-8 pr-3 text-base text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-[3px] focus:ring-ring/50 sm:h-9 sm:text-sm [touch-action:manipulation]"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|