import { 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 && ( )}
  • ); } function SearchResults() { const { results, query } = useContext(SearchContext); if (results.length === 0) { return (

    Aucun résultat pour “ {query}

    ); } return ( ); } 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 ( <>
    event.preventDefault()}>
    {results.length > 0 && }
    ); } 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, }} > ); }