"use client"; import { navigation } from "@/components/docs/Navigation"; import { type Result } from "@/mdx/search.mjs"; import { type AutocompleteApi, type AutocompleteCollection, type AutocompleteState, createAutocomplete, } from "@algolia/autocomplete-core"; import { Dialog, Transition } from "@headlessui/react"; import clsx from "clsx"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Fragment, Suspense, forwardRef, useCallback, useEffect, useId, useRef, useState } from "react"; import Highlighter from "react-highlight-words"; type EmptyObject = Record; type Autocomplete = AutocompleteApi; function useAutocomplete({ close }: { close: () => 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(); } } let [autocomplete] = useState(() => createAutocomplete({ id, placeholder: "Find something...", defaultActiveItemId: 0, onStateChange({ state }) { setAutocompleteState(state); }, shouldPanelOpen({ state }) { return state.query !== ""; }, navigator: { navigate, }, getSources({ query }) { return import("@/mdx/search.mjs").then(({ search }) => { return [ { sourceId: "documentation", getItems() { return search(query, { limit: 5 }); }, getItemUrl({ item }) { return item.url; }, onSelect: navigate, }, ]; }); }, }) ); return { autocomplete, autocompleteState }; } function SearchIcon(props: React.ComponentPropsWithoutRef<"svg">) { return ( ); } function NoResultsIcon(props: React.ComponentPropsWithoutRef<"svg">) { return ( ); } function LoadingIcon(props: React.ComponentPropsWithoutRef<"svg">) { let id = useId(); return ( ); } function HighlightQuery({ text, query }: { text: string; query: string }) { return ( ); } function SearchResult({ result, resultIndex, autocomplete, collection, query, }: { result: Result; resultIndex: number; 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 (
  • 0 && "border-t border-slate-100 dark:border-slate-800" )} aria-labelledby={`${id}-hierarchy ${id}-title`} {...autocomplete.getItemProps({ item: result, source: collection.source, })}> {hierarchy.length > 0 && ( )}
  • ); } function SearchResults({ autocomplete, query, collection, }: { autocomplete: Autocomplete; query: string; collection: AutocompleteCollection; }) { if (collection.items.length === 0) { return (

    Nothing found for{" "} ‘{query}’ . Please try again.

    ); } return (
      {collection.items.map((result, resultIndex) => ( ))}
    ); } const SearchInput = forwardRef< React.ElementRef<"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 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 { autocomplete, autocompleteState } = useAutocomplete({ close() { setOpen(false); }, }); let pathname = usePathname(); let searchParams = useSearchParams(); useEffect(() => { setOpen(false); }, [pathname, searchParams, setOpen]); 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 ( autocomplete.setQuery("")}>
    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); } }, [setOpen] ), }, }; } export function Search() { let [modifierKey, setModifierKey] = useState(); let { buttonProps, dialogProps } = useSearchProps(); useEffect(() => { setModifierKey(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl "); }, []); return (
    ); } export function MobileSearch() { let { buttonProps, dialogProps } = useSearchProps(); return (
    ); }