import { forwardRef, Fragment, useEffect, useId, useRef, useState } from 'react' import { useRouter } from 'next/router' import { createAutocomplete } from '@algolia/autocomplete-core' import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia' import { Dialog, Transition } from '@headlessui/react' import algoliasearch from 'algoliasearch/lite' import clsx from 'clsx' const searchClient = algoliasearch( process.env.NEXT_PUBLIC_DOCSEARCH_APP_ID, process.env.NEXT_PUBLIC_DOCSEARCH_API_KEY ) function useAutocomplete() { const id = useId() const router = useRouter() const [autocompleteState, setAutocompleteState] = useState({}) const [autocomplete] = useState(() => createAutocomplete({ id, placeholder: 'Find something...', defaultActiveItemId: 0, onStateChange({ state }) { setAutocompleteState(state) }, shouldPanelOpen({ state }) { return state.query !== '' }, navigator: { navigate({ itemUrl }) { autocomplete.setIsOpen(true) router.push(itemUrl) }, }, getSources() { return [ { sourceId: 'documentation', getItemInputValue({ item }) { return item.query }, getItemUrl({ item }) { const url = new URL(item.url) return `${url.pathname}${url.hash}` }, onSelect({ itemUrl }) { router.push(itemUrl) }, getItems({ query }) { return getAlgoliaResults({ searchClient, queries: [ { query, indexName: process.env.NEXT_PUBLIC_DOCSEARCH_INDEX_NAME, params: { hitsPerPage: 5, highlightPreTag: '', highlightPostTag: '', }, }, ], }) }, }, ] }, }) ) return { autocomplete, autocompleteState } } function resolveResult(result) { const allLevels = Object.keys(result.hierarchy) const hierarchy = Object.entries(result._highlightResult.hierarchy).filter( ([, { value }]) => Boolean(value) ) const levels = hierarchy.map(([level]) => level) const level = result.type === 'content' ? levels.pop() : levels .filter( (level) => allLevels.indexOf(level) <= allLevels.indexOf(result.type) ) .pop() return { titleHtml: result._highlightResult.hierarchy[level].value, hierarchyHtml: hierarchy .slice(0, levels.indexOf(level)) .map(([, { value }]) => value), } } function SearchIcon(props) { return ( ) } function NoResultsIcon(props) { return ( ) } function LoadingIcon(props) { const id = useId() return ( ) } function SearchResult({ result, resultIndex, autocomplete, collection }) { const id = useId() const { titleHtml, hierarchyHtml } = resolveResult(result) return (
  • 0 && 'border-t border-zinc-100 dark:border-zinc-800' )} aria-labelledby={`${id}-hierarchy ${id}-title`} {...autocomplete.getItemProps({ item: result, source: collection.source, })} >
  • ) } function SearchResults({ autocomplete, query, collection }) { if (collection.items.length === 0) { return (

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

    ) } return ( ) } const SearchInput = forwardRef(function SearchInput( { autocomplete, autocompleteState, onClose }, inputRef ) { const inputProps = autocomplete.getInputProps({}) 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. document.activeElement?.blur() onClose() } else { inputProps.onKeyDown(event) } }} /> {autocompleteState.status === 'stalled' && (
    )}
    ) }) function AlgoliaLogo(props) { return ( ) } function SearchButton(props) { const [modifierKey, setModifierKey] = useState() useEffect(() => { setModifierKey( /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl ' ) }, []) return ( <> ) } function SearchDialog({ open, setOpen, className }) { const router = useRouter() const formRef = useRef() const panelRef = useRef() const inputRef = useRef() const { autocomplete, autocompleteState } = useAutocomplete() useEffect(() => { if (!open) { return } function onRouteChange() { setOpen(false) } router.events.on('routeChangeStart', onRouteChange) router.events.on('hashChangeStart', onRouteChange) return () => { router.events.off('routeChangeStart', onRouteChange) router.events.off('hashChangeStart', onRouteChange) } }, [open, setOpen, router]) useEffect(() => { if (open) { return } function onKeyDown(event) { 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 && ( <>

    Search by{' '}

    )}
    ) } function useSearchProps() { const buttonRef = useRef() const [open, setOpen] = useState(false) return { buttonProps: { ref: buttonRef, onClick() { setOpen(true) }, }, dialogProps: { open, setOpen(open) { const { width, height } = buttonRef.current.getBoundingClientRect() if (!open || (width !== 0 && height !== 0)) { setOpen(open) } }, }, } } export function Search() { const [modifierKey, setModifierKey] = useState() const { buttonProps, dialogProps } = useSearchProps() useEffect(() => { setModifierKey( /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl ' ) }, []) return (
    ) } export function MobileSearch() { const { buttonProps, dialogProps } = useSearchProps() return (
    ) }