RustyCMS: file-based headless CMS — API, Admin UI (content, types, assets), Docker/Caddy, image transform; only demo type and demo content in version control
Made-with: Cursor
This commit is contained in:
63
admin-ui/src/components/SearchBar.tsx
Normal file
63
admin-ui/src/components/SearchBar.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user