Initial commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Search, X } from "lucide-react";
|
||||
|
||||
export function GridSearchInput() {
|
||||
const router = useRouter();
|
||||
const params = useSearchParams();
|
||||
const initial = params.get("q") ?? "";
|
||||
const [value, setValue] = useState(initial);
|
||||
const debounce = useRef<number | null>(null);
|
||||
const lastApplied = useRef(initial);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounce.current) window.clearTimeout(debounce.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Sync state from URL (e.g. when navigating, "All" link clears it).
|
||||
useEffect(() => {
|
||||
const fromUrl = params.get("q") ?? "";
|
||||
if (fromUrl !== lastApplied.current) {
|
||||
lastApplied.current = fromUrl;
|
||||
setValue(fromUrl);
|
||||
}
|
||||
}, [params]);
|
||||
|
||||
function apply(next: string) {
|
||||
if (next === lastApplied.current) return;
|
||||
lastApplied.current = next;
|
||||
const sp = new URLSearchParams(params.toString());
|
||||
if (next.trim()) {
|
||||
sp.set("q", next.trim());
|
||||
// Activating search clears the letter filter so the user sees all matches.
|
||||
sp.delete("letter");
|
||||
} else {
|
||||
sp.delete("q");
|
||||
}
|
||||
router.push(`?${sp.toString()}`, { scroll: false });
|
||||
}
|
||||
|
||||
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const next = e.target.value;
|
||||
setValue(next);
|
||||
if (debounce.current) window.clearTimeout(debounce.current);
|
||||
debounce.current = window.setTimeout(() => apply(next), 300);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
setValue("");
|
||||
if (debounce.current) window.clearTimeout(debounce.current);
|
||||
apply("");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-fg-muted)] pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (debounce.current) window.clearTimeout(debounce.current);
|
||||
apply(value);
|
||||
} else if (e.key === "Escape") {
|
||||
clear();
|
||||
}
|
||||
}}
|
||||
placeholder="Search Code, Title, Notes…"
|
||||
className="glass rounded-lg pl-8 pr-7 py-1.5 text-sm outline-none focus:border-[var(--color-cyan)] w-56"
|
||||
/>
|
||||
{value && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={clear}
|
||||
aria-label="Clear search"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--color-fg-muted)] hover:text-[var(--color-fg)]"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user