'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
)
}