mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-23 20:23:08 +00:00
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>
149 lines
3.6 KiB
TypeScript
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
|
|
)}
|
|
</>
|
|
);
|
|
}
|