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:
Peter Meier
2026-03-12 14:21:49 +01:00
parent aad93d145f
commit 7795a238e1
278 changed files with 15551 additions and 4072 deletions

View 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>
);
}