import { forwardRef, Fragment, Suspense, useCallback, useEffect, useId, useRef, useState } from "react"; import Highlighter from "react-highlight-words"; import { usePageContext } from "vike-react/usePageContext"; import { navigate as routerNavigate } from "vike/client/router"; // import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { type AutocompleteApi, type AutocompleteCollection, type AutocompleteState, createAutocomplete, } from "@algolia/autocomplete-core"; import { Dialog, DialogPanel } from "@headlessui/react"; import clsx from "clsx"; import { navigation } from "@/lib/navigation"; import { onSearch } from "./Search.telefunc"; import type { SearchResult } from "@/lib/search"; type EmptyObject = Record; type Autocomplete = AutocompleteApi; function SearchIcon(props: React.ComponentPropsWithoutRef<"svg">) { return ( ); } function useAutocomplete({ close }: { close: (autocomplete: Autocomplete) => void }) { let id = useId(); // let router = useRouter(); let [autocompleteState, setAutocompleteState] = useState | EmptyObject>({}); function navigate({ itemUrl }: { itemUrl?: string }) { if (!itemUrl) { return; } // router.push(itemUrl); if (itemUrl === window.location.pathname + window.location.search + window.location.hash) { close(autocomplete); } } let [autocomplete] = useState(() => createAutocomplete({ id, placeholder: "Find something...", defaultActiveItemId: 0, onStateChange({ state }) { setAutocompleteState(state); }, shouldPanelOpen({ state }) { return state.query !== ""; }, navigator: { navigate, }, async getSources({ query }) { return onSearch(query).then((searchResult) => { return [ { sourceId: "documentation", getItems() { console.log({ searchResult }); return []; return searchResult; }, getItemUrl({ item }) { return item.url; }, onSelect: navigate, }, ]; }); }, }), ); return { autocomplete, autocompleteState }; } function LoadingIcon(props: React.ComponentPropsWithoutRef<"svg">) { let id = useId(); return ( ); } function HighlightQuery({ text, query }: { text: string; query: string }) { return ( ); } function SearchResult({ result, autocomplete, collection, query, }: { result: Result; autocomplete: Autocomplete; collection: AutocompleteCollection; query: string; }) { let id = useId(); let sectionTitle = navigation.find((section) => section.links.find((link) => link.href === result.url.split("#")[0]), )?.title; let hierarchy = [sectionTitle, result.pageTitle].filter((x): x is string => typeof x === "string"); return (
  • {hierarchy.length > 0 && ( )}
  • ); } function SearchResults({ autocomplete, query, collection, }: { autocomplete: Autocomplete; query: string; collection: AutocompleteCollection; }) { if (collection.items.length === 0) { return (

    No results for “ {query}

    ); } return (
      {collection.items.map((result) => ( ))}
    ); } const SearchInput = forwardRef< React.ComponentRef<"input">, { autocomplete: Autocomplete; autocompleteState: AutocompleteState | EmptyObject; onClose: () => void; } >(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) { let inputProps = autocomplete.getInputProps({ inputElement: null }); return (
    { if (event.key === "Escape" && !autocompleteState.isOpen && autocompleteState.query === "") { // 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(); } onClose(); } else { inputProps.onKeyDown(event); } }} /> {autocompleteState.status === "stalled" && (
    )}
    ); }); function CloseOnNavigation({ close, autocomplete, }: { close: (autocomplete: Autocomplete) => void; autocomplete: Autocomplete; }) { const { urlParsed } = usePageContext(); const { pathname, search } = urlParsed; useEffect(() => { close(autocomplete); }, [pathname, search, close, autocomplete]); return null; } function SearchDialog({ open, setOpen, className, }: { open: boolean; setOpen: (open: boolean) => void; className?: string; }) { let formRef = useRef>(null); let panelRef = useRef>(null); let inputRef = useRef>(null); let close = useCallback( (autocomplete: Autocomplete) => { setOpen(false); autocomplete.setQuery(""); }, [setOpen], ); let { autocomplete, autocompleteState } = useAutocomplete({ close() { close(autocomplete); }, }); useEffect(() => { if (open) { return; } function onKeyDown(event: KeyboardEvent) { if (event.key === "k" && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setOpen(true); } } window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); }; }, [open, setOpen]); return ( <> close(autocomplete)} className={clsx("fixed inset-0 z-50", className)}>
    setOpen(false)} />
    {autocompleteState.isOpen && ( )}
    ); } function useSearchProps() { let buttonRef = useRef>(null); let [open, setOpen] = useState(false); return { buttonProps: { ref: buttonRef, onClick() { setOpen(true); }, }, dialogProps: { open, setOpen: useCallback((open: boolean) => { let { width = 0, height = 0 } = buttonRef.current?.getBoundingClientRect() ?? {}; if (!open || (width !== 0 && height !== 0)) { setOpen(open); } }, []), }, }; } export function Search() { let [modifierKey, setModifierKey] = useState(); let { buttonProps, dialogProps } = useSearchProps(); useEffect(() => { setModifierKey(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl "); }, []); return ( <> ); }