SearchModal.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import React, {
  2. useState, useCallback, useEffect,
  3. } from 'react';
  4. import Downshift, { type DownshiftState, type StateChangeOptions } from 'downshift';
  5. import { useRouter } from 'next/router';
  6. import { Modal, ModalBody } from 'reactstrap';
  7. import type { DownshiftItem } from '../interfaces/downshift';
  8. import { useSearchModal } from '../stores/search';
  9. import { SearchForm } from './SearchForm';
  10. import { SearchHelp } from './SearchHelp';
  11. import { SearchMethodMenuItem } from './SearchMethodMenuItem';
  12. import { SearchResultMenuItem } from './SearchResultMenuItem';
  13. const SearchModal = (): JSX.Element => {
  14. const [searchKeyword, setSearchKeyword] = useState('');
  15. const { data: searchModalData, close: closeSearchModal } = useSearchModal();
  16. const router = useRouter();
  17. const changeSearchTextHandler = useCallback((searchText: string) => {
  18. setSearchKeyword(searchText);
  19. }, []);
  20. const selectSearchMenuItemHandler = useCallback((selectedItem: DownshiftItem) => {
  21. router.push(selectedItem.url);
  22. closeSearchModal();
  23. }, [closeSearchModal, router]);
  24. const submitHandler = useCallback(() => {
  25. router.push(`/_search?q=${searchKeyword}`);
  26. closeSearchModal();
  27. }, [closeSearchModal, router, searchKeyword]);
  28. const stateReducer = (state: DownshiftState<DownshiftItem>, changes: StateChangeOptions<DownshiftItem>) => {
  29. // Do not update highlightedIndex on mouse hover
  30. if (changes.type === Downshift.stateChangeTypes.itemMouseEnter) {
  31. return {
  32. ...changes,
  33. highlightedIndex: state.highlightedIndex,
  34. };
  35. }
  36. return changes;
  37. };
  38. useEffect(() => {
  39. if (!searchModalData?.isOpened) {
  40. setSearchKeyword('');
  41. }
  42. }, [searchModalData?.isOpened]);
  43. return (
  44. <Modal size="lg" isOpen={searchModalData?.isOpened ?? false} toggle={closeSearchModal}>
  45. <ModalBody>
  46. <Downshift
  47. onSelect={selectSearchMenuItemHandler}
  48. stateReducer={stateReducer}
  49. defaultIsOpen
  50. >
  51. {({
  52. getRootProps,
  53. getInputProps,
  54. getItemProps,
  55. getMenuProps,
  56. highlightedIndex,
  57. }) => (
  58. <div {...getRootProps({}, { suppressRefError: true })}>
  59. <div className="text-muted d-flex justify-content-center align-items-center p-1">
  60. <span className="material-symbols-outlined fs-4 me-3">search</span>
  61. <SearchForm
  62. searchKeyword={searchKeyword}
  63. onChange={changeSearchTextHandler}
  64. onSubmit={submitHandler}
  65. getInputProps={getInputProps}
  66. />
  67. <button
  68. type="button"
  69. className="btn border-0 d-flex justify-content-center p-0"
  70. onClick={closeSearchModal}
  71. >
  72. <span className="material-symbols-outlined fs-4 ms-3">close</span>
  73. </button>
  74. </div>
  75. <ul {...getMenuProps()} className="list-unstyled">
  76. <div className="border-top mt-3 mb-2" />
  77. <SearchMethodMenuItem
  78. activeIndex={highlightedIndex}
  79. searchKeyword={searchKeyword}
  80. getItemProps={getItemProps}
  81. />
  82. <div className="border-top mt-2 mb-2" />
  83. <SearchResultMenuItem
  84. activeIndex={highlightedIndex}
  85. searchKeyword={searchKeyword}
  86. getItemProps={getItemProps}
  87. />
  88. </ul>
  89. </div>
  90. )}
  91. </Downshift>
  92. <SearchHelp />
  93. </ModalBody>
  94. </Modal>
  95. );
  96. };
  97. export default SearchModal;