SearchControl.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React, {
  2. useCallback, useEffect, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
  6. import { ISearchConditions, ISearchConfigurations } from '~/stores/search';
  7. import SearchForm from '../SearchForm';
  8. import SearchOptionModal from './SearchOptionModal';
  9. import SortControl from './SortControl';
  10. type Props = {
  11. isSearchServiceReachable: boolean,
  12. isEnableSort: boolean,
  13. isEnableFilter: boolean,
  14. initialSearchConditions: Partial<ISearchConditions>,
  15. onSearchInvoked: (keyword: string, configurations: Partial<ISearchConfigurations>) => void,
  16. allControl: React.ReactNode,
  17. }
  18. const SearchControl = React.memo((props: Props): JSX.Element => {
  19. const {
  20. isSearchServiceReachable,
  21. isEnableSort,
  22. isEnableFilter,
  23. initialSearchConditions,
  24. onSearchInvoked,
  25. allControl,
  26. } = props;
  27. const [keyword, setKeyword] = useState(initialSearchConditions.keyword ?? '');
  28. const [sort, setSort] = useState<SORT_AXIS>(initialSearchConditions.sort ?? SORT_AXIS.RELATION_SCORE);
  29. const [order, setOrder] = useState<SORT_ORDER>(initialSearchConditions.order ?? SORT_ORDER.DESC);
  30. const [includeUserPages, setIncludeUserPages] = useState(initialSearchConditions.includeUserPages ?? false);
  31. const [includeTrashPages, setIncludeTrashPages] = useState(initialSearchConditions.includeTrashPages ?? false);
  32. const [isFileterOptionModalShown, setIsFileterOptionModalShown] = useState(false);
  33. const { t } = useTranslation('');
  34. const invokeSearch = useCallback(() => {
  35. if (onSearchInvoked == null) {
  36. return;
  37. }
  38. onSearchInvoked(keyword, {
  39. sort, order, includeUserPages, includeTrashPages,
  40. });
  41. }, [keyword, sort, order, includeTrashPages, includeUserPages, onSearchInvoked]);
  42. const searchFormSubmittedHandler = useCallback((input: string) => {
  43. setKeyword(input);
  44. }, []);
  45. const changeSortHandler = useCallback((nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => {
  46. setSort(nextSort);
  47. setOrder(nextOrder);
  48. }, []);
  49. useEffect(() => {
  50. invokeSearch();
  51. }, [invokeSearch]);
  52. useEffect(() => {
  53. setKeyword(initialSearchConditions.keyword ?? '');
  54. }, [initialSearchConditions.keyword]);
  55. return (
  56. <div className="position-sticky sticky-top shadow-sm">
  57. <div className="grw-search-page-nav d-flex py-3 align-items-center">
  58. <div className="flex-grow-1 mx-4">
  59. <SearchForm
  60. isSearchServiceReachable={isSearchServiceReachable}
  61. keywordOnInit={keyword}
  62. disableIncrementalSearch
  63. onSubmit={searchFormSubmittedHandler}
  64. />
  65. </div>
  66. {/* sort option: show when screen is larger than lg */}
  67. {isEnableSort && (
  68. <div className="mr-4 d-lg-flex d-none">
  69. <SortControl
  70. sort={sort}
  71. order={order}
  72. onChange={changeSortHandler}
  73. />
  74. </div>
  75. )}
  76. </div>
  77. {/* TODO: replace the following elements deleteAll button , relevance button and include specificPath button component */}
  78. <div className="search-control d-flex align-items-center py-md-2 py-3 px-md-4 px-3 border-bottom border-gray">
  79. <div className="d-flex">
  80. {allControl}
  81. </div>
  82. {/* sort option: show when screen is smaller than lg */}
  83. {isEnableSort && (
  84. <div className="mr-md-4 mr-2 d-flex d-lg-none ml-auto">
  85. <SortControl
  86. sort={sort}
  87. order={order}
  88. onChange={changeSortHandler}
  89. />
  90. </div>
  91. )}
  92. {/* filter option */}
  93. {isEnableFilter && (
  94. <>
  95. <div className="d-lg-none">
  96. <button
  97. type="button"
  98. className="btn"
  99. onClick={() => setIsFileterOptionModalShown(true)}
  100. >
  101. <i className="icon-equalizer"></i>
  102. </button>
  103. </div>
  104. <div className="d-none d-lg-flex align-items-center ml-auto search-control-include-options">
  105. <div className="border rounded px-2 py-1 mr-3">
  106. <div className="custom-control custom-checkbox custom-checkbox-succsess">
  107. <input
  108. className="custom-control-input mr-2"
  109. type="checkbox"
  110. id="flexCheckDefault"
  111. defaultChecked={includeUserPages}
  112. onChange={e => setIncludeUserPages(e.target.checked)}
  113. />
  114. <label className="custom-control-label mb-0 d-flex align-items-center text-secondary with-no-font-weight" htmlFor="flexCheckDefault">
  115. {t('Include Subordinated Target Page', { target: '/user' })}
  116. </label>
  117. </div>
  118. </div>
  119. <div className="border rounded px-2 py-1">
  120. <div className="custom-control custom-checkbox custom-checkbox-succsess">
  121. <input
  122. className="custom-control-input mr-2"
  123. type="checkbox"
  124. id="flexCheckChecked"
  125. checked={includeTrashPages}
  126. onChange={e => setIncludeTrashPages(e.target.checked)}
  127. />
  128. <label
  129. className="custom-control-label
  130. d-flex align-items-center text-secondary with-no-font-weight"
  131. htmlFor="flexCheckChecked"
  132. >
  133. {t('Include Subordinated Target Page', { target: '/trash' })}
  134. </label>
  135. </div>
  136. </div>
  137. </div>
  138. </>
  139. )}
  140. </div>
  141. <SearchOptionModal
  142. isOpen={isFileterOptionModalShown || false}
  143. onClose={() => setIsFileterOptionModalShown(false)}
  144. includeUserPages={includeUserPages}
  145. includeTrashPages={includeTrashPages}
  146. onIncludeUserPagesSwitched={setIncludeUserPages}
  147. onIncludeTrashPagesSwitched={setIncludeTrashPages}
  148. />
  149. </div>
  150. );
  151. });
  152. SearchControl.displayName = 'SearchControl';
  153. export default SearchControl;