import React, { useId, useState, useEffect, createContext, useContext, Fragment } from "react";
import { SearchResult } from "@/services/FlexSearchService";
import { Dialog, DialogPanel } from "@headlessui/react";
import { useDebounce } from "@/hooks/useDebounce";
import Highlighter from "react-highlight-words";
import { navigation } from "@/lib/navigation";
import { navigate } from "vike/client/router";
import { onSearch } from "./Search.telefunc";
import clsx from "clsx";
const SearchContext = createContext<{
query: string;
close: () => void;
results: SearchResult[];
isLoading: boolean;
isOpened: boolean;
setQuery: (query: string) => void;
setIsOpened: (isOpened: boolean) => void;
setIsLoading: (isLoading: boolean) => void;
setResults: (results: SearchResult[]) => void;
}>({
query: "",
close: () => {},
results: [],
isLoading: false,
isOpened: false,
setQuery: () => {},
setIsOpened: () => {},
setIsLoading: () => {},
setResults: () => {},
});
function SearchIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
);
}
function LoadingIcon(props: React.ComponentPropsWithoutRef<"svg">) {
const id = useId();
return (
);
}
function SearchInput() {
const { close, setQuery, query, isLoading } = useContext(SearchContext);
return (
{
if (event.key === "Escape") {
// In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the
// bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI.
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
close();
}
}}
value={query}
onChange={(event) => setQuery(event.currentTarget.value)}
/>
{isLoading && (
)}
);
}
function HighlightQuery({ text, query }: { text: string; query: string }) {
return (
);
}
function SearchResultItem({ result, query }: { result: SearchResult; query: string }) {
const { close } = useContext(SearchContext);
const id = useId();
const sectionTitle = navigation.find((section) =>
section.links.find((link) => link.href === result.url.split("#")[0]),
)?.title;
const hierarchy = [sectionTitle, result.pageTitle].filter((x): x is string => typeof x === "string");
return (
{
navigate(result.url);
close();
}}
>
{hierarchy.length > 0 && (
{hierarchy.map((item, itemIndex, items) => (
/
))}
)}
);
}
function SearchResults() {
const { results, query } = useContext(SearchContext);
if (results.length === 0) {
return (
Aucun résultat pour “
{query}
”
);
}
return (
{results.map((result) => (
))}
);
}
function SearchDialog({ className }: { className?: string }) {
const { close, isOpened, setIsOpened, results } = useContext(SearchContext);
useEffect(() => {
if (isOpened) return;
function onKeyDown(event: KeyboardEvent) {
if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
setIsOpened(true);
}
}
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
};
}, [isOpened, setIsOpened]);
return (
<>
>
);
}
export function Search() {
const [results, setResults] = useState([]);
const [debouncedQuery, setDebouncedQuery] = useDebounce();
const [modifierKey, setModifierKey] = useState();
const [isLoading, setIsLoading] = useState(false);
const [isOpened, setIsOpened] = useState(false);
const [query, setQuery] = useState("");
useEffect(() => {
const platform = navigator.userAgentData?.platform || navigator.platform;
setModifierKey(/(Mac|iPhone|iPod|iPad)/i.test(platform) ? "⌘" : "Ctrl ");
}, []);
useEffect(() => {
setDebouncedQuery(query);
}, [query]);
useEffect(() => {
if (debouncedQuery.length === 0) {
setIsLoading(false);
setResults([]);
return;
}
setIsLoading(true);
onSearch(debouncedQuery, 5)
.then(setResults)
.finally(() => {
setIsLoading(false);
});
}, [debouncedQuery]);
return (
setIsOpened(false),
results,
isLoading,
isOpened,
setQuery,
setIsOpened,
setIsLoading,
setResults,
}}
>
setIsOpened(true)}
>
Rechercher...
{modifierKey && (
{modifierKey}
K
)}
);
}