|
| 1 | +/* eslint-disable react/react-in-jsx-scope */ |
| 2 | +import { useDocSearchKeyboardEvents } from '@docsearch/core/useDocSearchKeyboardEvents'; |
| 3 | +import { DocSearchButton } from '@docsearch/react/button'; |
| 4 | +import type { DocSearchModal as DocSearchModalType } from '@docsearch/react/modal'; |
| 5 | +import { useCallback, useRef, useState, type JSX } from 'react'; |
| 6 | +import { createPortal } from 'react-dom'; |
| 7 | + |
| 8 | +let DocSearchModal: typeof DocSearchModalType | null = null; |
| 9 | + |
| 10 | +function importDocSearchModalIfNeeded(): Promise<void> { |
| 11 | + if (DocSearchModal) { |
| 12 | + return Promise.resolve(); |
| 13 | + } |
| 14 | + // eslint-disable-next-line import/dynamic-import-chunkname |
| 15 | + return Promise.all([import('@docsearch/react/modal')]).then(([{ DocSearchModal: Modal }]) => { |
| 16 | + DocSearchModal = Modal; |
| 17 | + }); |
| 18 | +} |
| 19 | + |
| 20 | +function DocSearch(): JSX.Element { |
| 21 | + const [isOpen, setIsOpen] = useState(false); |
| 22 | + const [initialQuery, setInitialQuery] = useState<string | undefined>(undefined); |
| 23 | + const [isAskAiActive, setIsAskAiActive] = useState(false); |
| 24 | + const searchContainer = useRef<HTMLDivElement | null>(null); |
| 25 | + const searchButtonRef = useRef<HTMLButtonElement | null>(null); |
| 26 | + |
| 27 | + const prepareSearchContainer = useCallback(() => { |
| 28 | + if (!searchContainer.current) { |
| 29 | + const divElement = document.createElement('div'); |
| 30 | + searchContainer.current = divElement; |
| 31 | + document.body.insertBefore(divElement, document.body.firstChild); |
| 32 | + } |
| 33 | + }, []); |
| 34 | + |
| 35 | + const openModal = useCallback(() => { |
| 36 | + prepareSearchContainer(); |
| 37 | + importDocSearchModalIfNeeded().then(() => setIsOpen(true)); |
| 38 | + }, [prepareSearchContainer]); |
| 39 | + |
| 40 | + const closeModal = useCallback(() => { |
| 41 | + setIsOpen(false); |
| 42 | + searchButtonRef.current?.focus(); |
| 43 | + setInitialQuery(undefined); |
| 44 | + }, []); |
| 45 | + |
| 46 | + const handleInput = useCallback( |
| 47 | + (event: KeyboardEvent) => { |
| 48 | + if (event.key === 'f' && (event.metaKey || event.ctrlKey)) { |
| 49 | + // ignore browser's ctrl+f |
| 50 | + return; |
| 51 | + } |
| 52 | + // prevents duplicate key insertion in the modal input |
| 53 | + event.preventDefault(); |
| 54 | + setInitialQuery(event.key); |
| 55 | + openModal(); |
| 56 | + }, |
| 57 | + [openModal], |
| 58 | + ); |
| 59 | + |
| 60 | + const toggleAskAi = (active: boolean): void => { |
| 61 | + setIsAskAiActive(active); |
| 62 | + }; |
| 63 | + |
| 64 | + useDocSearchKeyboardEvents({ |
| 65 | + isOpen, |
| 66 | + onOpen: openModal, |
| 67 | + onClose: closeModal, |
| 68 | + onInput: handleInput, |
| 69 | + searchButtonRef, |
| 70 | + isAskAiActive, |
| 71 | + onAskAiToggle: toggleAskAi, |
| 72 | + }); |
| 73 | + |
| 74 | + return ( |
| 75 | + <> |
| 76 | + <DocSearchButton |
| 77 | + ref={searchButtonRef} |
| 78 | + translations={{ buttonText: 'Dynamic modal search' }} |
| 79 | + onTouchStart={importDocSearchModalIfNeeded} |
| 80 | + onFocus={importDocSearchModalIfNeeded} |
| 81 | + onMouseOver={importDocSearchModalIfNeeded} |
| 82 | + onClick={openModal} |
| 83 | + /> |
| 84 | + |
| 85 | + {isOpen && |
| 86 | + DocSearchModal && |
| 87 | + searchContainer.current && |
| 88 | + createPortal( |
| 89 | + <DocSearchModal |
| 90 | + indexName="docsearch" |
| 91 | + appId="PMZUYBQDAK" |
| 92 | + apiKey="24b09689d5b4223813d9b8e48563c8f6" |
| 93 | + askAi={{ |
| 94 | + assistantId: 'askAIDemo', |
| 95 | + searchParameters: { |
| 96 | + facetFilters: ['language:en'], |
| 97 | + }, |
| 98 | + }} |
| 99 | + initialScrollY={window.scrollY} |
| 100 | + initialQuery={initialQuery} |
| 101 | + isAskAiActive={isAskAiActive} |
| 102 | + canHandleAskAi={true} |
| 103 | + onClose={closeModal} |
| 104 | + onAskAiToggle={toggleAskAi} |
| 105 | + />, |
| 106 | + searchContainer.current, |
| 107 | + )} |
| 108 | + </> |
| 109 | + ); |
| 110 | +} |
| 111 | + |
| 112 | +export default function DynamicImportModal(): JSX.Element { |
| 113 | + return <DocSearch />; |
| 114 | +} |
0 commit comments