|
|
@@ -0,0 +1,113 @@
|
|
|
+
|
|
|
+import React, {
|
|
|
+ useState, useCallback, useEffect,
|
|
|
+} from 'react';
|
|
|
+
|
|
|
+import Downshift, { type DownshiftState, type StateChangeOptions } from 'downshift';
|
|
|
+import { useRouter } from 'next/router';
|
|
|
+import { Modal, ModalBody } from 'reactstrap';
|
|
|
+
|
|
|
+import type { DownshiftItem } from '../interfaces/downshift';
|
|
|
+import { useSearchModal } from '../stores/search';
|
|
|
+
|
|
|
+import { SearchForm } from './SearchForm';
|
|
|
+import { SearchHelp } from './SearchHelp';
|
|
|
+import { SearchMethodMenuItem } from './SearchMethodMenuItem';
|
|
|
+import { SearchResultMenuItem } from './SearchResultMenuItem';
|
|
|
+
|
|
|
+const SearchModal = (): JSX.Element => {
|
|
|
+ const [searchKeyword, setSearchKeyword] = useState('');
|
|
|
+
|
|
|
+ const { data: searchModalData, close: closeSearchModal } = useSearchModal();
|
|
|
+
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ const changeSearchTextHandler = useCallback((searchText: string) => {
|
|
|
+ setSearchKeyword(searchText);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const selectSearchMenuItemHandler = useCallback((selectedItem: DownshiftItem) => {
|
|
|
+ router.push(selectedItem.url);
|
|
|
+ closeSearchModal();
|
|
|
+ }, [closeSearchModal, router]);
|
|
|
+
|
|
|
+ const submitHandler = useCallback(() => {
|
|
|
+ router.push(`/_search?q=${searchKeyword}`);
|
|
|
+ closeSearchModal();
|
|
|
+ }, [closeSearchModal, router, searchKeyword]);
|
|
|
+
|
|
|
+ const stateReducer = (state: DownshiftState<DownshiftItem>, changes: StateChangeOptions<DownshiftItem>) => {
|
|
|
+ // Do not update highlightedIndex on mouse hover
|
|
|
+ if (changes.type === Downshift.stateChangeTypes.itemMouseEnter) {
|
|
|
+ return {
|
|
|
+ ...changes,
|
|
|
+ highlightedIndex: state.highlightedIndex,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return changes;
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (!searchModalData?.isOpened) {
|
|
|
+ setSearchKeyword('');
|
|
|
+ }
|
|
|
+ }, [searchModalData?.isOpened]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Modal size="lg" isOpen={searchModalData?.isOpened ?? false} toggle={closeSearchModal}>
|
|
|
+ <ModalBody>
|
|
|
+ <Downshift
|
|
|
+ onSelect={selectSearchMenuItemHandler}
|
|
|
+ stateReducer={stateReducer}
|
|
|
+ defaultIsOpen
|
|
|
+ >
|
|
|
+ {({
|
|
|
+ getRootProps,
|
|
|
+ getInputProps,
|
|
|
+ getItemProps,
|
|
|
+ getMenuProps,
|
|
|
+ highlightedIndex,
|
|
|
+ }) => (
|
|
|
+ <div {...getRootProps({}, { suppressRefError: true })}>
|
|
|
+ <div className="text-muted d-flex justify-content-center align-items-center p-1">
|
|
|
+ <span className="material-symbols-outlined fs-4 me-3">search</span>
|
|
|
+ <SearchForm
|
|
|
+ searchKeyword={searchKeyword}
|
|
|
+ onChange={changeSearchTextHandler}
|
|
|
+ onSubmit={submitHandler}
|
|
|
+ getInputProps={getInputProps}
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ className="btn border-0 d-flex justify-content-center p-0"
|
|
|
+ onClick={closeSearchModal}
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined fs-4 ms-3">close</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <ul {...getMenuProps()} className="list-unstyled">
|
|
|
+ <div className="border-top mt-3 mb-2" />
|
|
|
+ <SearchMethodMenuItem
|
|
|
+ activeIndex={highlightedIndex}
|
|
|
+ searchKeyword={searchKeyword}
|
|
|
+ getItemProps={getItemProps}
|
|
|
+ />
|
|
|
+ <div className="border-top mt-2 mb-2" />
|
|
|
+ <SearchResultMenuItem
|
|
|
+ activeIndex={highlightedIndex}
|
|
|
+ searchKeyword={searchKeyword}
|
|
|
+ getItemProps={getItemProps}
|
|
|
+ />
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Downshift>
|
|
|
+ <SearchHelp />
|
|
|
+ </ModalBody>
|
|
|
+ </Modal>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default SearchModal;
|