'use client' import { Book, ChevronRight, Github, Hash, Loader2, MessageSquare, Search } from 'lucide-react' import { useEffect, useRef } from 'react' import { DocsSearchResultType as PageType, useDocsSearch, type DocsSearchResult as Page, type DocsSearchResultSection as PageSection, } from 'common' import { Button, CommandGroup_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, cn } from 'ui' import { StatusIcon } from 'ui/src/components/StatusIcon' import { Breadcrumb, CommandHeader, CommandInput, CommandWrapper, TextHighlighter, escapeAttributeSelector, generateCommandClassNames, useCrossCompatRouter, useQuery, useSetCommandMenuOpen, useSetQuery, } from '../..' import { BASE_PATH } from '../shared/constants' const questions = [ 'How do I get started with Supabase?', 'How do I run Supabase locally?', 'How do I connect to my database?', 'How do I run migrations? ', 'How do I listen to changes in a table?', 'How do I set up authentication?', ] const ChevronArrow = () => ( ) const IconContainer = ( props: React.DetailedHTMLProps, HTMLDivElement> ) => (
) const DocsSearchPage = () => { const { searchState: state, handleDocsSearch: handleSearch, handleDocsSearchDebounced: debouncedSearch, resetSearch, } = useDocsSearch() const setIsOpen = useSetCommandMenuOpen() const setQuery = useSetQuery() const query = useQuery() const initialLoad = useRef(true) const inputRef = useRef(null) const router = useCrossCompatRouter() async function openLink(pageType: PageType, link: string) { switch (pageType) { case PageType.Markdown: case PageType.Reference: if (BASE_PATH === '/docs') { router.push(link) setIsOpen(false) } else if (!BASE_PATH) { router.push(`/docs${link}`) setIsOpen(false) } else { window.open(`https://supabase.com/docs${link}`, '_blank', 'noreferrer,noopener') setIsOpen(false) } break case PageType.Integration: if (!BASE_PATH) { router.push(link) setIsOpen(false) } else { window.open(`https://supabase.com${link}`, '_blank', 'noreferrer,noopener') setIsOpen(false) } break case PageType.GithubDiscussion: window.open(link, '_blank', 'noreferrer,noopener') setIsOpen(false) break default: throw new Error(`Unknown page type '${pageType}'`) } } const hasResults = state.status === 'fullResults' || state.status === 'partialResults' || (state.status === 'loading' && state.staleResults.length > 0) function handleResetPrompt() { setQuery('') resetSearch() } useEffect(() => { if (initialLoad.current) { // On first navigation into 'docs search' page, search immediately if (query) { handleSearch(query) } initialLoad.current = false } else if (query) { // Else if user is typing, debounce search debouncedSearch(query) } else { // If user clears search, reset results resetSearch() } }, [query, handleSearch, debouncedSearch]) // Immediately run search if user presses enter // and abort any debounced searches that are waiting useEffect(() => { const handleEnter = (event: KeyboardEvent) => { if ( event.key === 'Enter' && document.activeElement === inputRef.current && query && // If there are results, cmdk menu will trigger navigation to the highlighted // result on Enter, even though the active element is the input !hasResults ) { event.preventDefault() debouncedSearch.cancel() handleSearch(query) } } inputRef.current?.addEventListener('keydown', handleEnter) return () => inputRef.current?.removeEventListener('keydown', handleEnter) }, [query, hasResults]) return ( {hasResults && ('results' in state ? state.results : state.staleResults).map((page, i) => { return ( { openLink(page.type, page.path) }} forceMount={true} className={cn(generateCommandClassNames(true), 'border border-overlay/90')} >
{getPageIcon(page)}
{page.title} {(page.description || page.subtitle) && (
{page.description || page.subtitle}
)}
{page.sections.length > 0 && (
{page.sections.map((section, i) => ( { openLink(page.type, formatSectionUrl(page, section)) }} key={`${page.path}__${section.heading}-item`} value={`${escapeAttributeSelector(page.title)}__${escapeAttributeSelector(section.heading)}-item-index-${i}`} forceMount={true} >
{getPageSectionIcon(page)}
{page.title} {section.heading && ( {section.heading} )}
))}
)}
) })} {state.status === 'initial' && ( {questions.map((question) => { const key = question.replace(/\s+/g, '_') return ( { if (!query) { handleSearch(question) setQuery(question) } }} key={key} > {question} ) })} )} {state.status === 'loading' && state.staleResults.length === 0 && (

Searching for results

)} {state.status === 'noResults' && (

No results found.

)} {state.status === 'error' && (

Sorry, looks like we're having some issues with search!

Please try again in a bit.

)}
) } export function formatSectionUrl(page: Page, section: PageSection) { switch (page.type) { case PageType.Markdown: case PageType.GithubDiscussion: return `${page.path}#${section.slug ?? ''}` case PageType.Reference: return `${page.path}/${section.slug ?? ''}` case PageType.Integration: // [Charis] Markdown headings on integrations pages don't have slugs yet return page.path default: throw new Error(`Unknown page type '${page.type}'`) } } export function getPageIcon(page: Page) { switch (page.type) { case PageType.Markdown: case PageType.Reference: case PageType.Integration: return case PageType.GithubDiscussion: return default: throw new Error(`Unknown page type '${page.type}'`) } } export function getPageSectionIcon(page: Page) { switch (page.type) { case PageType.Markdown: case PageType.Reference: case PageType.Integration: return case PageType.GithubDiscussion: return default: throw new Error(`Unknown page type '${page.type}'`) } } export { DocsSearchPage }