beta/src/components/Search.tsx (128 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. */ // @ts-ignore import {useDocSearchKeyboardEvents} from '@docsearch/react'; import {IconSearch} from 'components/Icon/IconSearch'; import Head from 'next/head'; import Link from 'next/link'; import Router from 'next/router'; import * as React from 'react'; import {createPortal} from 'react-dom'; import {siteConfig} from 'siteConfig'; export interface SearchProps { appId?: string; apiKey?: string; indexName?: string; searchParameters?: any; renderModal?: boolean; } function Hit({hit, children}: any) { return ( <Link href={hit.url.replace()}> <a>{children}</a> </Link> ); } function Kbd(props: {children?: React.ReactNode}) { return ( <kbd className="h-6 w-6 border border-transparent mr-1 bg-wash dark:bg-wash-dark text-gray-30 align-middle p-0 inline-flex justify-center items-center text-xs text-center rounded" {...props} /> ); } const options = { appId: siteConfig.algolia.appId, apiKey: siteConfig.algolia.apiKey, indexName: siteConfig.algolia.indexName, }; let DocSearchModal: any = null; export const Search: React.FC<SearchProps> = ({ searchParameters = { hitsPerPage: 5, }, }) => { const [isLoaded] = React.useState(true); const [isShowing, setIsShowing] = React.useState(false); const importDocSearchModalIfNeeded = React.useCallback( function importDocSearchModalIfNeeded() { if (DocSearchModal) { return Promise.resolve(); } // @ts-ignore return Promise.all([import('@docsearch/react/modal')]).then( ([{DocSearchModal: Modal}]) => { DocSearchModal = Modal; } ); }, [] ); const onOpen = React.useCallback( function onOpen() { importDocSearchModalIfNeeded().then(() => { setIsShowing(true); }); }, [importDocSearchModalIfNeeded, setIsShowing] ); const onClose = React.useCallback( function onClose() { setIsShowing(false); }, [setIsShowing] ); useDocSearchKeyboardEvents({isOpen: isShowing, onOpen, onClose}); return ( <> <Head> <link rel="preconnect" href={`https://${options.appId}-dsn.algolia.net`} /> </Head> <button aria-label="Search" type="button" className="inline-flex md:hidden items-center text-lg p-1 ml-4 lg:ml-6" onClick={onOpen}> <IconSearch className="align-middle" /> </button> <button type="button" className="hidden md:flex relative pl-4 pr-0.5 py-1 h-10 bg-secondary-button dark:bg-gray-80 outline-none focus:ring focus:outline-none betterhover:hover:bg-opacity-80 pointer items-center shadow-inner text-left w-full text-gray-30 rounded-lg align-middle text-sm" onClick={onOpen}> <IconSearch className="mr-3 align-middle text-gray-30 shrink-0 group-betterhover:hover:text-gray-70" /> Search <span className="ml-auto hidden sm:flex item-center"> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </span> </button> {isLoaded && isShowing && createPortal( <DocSearchModal {...options} initialScrollY={window.scrollY} searchParameters={searchParameters} onClose={onClose} navigator={{ navigate({itemUrl}: any) { Router.push(itemUrl); }, }} transformItems={(items: any[]) => { return items.map((item) => { const url = new URL(item.url); return { ...item, url: item.url.replace(url.origin, '').replace('#__next', ''), }; }); }} hitComponent={Hit} />, document.body )} </> ); }; Search.displayName = 'Search';