86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
"use client";
|
|
import { getSearchPath } from "@/lib/common";
|
|
import { useRouter } from "next/navigation";
|
|
import {
|
|
type ReactNode,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
useTransition,
|
|
} from "react";
|
|
import { useDebouncedCallback } from "use-debounce";
|
|
import { Icon } from "../general/Icon";
|
|
import { PageHeader } from "../general/PageHeader";
|
|
import styles from "./searchContainer.module.scss";
|
|
|
|
export function SearchShell({
|
|
initialQuery,
|
|
children,
|
|
}: {
|
|
initialQuery: string;
|
|
children: ReactNode;
|
|
}) {
|
|
const { replace } = useRouter();
|
|
const [inputValue, setInputValue] = useState(initialQuery);
|
|
const [isPending, startTransition] = useTransition();
|
|
const lastPushedRef = useRef(initialQuery);
|
|
const fetching = isPending || inputValue !== lastPushedRef.current;
|
|
|
|
const pushQuery = useDebouncedCallback((next: string) => {
|
|
lastPushedRef.current = next;
|
|
startTransition(() => {
|
|
replace(getSearchPath(next));
|
|
});
|
|
}, 300);
|
|
|
|
useEffect(() => {
|
|
if (initialQuery !== lastPushedRef.current) {
|
|
lastPushedRef.current = initialQuery;
|
|
setInputValue(initialQuery);
|
|
}
|
|
}, [initialQuery]);
|
|
|
|
return (
|
|
<div className={styles.searchContainer}>
|
|
<PageHeader heading={initialQuery ? `Søk: «${initialQuery}»` : "Søk"} />
|
|
<form
|
|
action="/sok"
|
|
method="get"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
pushQuery.cancel();
|
|
lastPushedRef.current = inputValue;
|
|
startTransition(() => {
|
|
replace(getSearchPath(inputValue));
|
|
});
|
|
}}
|
|
>
|
|
<div className={styles.searchField}>
|
|
<label htmlFor="search-query" className="sr-only">
|
|
Søk
|
|
</label>
|
|
<input
|
|
id="search-query"
|
|
name="q"
|
|
type="text"
|
|
value={inputValue}
|
|
onChange={(e) => {
|
|
setInputValue(e.target.value);
|
|
pushQuery(e.target.value);
|
|
}}
|
|
/>
|
|
<div className={styles.searchIcon} aria-hidden="true">
|
|
<Icon type="search" />
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<div
|
|
className={fetching ? styles.fetching : undefined}
|
|
aria-busy={fetching}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|