feat: add search menu to all resources
This commit is contained in:
@ -102,7 +102,15 @@ export const KMenu = (
|
||||
}
|
||||
|
||||
useEventListener("keydown", (ev: KeyboardEvent) => {
|
||||
if (ev.key === "/" && ev.ctrlKey) {
|
||||
if (ev.key === "k") {
|
||||
if (ev?.target?.nodeName == "INPUT") {
|
||||
return;
|
||||
}
|
||||
if (!visible.value) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
visible.value = !visible.value;
|
||||
}
|
||||
|
||||
|
96
islands/Search.tsx
Normal file
96
islands/Search.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import useDebouncedCallback from "@lib/hooks/useDebouncedCallback.ts";
|
||||
import { IconSearch } from "@components/icons.tsx";
|
||||
import { useEventListener } from "@lib/hooks/useEventListener.ts";
|
||||
|
||||
export const RedirectSearchHandler = () => {
|
||||
useEventListener("keydown", (e: KeyboardEvent) => {
|
||||
if (e?.target?.nodeName == "INPUT") return;
|
||||
|
||||
if (
|
||||
e.key === "?" &&
|
||||
window.location.search === ""
|
||||
) {
|
||||
window.location.href += "?q=";
|
||||
}
|
||||
});
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const SearchComponent = (
|
||||
{ q, type }: { q: string; type?: string },
|
||||
) => {
|
||||
const [searchQuery, setSearchQuery] = useState(q);
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
if ("history" in globalThis) {
|
||||
const u = new URL(window.location.href);
|
||||
if (u.searchParams.get("q") !== searchQuery) {
|
||||
u.searchParams.set("q", searchQuery);
|
||||
window.history.replaceState({}, "", u);
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = async (query: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const fetchUrl = new URL(window.location.href);
|
||||
fetchUrl.pathname = "/api/resources";
|
||||
if (searchQuery) {
|
||||
fetchUrl.searchParams.set("q", encodeURIComponent(searchQuery));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (type) {
|
||||
fetchUrl.searchParams.set("type", type);
|
||||
}
|
||||
const response = await fetch(fetchUrl);
|
||||
const jsonData = await response.json();
|
||||
setData(jsonData);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current && searchQuery?.length === 0) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [inputRef.current, searchQuery]);
|
||||
|
||||
const debouncedFetchData = useDebouncedCallback(fetchData, 500); // Debounce the fetchData function with a delay of 500ms
|
||||
|
||||
const handleInputChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
setSearchQuery(target.value);
|
||||
debouncedFetchData(target.value); // Call the debounced fetch function with the updated search query
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="max-w-full">
|
||||
<div class="bg-white flex items-center gap-1 w-full py-3 px-4 pr-12 rounded-xl border border-gray-300 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-600 focus:border-blue-600">
|
||||
<IconSearch class="w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
class=""
|
||||
ref={inputRef}
|
||||
value={searchQuery}
|
||||
onInput={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{isLoading ? <div>Loading...</div> : (
|
||||
<div>
|
||||
{data.map((d) => (
|
||||
<pre class="text-white">{JSON.stringify(d.document, null, 2)}</pre>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchComponent;
|
Reference in New Issue
Block a user