'use client' import { Combobox, ComboboxItem, ComboboxProvider } from '@ariakit/react' import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' import { debounce } from '@tldraw/utils' import { Hit } from 'instantsearch.js' import { SendEventForHits } from 'instantsearch.js/es/lib/utils' import Link from 'next/link' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { createRef, Fragment, useCallback, useEffect, useState } from 'react' import { Configure, Highlight, InstantSearch, useHits, useSearchBox } from 'react-instantsearch' import { twJoin } from 'tailwind-merge' import { getSearchIndexName, SearchEntry } from '@/utils/algolia' import { searchClient } from '@/utils/search-api' import { ContentHighlight } from './ContentHighlight' export function FullPageSearch({ indexName }: { indexName: 'docs' | 'blog' }) { return ( ) } function InstantSearchInner({ indexName }: { indexName: 'docs' | 'blog' }) { const { items, sendEvent } = useHits() const { refine } = useSearchBox() const router = useRouter() const [open, setOpen] = useState(true) const searchParams = useSearchParams() const urlQuery = searchParams.get('query') || '' const handleChange = (path: string) => { setOpen(false) router.push(path) } const updateSearchQuery = debounce((value: string) => refine(value), 500) useEffect(() => { refine(urlQuery) }, [urlQuery, refine]) return ( open && ( ) ) } interface AutocompleteProps { items: Hit[] onChange(value: string): void onInputChange(value: string): void defaultValue: string sendEvent: SendEventForHits indexName: 'docs' | 'blog' } function SearchAutocomplete({ items, defaultValue, onInputChange, onChange, sendEvent, indexName, }: AutocompleteProps) { return ( defaultSelectedValue="" defaultValue={defaultValue} includesBaseElement={true} setValue={(newValue) => onInputChange(newValue)} setSelectedValue={(newValue) => onChange(newValue)} >
) } function SearchInput({ indexName }: { indexName: 'docs' | 'blog' }) { const comboboxRef = createRef() const router = useRouter() const pathname = usePathname() const capitalizedIndexName = indexName.charAt(0).toUpperCase() + indexName.slice(1) const updateUrl = useCallback(() => { const queryParam = comboboxRef.current?.value || '' router.push(`${pathname}?query=${encodeURIComponent(queryParam.trim())}`) }, [comboboxRef, router, pathname]) const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key !== 'Enter') return updateUrl() }, [updateUrl] ) useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { comboboxRef.current?.focus() comboboxRef.current?.setSelectionRange(0, comboboxRef.current.value.length) } } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }, [comboboxRef]) return (
) } function Results({ items, sendEvent }: { items: Hit[]; sendEvent: SendEventForHits }) { let section = '' const renderedItems = items.map((hit) => { const showChapter = hit.section !== section section = hit.section const href = hit.headingSlug ? `${hit.path}#${hit.headingSlug}` : hit.path return ( {showChapter && (
{section}
)} { if (e.key === 'Enter') { sendEvent('click', hit, 'Hit clicked via keyboard') } }} > sendEvent('click', hit, 'Hit clicked')}>
) }) return items.length === 0 ? (
No results found.
) : ( renderedItems ) }