SearchControl.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import React, { type JSX, useCallback, useEffect, useState } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import { Collapse } from 'reactstrap';
  4. import { SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
  5. import type { ISearchConditions, ISearchConfigurations } from '~/stores/search';
  6. import { SearchModalTriggerinput } from './SearchModalTriggerinput';
  7. import { SearchOptionModalLazyLoaded } from './SearchOptionModal';
  8. import SortControl from './SortControl';
  9. type Props = {
  10. isEnableSort: boolean;
  11. isEnableFilter: boolean;
  12. isHidingUserPages: boolean;
  13. initialSearchConditions: Partial<ISearchConditions>;
  14. onSearchInvoked?: (
  15. keyword: string,
  16. configurations: Partial<ISearchConfigurations>,
  17. ) => void;
  18. extraControls: React.ReactNode;
  19. collapseContents?: React.ReactNode;
  20. isCollapsed?: boolean;
  21. };
  22. const SearchControl = React.memo((props: Props): JSX.Element => {
  23. const {
  24. isEnableSort,
  25. isEnableFilter,
  26. isHidingUserPages,
  27. initialSearchConditions,
  28. onSearchInvoked,
  29. extraControls,
  30. collapseContents,
  31. isCollapsed,
  32. } = props;
  33. const keywordOnInit = initialSearchConditions.keyword ?? '';
  34. const [keyword, setKeyword] = useState(keywordOnInit);
  35. const [sort, setSort] = useState<SORT_AXIS>(
  36. initialSearchConditions.sort ?? SORT_AXIS.RELATION_SCORE,
  37. );
  38. const [order, setOrder] = useState<SORT_ORDER>(
  39. initialSearchConditions.order ?? SORT_ORDER.DESC,
  40. );
  41. const [includeUserPages, setIncludeUserPages] = useState(
  42. initialSearchConditions.includeUserPages ?? false,
  43. );
  44. const [includeTrashPages, setIncludeTrashPages] = useState(
  45. initialSearchConditions.includeTrashPages ?? false,
  46. );
  47. const [isFileterOptionModalShown, setIsFileterOptionModalShown] =
  48. useState(false);
  49. const { t } = useTranslation('');
  50. const searchBySearchControlHandler = useCallback(
  51. (newKeyword: string) => {
  52. setKeyword(newKeyword);
  53. onSearchInvoked?.(newKeyword, {
  54. sort,
  55. order,
  56. includeUserPages,
  57. includeTrashPages,
  58. });
  59. },
  60. [includeTrashPages, includeUserPages, onSearchInvoked, order, sort],
  61. );
  62. const changeSortHandler = useCallback(
  63. (nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => {
  64. setSort(nextSort);
  65. setOrder(nextOrder);
  66. onSearchInvoked?.(keyword, {
  67. sort: nextSort,
  68. order: nextOrder,
  69. includeUserPages,
  70. includeTrashPages,
  71. });
  72. },
  73. [includeTrashPages, includeUserPages, keyword, onSearchInvoked],
  74. );
  75. const changeIncludeUserPagesHandler = useCallback(
  76. (include: boolean) => {
  77. setIncludeUserPages(include);
  78. onSearchInvoked?.(keyword, {
  79. sort,
  80. order,
  81. includeUserPages: include,
  82. includeTrashPages,
  83. });
  84. },
  85. [includeTrashPages, keyword, onSearchInvoked, order, sort],
  86. );
  87. const changeIncludeTrashPagesHandler = useCallback(
  88. (include: boolean) => {
  89. setIncludeTrashPages(include);
  90. onSearchInvoked?.(keyword, {
  91. sort,
  92. order,
  93. includeUserPages,
  94. includeTrashPages: include,
  95. });
  96. },
  97. [includeUserPages, keyword, onSearchInvoked, order, sort],
  98. );
  99. useEffect(() => {
  100. setKeyword(keywordOnInit);
  101. }, [keywordOnInit]);
  102. return (
  103. <div className="shadow-sm">
  104. <div className="grw-search-page-nav d-flex py-3 align-items-center">
  105. <div className="flex-grow-1 mx-4">
  106. <SearchModalTriggerinput
  107. keywordOnInit={keyword}
  108. onSearchInvoked={searchBySearchControlHandler}
  109. />
  110. </div>
  111. </div>
  112. {/* TODO: replace the following elements deleteAll button , relevance button and include specificPath button component */}
  113. <div className="search-control d-flex align-items-center py-md-2 py-3 px-md-4 px-3 border-bottom border-gray">
  114. {/* sort option */}
  115. {isEnableSort && (
  116. <div className="flex-grow-1">
  117. <SortControl
  118. sort={sort}
  119. order={order}
  120. onChange={changeSortHandler}
  121. />
  122. </div>
  123. )}
  124. {/* filter option */}
  125. {isEnableFilter && (
  126. <>
  127. <div className="d-lg-none">
  128. <button
  129. type="button"
  130. className="btn"
  131. onClick={() => setIsFileterOptionModalShown(true)}
  132. >
  133. <span className="material-symbols-outlined">tune</span>
  134. </button>
  135. </div>
  136. <div className="d-none d-lg-flex align-items-center search-control-include-options">
  137. {isHidingUserPages === false && (
  138. <div className="px-2 py-1">
  139. <div className="form-check form-check-succsess">
  140. <input
  141. className="form-check-input me-2"
  142. type="checkbox"
  143. id="flexCheckDefault"
  144. defaultChecked={includeUserPages}
  145. onChange={(e) =>
  146. changeIncludeUserPagesHandler(e.target.checked)
  147. }
  148. />
  149. <label
  150. className="form-label form-check-label mb-0 d-flex align-items-center text-secondary with-no-font-weight"
  151. htmlFor="flexCheckDefault"
  152. >
  153. {t('Include Subordinated Target Page', {
  154. target: '/user',
  155. })}
  156. </label>
  157. </div>
  158. </div>
  159. )}
  160. <div className="px-2 py-1">
  161. <div className="form-check form-check-succsess">
  162. <input
  163. className="form-check-input me-2"
  164. type="checkbox"
  165. id="flexCheckChecked"
  166. checked={includeTrashPages}
  167. onChange={(e) =>
  168. changeIncludeTrashPagesHandler(e.target.checked)
  169. }
  170. />
  171. <label
  172. className="form-label form-check-label mb-0 d-flex align-items-center text-secondary with-no-font-weight"
  173. htmlFor="flexCheckChecked"
  174. >
  175. {t('Include Subordinated Target Page', {
  176. target: '/trash',
  177. })}
  178. </label>
  179. </div>
  180. </div>
  181. </div>
  182. </>
  183. )}
  184. {extraControls}
  185. </div>
  186. {collapseContents != null && (
  187. <Collapse isOpen={isCollapsed}>{collapseContents}</Collapse>
  188. )}
  189. <SearchOptionModalLazyLoaded
  190. isOpen={isFileterOptionModalShown || false}
  191. onClose={() => setIsFileterOptionModalShown(false)}
  192. isHidingUserPages={isHidingUserPages}
  193. includeUserPages={includeUserPages}
  194. includeTrashPages={includeTrashPages}
  195. onIncludeUserPagesSwitched={setIncludeUserPages}
  196. onIncludeTrashPagesSwitched={setIncludeTrashPages}
  197. />
  198. </div>
  199. );
  200. });
  201. SearchControl.displayName = 'SearchControl';
  202. export default SearchControl;