SearchControl.tsx 5.5 KB

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