Files
react.dev/src/components/Search.tsx
Matt Carroll 855ce2373d Upgrade to React 19, Next 15.1 and enable React Compiler (#6996)
Co-authored-by: Sebastian Markbåge <sebastian@calyptus.eu>
Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>
2025-01-13 08:48:01 -08:00

149 lines
3.6 KiB
TypeScript

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import Head from 'next/head';
import Link from 'next/link';
import Router from 'next/router';
import {lazy, useEffect} from 'react';
import * as React from 'react';
import {createPortal} from 'react-dom';
import {siteConfig} from 'siteConfig';
import type {ComponentType, PropsWithChildren} from 'react';
import type {DocSearchModalProps} from '@docsearch/react/modal';
export interface SearchProps {
appId?: string;
apiKey?: string;
indexName?: string;
searchParameters?: any;
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
}
function Hit({hit, children}: any) {
return <Link href={hit.url.replace()}>{children}</Link>;
}
// Copy-pasted from @docsearch/react to avoid importing the whole bundle.
// Slightly trimmed to features we use.
// (c) Algolia, Inc.
function isEditingContent(event: any) {
var element = event.target;
var tagName = element.tagName;
return (
element.isContentEditable ||
tagName === 'INPUT' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
);
}
function useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
}: {
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
}) {
useEffect(() => {
function onKeyDown(event: any) {
function open() {
// We check that no other DocSearch modal is showing before opening
// another one.
if (!document.body.classList.contains('DocSearch--active')) {
onOpen();
}
}
if (
(event.keyCode === 27 && isOpen) ||
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
(!isEditingContent(event) && event.key === '/' && !isOpen)
) {
event.preventDefault();
if (isOpen) {
onClose();
} else if (!document.body.classList.contains('DocSearch--active')) {
open();
}
}
}
window.addEventListener('keydown', onKeyDown);
return function () {
window.removeEventListener('keydown', onKeyDown);
};
}, [isOpen, onOpen, onClose]);
}
const options = {
appId: siteConfig.algolia.appId,
apiKey: siteConfig.algolia.apiKey,
indexName: siteConfig.algolia.indexName,
};
const DocSearchModal: any = lazy(() =>
import('@docsearch/react/modal').then((mod) => ({
default: mod.DocSearchModal as ComponentType<
PropsWithChildren<DocSearchModalProps>
>,
}))
);
export function Search({
isOpen,
onOpen,
onClose,
searchParameters = {
hitsPerPage: 30,
attributesToHighlight: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
],
},
}: SearchProps) {
useDocSearchKeyboardEvents({isOpen, onOpen, onClose});
return (
<>
<Head>
<link
rel="preconnect"
href={`https://${options.appId}-dsn.algolia.net`}
/>
</Head>
{isOpen &&
createPortal(
<DocSearchModal
{...options}
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
)}
</>
);
}