Просмотр исходного кода

Merge pull request #5583 from weseek/fix/search-typeahead

imprv: SearchTypeahead
Yuki Takei 4 лет назад
Родитель
Сommit
f4b57574bf
31 измененных файлов с 358 добавлено и 356 удалено
  1. 1 1
      packages/app/package.json
  2. 3 1
      packages/app/src/client/interfaces/react-bootstrap-typeahead.ts
  3. 1 1
      packages/app/src/components/Navbar/GlobalSearch.tsx
  4. 3 3
      packages/app/src/components/PageCreateModal.jsx
  5. 3 3
      packages/app/src/components/PageEditor/LinkEditModal.jsx
  6. 1 1
      packages/app/src/components/PageEditor/PreviewWithSuspense.jsx
  7. 4 23
      packages/app/src/components/PagePathAutoComplete.jsx
  8. 14 32
      packages/app/src/components/SearchForm.tsx
  9. 1 1
      packages/app/src/components/SearchPage/SearchControl.tsx
  10. 142 127
      packages/app/src/components/SearchTypeahead.tsx
  11. 4 4
      packages/app/src/stores/search.tsx
  12. 15 14
      packages/app/src/styles/theme/_apply-colors-dark.scss
  13. 11 0
      packages/app/src/styles/theme/_apply-colors-light.scss
  14. 0 27
      packages/app/src/styles/theme/_apply-colors.scss
  15. 35 0
      packages/app/src/styles/theme/_reboot-bootstrap-dropdown.scss
  16. 0 4
      packages/app/src/styles/theme/antarctic.scss
  17. 0 1
      packages/app/src/styles/theme/blackboard.scss
  18. 1 3
      packages/app/src/styles/theme/christmas.scss
  19. 0 7
      packages/app/src/styles/theme/default.scss
  20. 0 5
      packages/app/src/styles/theme/fire-red.scss
  21. 0 5
      packages/app/src/styles/theme/future.scss
  22. 0 1
      packages/app/src/styles/theme/halloween.scss
  23. 2 5
      packages/app/src/styles/theme/hufflepuff.scss
  24. 0 2
      packages/app/src/styles/theme/island.scss
  25. 0 5
      packages/app/src/styles/theme/jade-green.scss
  26. 0 2
      packages/app/src/styles/theme/kibela.scss
  27. 0 9
      packages/app/src/styles/theme/mono-blue.scss
  28. 0 5
      packages/app/src/styles/theme/nature.scss
  29. 0 2
      packages/app/src/styles/theme/spring.scss
  30. 0 2
      packages/app/src/styles/theme/wood.scss
  31. 117 60
      yarn.lock

+ 1 - 1
packages/app/package.json

@@ -223,7 +223,7 @@
     "postcss-loader": "^3.0.0",
     "prettier": "^1.19.1",
     "react": "^16.8.3",
-    "react-bootstrap-typeahead": "^3.4.7",
+    "react-bootstrap-typeahead": "^5.2.2",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
     "react-dom": "^16.8.3",

+ 3 - 1
packages/app/src/client/interfaces/react-bootstrap-typeahead.ts

@@ -1,13 +1,15 @@
-// https://github.com/ericgio/react-bootstrap-typeahead/blob/3.x/docs/Props.md
+// https://github.com/ericgio/react-bootstrap-typeahead/blob/5.x/docs/API.md
 export type TypeaheadProps = {
   dropup?: boolean,
   emptyLabel?: string,
   placeholder?: string,
   autoFocus?: boolean,
+  inputProps?: unknown,
 
   onChange?: (data: unknown[]) => void,
   onBlur?: () => void,
   onFocus?: () => void,
+  onSearch?: (text: string) => void,
   onInputChange?: (text: string) => void,
   onKeyDown?: (input: string) => void,
 };

+ 1 - 1
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -40,7 +40,7 @@ const GlobalSearch: FC<Props> = (props: Props) => {
 
     // navigate to page
     if (page != null) {
-      window.location.href = page._id;
+      window.location.href = `/${page._id}`;
     }
   }, []);
 

+ 3 - 3
packages/app/src/components/PageCreateModal.jsx

@@ -1,6 +1,6 @@
 
 import React, {
-  useEffect, useState, useMemo,
+  useEffect, useState, useMemo, useCallback,
 } from 'react';
 import PropTypes from 'prop-types';
 
@@ -135,8 +135,8 @@ const PageCreateModal = (props) => {
     setPageNameInput(value);
   }
 
-  function ppacSubmitHandler() {
-    createInputPage();
+  function ppacSubmitHandler(input) {
+    redirectToEditor(input);
   }
 
   /**

+ 3 - 3
packages/app/src/components/PageEditor/LinkEditModal.jsx

@@ -37,7 +37,7 @@ class LinkEditModal extends React.PureComponent {
       linkInputValue: '',
       labelInputValue: '',
       linkerType: Linker.types.markdownLink,
-      markdown: '',
+      markdown: null,
       previewError: '',
       permalink: '',
       isPreviewOpen: false,
@@ -152,7 +152,7 @@ class LinkEditModal extends React.PureComponent {
   async setMarkdown() {
     const { t } = this.props;
     const path = this.state.linkInputValue;
-    let markdown = '';
+    let markdown = null;
     let permalink = '';
     let previewError = '';
 
@@ -204,7 +204,7 @@ class LinkEditModal extends React.PureComponent {
   handleChangeTypeahead(selected) {
     const pageWithMeta = selected[0];
     if (pageWithMeta != null) {
-      const page = pageWithMeta.pageData;
+      const page = pageWithMeta.data;
       const permalink = `${window.location.origin}/${page.id}`;
       this.setState({ linkInputValue: page.path, permalink });
     }

+ 1 - 1
packages/app/src/components/PageEditor/PreviewWithSuspense.jsx

@@ -5,7 +5,7 @@ import Preview from './Preview';
 import { withLoadingSppiner } from '../SuspenseUtils';
 
 function PagePreview(props) {
-  if (props.markdown === '') {
+  if (props.markdown == null) {
     if (props.error !== '') {
       return props.error;
     }

+ 4 - 23
packages/app/src/components/PagePathAutoComplete.jsx

@@ -8,27 +8,9 @@ import SearchTypeahead from './SearchTypeahead';
 const PagePathAutoComplete = (props) => {
 
   const {
-    addTrailingSlash, onSubmit, onInputChange, initializedPath,
+    addTrailingSlash, initializedPath,
   } = props;
 
-  function inputChangeHandler(pages) {
-    if (onInputChange == null) {
-      return;
-    }
-    const page = pages[0]; // should be single page selected
-
-    if (page != null) {
-      onInputChange(page.path);
-    }
-  }
-
-  function submitHandler() {
-    if (onSubmit == null) {
-      return;
-    }
-    onSubmit();
-  }
-
   function getKeywordOnInit(path) {
     if (path == null) {
       return;
@@ -40,10 +22,8 @@ const PagePathAutoComplete = (props) => {
 
   return (
     <SearchTypeahead
-      onSubmit={submitHandler}
-      onChange={inputChangeHandler}
-      onInputChange={props.onInputChange}
-      inputName="new_path"
+      {...props}
+      inputProps={{ name: 'new_path' }}
       placeholder="Input page path"
       keywordOnInit={getKeywordOnInit(initializedPath)}
       autoFocus={props.autoFocus}
@@ -56,6 +36,7 @@ PagePathAutoComplete.propTypes = {
   initializedPath:  PropTypes.string,
   addTrailingSlash: PropTypes.bool,
 
+  onChange:         PropTypes.func,
   onSubmit:         PropTypes.func,
   onInputChange:    PropTypes.func,
   autoFocus:        PropTypes.bool,

+ 14 - 32
packages/app/src/components/SearchForm.tsx

@@ -5,6 +5,7 @@ import React, {
 import { useTranslation } from 'react-i18next';
 
 import { IFocusable } from '~/client/interfaces/focusable';
+import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
 
@@ -13,13 +14,12 @@ import SearchTypeahead from './SearchTypeahead';
 
 type SearchFormHelpProps = {
   isReachable: boolean,
-  isShownHelp: boolean,
 }
 
-const SearchFormHelp: FC<SearchFormHelpProps> = (props: SearchFormHelpProps) => {
+const SearchFormHelp: FC<SearchFormHelpProps> = React.memo((props: SearchFormHelpProps) => {
   const { t } = useTranslation();
 
-  const { isReachable, isShownHelp } = props;
+  const { isReachable } = props;
 
   if (!isReachable) {
     return (
@@ -30,10 +30,6 @@ const SearchFormHelp: FC<SearchFormHelpProps> = (props: SearchFormHelpProps) =>
     );
   }
 
-  if (!isShownHelp) {
-    return <></>;
-  }
-
   return (
     <table className="table grw-search-table search-help m-0">
       <caption className="text-left text-primary p-2">
@@ -77,33 +73,29 @@ const SearchFormHelp: FC<SearchFormHelpProps> = (props: SearchFormHelpProps) =>
       </tbody>
     </table>
   );
-};
+});
 
 
-type Props = {
+type Props = TypeaheadProps & {
   isSearchServiceReachable: boolean,
 
-  dropup?: boolean,
-  keyword?: string,
+  keywordOnInit?: string,
   disableIncrementalSearch?: boolean,
   onChange?: (data: IPageWithMeta<IPageSearchMeta>[]) => void,
-  onBlur?: () => void,
-  onFocus?: () => void,
   onSubmit?: (input: string) => void,
-  onInputChange?: (text: string) => void,
 };
 
 
 const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
   const { t } = useTranslation();
   const {
-    isSearchServiceReachable, dropup,
+    isSearchServiceReachable,
+    keywordOnInit,
     disableIncrementalSearch,
-    onChange, onBlur, onFocus, onSubmit, onInputChange,
+    dropup, onChange, onBlur, onFocus, onSubmit, onInputChange,
   } = props;
 
   const [searchError, setSearchError] = useState<Error | null>(null);
-  const [isShownHelp, setShownHelp] = useState(false);
 
   const searchTyheaheadRef = useRef<IFocusable>(null);
 
@@ -131,25 +123,15 @@ const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, r
       dropup={dropup}
       emptyLabel={emptyLabel}
       placeholder={placeholder}
-      disableIncrementalSearch={disableIncrementalSearch}
       onChange={onChange}
       onSubmit={onSubmit}
       onInputChange={onInputChange}
       onSearchError={err => setSearchError(err)}
-      onBlur={() => {
-        setShownHelp(false);
-        if (onBlur != null) {
-          onBlur();
-        }
-      }}
-      onFocus={() => {
-        setShownHelp(true);
-        if (onFocus != null) {
-          onFocus();
-        }
-      }}
-      helpElement={<SearchFormHelp isShownHelp={isShownHelp} isReachable={isSearchServiceReachable} />}
-      keywordOnInit={props.keyword}
+      onBlur={onBlur}
+      onFocus={onFocus}
+      keywordOnInit={keywordOnInit}
+      disableIncrementalSearch={disableIncrementalSearch}
+      helpElement={<SearchFormHelp isReachable={isSearchServiceReachable} />}
     />
   );
 };

+ 1 - 1
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -66,7 +66,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
         <div className="flex-grow-1 mx-4">
           <SearchForm
             isSearchServiceReachable={isSearchServiceReachable}
-            keyword={keyword}
+            keywordOnInit={keyword}
             disableIncrementalSearch
             onSubmit={searchFormSubmittedHandler}
           />

+ 142 - 127
packages/app/src/components/SearchTypeahead.tsx

@@ -1,32 +1,33 @@
 import React, {
   FC, ForwardRefRenderFunction, forwardRef, useImperativeHandle,
-  KeyboardEvent, useCallback, useRef, useState, MouseEvent,
+  KeyboardEvent, useCallback, useRef, useState, MouseEvent, useEffect,
 } from 'react';
 
-import { AsyncTypeahead } from 'react-bootstrap-typeahead';
+import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
-import { apiGet } from '~/client/util/apiv1-client';
-import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
+import { IPageSearchMeta } from '~/interfaces/search';
 import { IPageWithMeta } from '~/interfaces/page';
+import { useSWRxFullTextSearch } from '~/stores/search';
 
 
 type ResetFormButtonProps = {
-  keywordOnInit: string,
-  input: string,
+  input?: string,
   onReset: (e: MouseEvent<HTMLButtonElement>) => void,
 }
 
 const ResetFormButton: FC<ResetFormButtonProps> = (props: ResetFormButtonProps) => {
-  const isHidden = props.input.length === 0;
+  const { input, onReset } = props;
+
+  const isHidden = input == null || input.length === 0;
 
   return isHidden ? (
     <span />
   ) : (
-    <button type="button" className="btn btn-outline-secondary search-clear text-muted border-0" onMouseDown={props.onReset}>
+    <button type="button" className="btn btn-outline-secondary search-clear text-muted border-0" onMouseDown={onReset}>
       <i className="icon-close" />
     </button>
   );
@@ -34,117 +35,79 @@ const ResetFormButton: FC<ResetFormButtonProps> = (props: ResetFormButtonProps)
 
 
 type Props = TypeaheadProps & {
-  onSearchSuccess?: (res: IPageWithMeta<IPageSearchMeta>[]) => void,
   onSearchError?: (err: Error) => void,
   onSubmit?: (input: string) => void,
-  inputName?: string,
   keywordOnInit?: string,
   disableIncrementalSearch?: boolean,
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  helpElement?: any,
+  helpElement?: React.ReactNode,
 };
 
 // see https://github.com/ericgio/react-bootstrap-typeahead/issues/266#issuecomment-414987723
 type TypeaheadInstance = {
   clear: () => void,
   focus: () => void,
-  setState: ({ text: string }) => void,
-}
-type TypeaheadInstanceFactory = {
-  getInstance: () => TypeaheadInstance,
+  toggleMenu: () => void,
+  state: { selected: IPageWithMeta<IPageSearchMeta>[] }
 }
 
 const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
   const {
-    onSearchSuccess, onSearchError, onInputChange, onSubmit,
-    emptyLabel, helpElement, keywordOnInit, disableIncrementalSearch,
+    onSearchError, onSearch, onInputChange, onChange, onSubmit,
+    inputProps, keywordOnInit, disableIncrementalSearch, helpElement,
+    onBlur, onFocus,
   } = props;
 
-  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-  const [input, setInput] = useState(props.keywordOnInit!);
-  const [pages, setPages] = useState<IPageWithMeta<IPageSearchMeta>[]>();
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const [searchError, setSearchError] = useState<Error | null>(null);
-  const [isLoading, setLoading] = useState(false);
+  const [input, setInput] = useState(keywordOnInit);
+  const [searchKeyword, setSearchKeyword] = useState('');
+  const [isForcused, setFocused] = useState(false);
+
+  const { data: searchResult, error: searchError } = useSWRxFullTextSearch(
+    disableIncrementalSearch ? null : searchKeyword,
+    { limit: 10 },
+  );
 
-  const typeaheadRef = useRef<TypeaheadInstanceFactory>(null);
+  const typeaheadRef = useRef<TypeaheadInstance>(null);
 
   const focusToTypeahead = () => {
-    const instance = typeaheadRef.current?.getInstance();
+    const instance = typeaheadRef.current;
     if (instance != null) {
       instance.focus();
     }
   };
 
-  // publish focus()
-  useImperativeHandle(ref, () => ({
-    focus: focusToTypeahead,
-  }));
-
-  const changeKeyword = (text: string | undefined) => {
-    const instance = typeaheadRef.current?.getInstance();
+  const clearTypeahead = () => {
+    const instance = typeaheadRef.current;
     if (instance != null) {
       instance.clear();
-      instance.setState({ text });
     }
   };
 
-  const resetForm = (e: MouseEvent<HTMLButtonElement>) => {
+  // publish focus()
+  useImperativeHandle(ref, () => ({
+    focus: focusToTypeahead,
+  }));
+
+  const resetForm = useCallback((e: MouseEvent<HTMLButtonElement>) => {
     e.preventDefault();
 
     setInput('');
-    changeKeyword('');
-    setPages([]);
+    setSearchKeyword('');
 
+    clearTypeahead();
     focusToTypeahead();
 
-    if (onInputChange != null) {
-      onInputChange('');
-    }
-  };
-
-  /**
-   * Callback function which is occured when search is exit successfully
-   */
-  const searchSuccessHandler = useCallback((result: IFormattedSearchResult) => {
-    const searchResultData = result.data;
-    setPages(searchResultData);
-
-    if (onSearchSuccess != null) {
-      onSearchSuccess(searchResultData);
+    if (onSearch != null) {
+      onSearch('');
     }
-  }, [onSearchSuccess]);
-
-  /**
-   * Callback function which is occured when search is exit abnormaly
-   */
-  const searchErrorHandler = useCallback((err: Error) => {
-    setSearchError(err);
+  }, [onSearch]);
 
-    if (onSearchError != null) {
-      onSearchError(err);
-    }
-  }, [onSearchError]);
+  const searchHandler = useCallback((text: string) => {
+    setSearchKeyword(text);
 
-  const search = useCallback(async(keyword: string) => {
-    if (disableIncrementalSearch || keyword === '') {
-      return;
+    if (onSearch != null) {
+      onSearch(text);
     }
-
-    setLoading(true);
-
-    try {
-      const result = await apiGet('/search', { q: keyword }) as IFormattedSearchResult;
-      searchSuccessHandler(result);
-    }
-    catch (err) {
-      searchErrorHandler(err);
-    }
-    finally {
-      setLoading(false);
-    }
-
-  }, [disableIncrementalSearch, searchErrorHandler, searchSuccessHandler]);
+  }, [onSearch]);
 
   const inputChangeHandler = useCallback((text: string) => {
     setInput(text);
@@ -152,53 +115,98 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
     if (onInputChange != null) {
       onInputChange(text);
     }
+  }, [onInputChange]);
+
+  /* -------------------------------------------------------------------------------------------------------
+   *
+   * Dirty hack for https://github.com/ericgio/react-bootstrap-typeahead/issues/492 -- 2022.03.22 Yuki Takei
+   *
+   * 1. Schedule to submit with delay when Enter key downed
+   * 2. Fire onChange and cancel the schedule to submit if change event occured
+   * 3. Fire onSubmit if the schedule is not canceled
+   *
+   */
+  const DELAY_FOR_SUBMISSION = 100;
+  const timeoutIdRef = useRef<NodeJS.Timeout>();
+
+  const changeHandler = useCallback((selectedItems: IPageWithMeta<IPageSearchMeta>[]) => {
+    // cancel schedule to submit
+    if (timeoutIdRef.current != null) {
+      clearTimeout(timeoutIdRef.current);
+    }
 
-    if (text === '') {
-      setPages([]);
+    if (selectedItems.length > 0) {
+      setInput(selectedItems[0].data.path);
+
+      if (onChange != null) {
+        onChange(selectedItems);
+      }
     }
-  }, [onInputChange]);
+  }, [onChange]);
 
   const keyDownHandler = useCallback((event: KeyboardEvent) => {
     if (event.keyCode === 13) { // Enter key
-      if (onSubmit != null) {
-        onSubmit(input);
+      if (onSubmit != null && input != null && input.length > 0) {
+        // schedule to submit with 100ms delay
+        timeoutIdRef.current = setTimeout(() => onSubmit(input), DELAY_FOR_SUBMISSION);
       }
     }
   }, [input, onSubmit]);
+  /*
+   * -------------------------------------------------------------------------------------------------------
+   */
 
-  const getEmptyLabel = () => {
-    // show help element if empty
-    if (input.length === 0) {
-      return helpElement;
+  useEffect(() => {
+    if (onSearchError != null && searchError != null) {
+      onSearchError(searchError);
     }
+  }, [onSearchError, searchError]);
+
+  const labelKey = useCallback((option?: IPageWithMeta<IPageSearchMeta>) => {
+    return option?.data.path ?? '';
+  }, []);
 
-    // use props.emptyLabel as is if defined
-    if (emptyLabel !== undefined) {
-      return emptyLabel;
+  const renderMenu = useCallback((options: IPageWithMeta<IPageSearchMeta>[], menuProps) => {
+    if (!isForcused) {
+      return <></>;
     }
 
-    return <></>;
-  };
+    const isEmptyInput = input == null || input.length === 0;
+    if (isEmptyInput) {
+      if (helpElement == null) {
+        return <></>;
+      }
+
+      return (
+        <Menu {...menuProps}>
+          <div className="p-3">
+            {helpElement}
+          </div>
+        </Menu>
+      );
+    }
+
+    if (disableIncrementalSearch) {
+      return <></>;
+    }
 
-  const defaultSelected = (keywordOnInit !== '')
-    ? [{ path: keywordOnInit }]
-    : [];
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const inputProps: any = { autoComplete: 'off' };
-  if (props.inputName != null) {
-    inputProps.name = props.inputName;
-  }
-
-  const renderMenuItemChildren = (option: IPageWithMeta<IPageSearchMeta>) => {
-    const { data: pageData } = option;
     return (
-      <span>
-        <UserPicture user={pageData.lastUpdateUser} size="sm" noLink />
-        <span className="ml-1 mr-2 text-break text-wrap"><PagePathLabel path={pageData.path} /></span>
-        <PageListMeta page={pageData} />
-      </span>
+      <Menu {...menuProps}>
+        {options.map((pageWithMeta, index) => (
+          <MenuItem key={pageWithMeta.data._id} option={pageWithMeta} position={index}>
+            <span>
+              <UserPicture user={pageWithMeta.data.lastUpdateUser} size="sm" noLink />
+              <span className="ml-1 mr-2 text-break text-wrap"><PagePathLabel path={pageWithMeta.data.path} /></span>
+              <PageListMeta page={pageWithMeta.data} />
+            </span>
+          </MenuItem>
+        ))}
+      </Menu>
     );
-  };
+  }, [disableIncrementalSearch, helpElement, input, isForcused]);
+
+  const isLoading = searchResult == null && searchError == null;
+  const isOpenAlways = helpElement != null;
 
   return (
     <div className="search-typeahead">
@@ -206,28 +214,35 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
         {...props}
         id="search-typeahead-asynctypeahead"
         ref={typeaheadRef}
-        inputProps={inputProps}
+        delay={400}
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        inputProps={{ autoComplete: 'off', ...(inputProps as any ?? {}) }}
         isLoading={isLoading}
-        labelKey={data => data?.pageData?.path || keywordOnInit || ''} // https://github.com/ericgio/react-bootstrap-typeahead/blob/master/docs/Rendering.md#labelkey-stringfunction
-        minLength={0}
-        options={pages} // Search result (Some page names)
-        promptText={props.helpElement}
-        emptyLabel={disableIncrementalSearch ? null : getEmptyLabel()}
+        labelKey={labelKey}
+        defaultInputValue={keywordOnInit}
+        options={searchResult?.data} // Search result (Some page names)
         align="left"
-        onSearch={search}
+        open={isOpenAlways || undefined}
+        renderMenu={renderMenu}
+        autoFocus={props.autoFocus}
+        onChange={changeHandler}
+        onSearch={searchHandler}
         onInputChange={inputChangeHandler}
         onKeyDown={keyDownHandler}
-        renderMenuItemChildren={renderMenuItemChildren}
-        caseSensitive={false}
-        defaultSelected={defaultSelected}
-        autoFocus={props.autoFocus}
-        onBlur={props.onBlur}
-        onFocus={props.onFocus}
+        onBlur={() => {
+          setFocused(false);
+          if (onBlur != null) {
+            onBlur();
+          }
+        }}
+        onFocus={() => {
+          setFocused(true);
+          if (onFocus != null) {
+            onFocus();
+          }
+        }}
       />
       <ResetFormButton
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        keywordOnInit={props.keywordOnInit!}
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         input={input}
         onReset={resetForm}
       />

+ 4 - 4
packages/app/src/stores/search.tsx

@@ -32,7 +32,7 @@ type ISearchConfigurationsFixed = {
 }
 
 export type ISearchConditions = ISearchConfigurationsFixed & {
-  keyword: string,
+  keyword: string | null,
   rawQuery: string,
 }
 
@@ -51,7 +51,7 @@ const createSearchQuery = (keyword: string, includeTrashPages: boolean, includeU
 };
 
 export const useSWRxFullTextSearch = (
-    keyword: string, configurations: ISearchConfigurations, disableTermManager = false,
+    keyword: string | null, configurations: ISearchConfigurations, disableTermManager = false,
 ): SWRResponse<IFormattedSearchResult, Error> & { conditions: ISearchConditions } => {
   const { data: termNumber } = useFullTextSearchTermManager(disableTermManager);
 
@@ -67,10 +67,10 @@ export const useSWRxFullTextSearch = (
     includeTrashPages: includeTrashPages ?? false,
     includeUserPages: includeUserPages ?? false,
   };
-  const rawQuery = createSearchQuery(keyword, fixedConfigurations.includeTrashPages, fixedConfigurations.includeUserPages);
+  const rawQuery = createSearchQuery(keyword ?? '', fixedConfigurations.includeTrashPages, fixedConfigurations.includeUserPages);
 
   const swrResult = useSWRImmutable(
-    ['/search', keyword, fixedConfigurations, termNumber],
+    keyword == null ? null : ['/search', keyword, fixedConfigurations, termNumber],
     (endpoint, keyword, fixedConfigurations) => {
       const {
         limit, offset, sort, order,

+ 15 - 14
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -16,6 +16,13 @@ $color-tags: #949494 !default;
 $bgcolor-tags: $dark !default;
 $border-color-global: $gray-500 !default;
 $border-color-toc: $border-color-global !default;
+$color-dropdown: $color-global !default;
+$bgcolor-dropdown: $bgcolor-global !default;
+$color-dropdown-link: $color-global !default;
+$color-dropdown-link-hover: $light !default;
+$bgcolor-dropdown-link-hover: lighten($bgcolor-global, 15%) !default;
+$color-dropdown-link-active: $light !default;
+$bgcolor-dropdown-link-active: $primary !default;
 
 // override bootstrap variables
 $text-muted: $gray-550;
@@ -25,10 +32,18 @@ $table-dark-border-color: $border-color-table;
 $table-dark-hover-color: $color-table-hover;
 $table-dark-hover-bg: $bgcolor-table-hover;
 $border-color: $border-color-global;
+$dropdown-color: $color-dropdown;
+$dropdown-bg: $bgcolor-dropdown;
+$dropdown-link-color: $color-dropdown-link;
+$dropdown-link-hover-color: $color-dropdown-link-hover;
+$dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
+$dropdown-link-active-color: $color-dropdown-link-active;
+$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 
 @import 'reboot-bootstrap-text';
 @import 'reboot-bootstrap-border-colors';
 @import 'reboot-bootstrap-tables';
+@import 'reboot-bootstrap-dropdown';
 
 // List Group
 @include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
@@ -74,20 +89,6 @@ label.custom-control-label::before {
   background-color: darken($bgcolor-global, 5%);
 }
 
-/*
- * Dropdown
- */
-.dropdown-menu {
-  background-color: $bgcolor-global;
-}
-
-.dropdown-item {
-  &:hover {
-    color: $light;
-    background-color: lighten($bgcolor-global, 15%);
-  }
-}
-
 /*
  * Table
  */

+ 11 - 0
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -15,6 +15,11 @@ $color-tags: $secondary !default;
 $bgcolor-tags: $gray-200 !default;
 $border-color-global: $gray-300 !default;
 $border-color-toc: $border-color-global !default;
+$color-dropdown: $color-global !default;
+$color-dropdown-link: $color-global !default;
+$color-dropdown-link-hover: $color-global !default;
+$color-dropdown-link-active: $color-reversal !default;
+$bgcolor-dropdown-link-active: $primary !default;
 
 // override bootstrap variables
 $text-muted: $gray-500;
@@ -24,10 +29,16 @@ $table-border-color: $border-color-table;
 $table-hover-color: $color-table-hover;
 $table-hover-bg: $bgcolor-table-hover;
 $border-color: $border-color-global;
+$dropdown-color: $color-dropdown;
+$dropdown-link-color: $color-dropdown-link;
+$dropdown-link-hover-color: $color-dropdown-link-hover;
+$dropdown-link-active-color: $color-dropdown-link-active;
+$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 
 @import 'reboot-bootstrap-text';
 @import 'reboot-bootstrap-border-colors';
 @import 'reboot-bootstrap-tables';
+@import 'reboot-bootstrap-dropdown';
 
 // List Group
 @include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);

+ 0 - 27
packages/app/src/styles/theme/_apply-colors.scss

@@ -77,10 +77,6 @@ pre:not(.hljs):not(.CodeMirror-line) {
 }
 
 // Dropdown
-.dropdown-menu {
-  color: $color-global;
-}
-
 .grw-personal-dropdown {
   .grw-icon-container svg {
     fill: $color-global;
@@ -90,29 +86,6 @@ pre:not(.hljs):not(.CodeMirror-line) {
   }
 }
 
-.dropdown-item {
-  color: $color-global;
-
-  svg {
-    fill: $color-global;
-  }
-
-  &:active,
-  &.active,
-  &:active:hover,
-  &.active:hover {
-    color: $color-dropdown-link-active;
-    background-color: $bgcolor-dropdown-link-active;
-
-    svg {
-      fill: $color-dropdown-link-active;
-    }
-  }
-  &:hover {
-    background-color: $light;
-  }
-}
-
 // Form
 .form-control {
   @include form-control-focus();

+ 35 - 0
packages/app/src/styles/theme/_reboot-bootstrap-dropdown.scss

@@ -0,0 +1,35 @@
+.dropdown-menu {
+  color: $dropdown-color;
+  svg {
+    fill: $dropdown-color;
+  }
+
+  background-color: $dropdown-bg;
+}
+
+.dropdown-item {
+  color: $dropdown-link-color;
+  svg {
+    fill: $dropdown-link-color;
+  }
+
+  @include hover-focus() {
+    color: $dropdown-link-hover-color;
+    svg {
+      fill: $dropdown-link-hover-color;
+    }
+
+    @include gradient-bg($dropdown-link-hover-bg);
+  }
+
+  &:active,
+  &.active,
+  &:active:hover,
+  &.active:hover {
+    color: $dropdown-link-active-color;
+    background-color: $dropdown-link-active-bg;
+    svg {
+      fill: $dropdown-link-active-color;
+    }
+  }
+}

+ 0 - 4
packages/app/src/styles/theme/antarctic.scss

@@ -106,8 +106,6 @@ html[dark] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);
@@ -189,8 +187,6 @@ html[dark] {
 
 //   // Dropdown colors
 //   $bgcolor-dropdown-link-active: $primary;
-//   $color-dropdown-link-active: $color-global;
-//   $color-dropdown-link-hover: $color-reversal;
 
 //   // Sidebar
 //   $bgcolor-sidebar: $bgcolor-navbar;

+ 0 - 1
packages/app/src/styles/theme/blackboard.scss

@@ -73,7 +73,6 @@ html[dark] {
   $bordercolor-inline-code: #4d4d4d; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
   $color-dropdown-link-active: $color-global;
   $color-dropdown-link-hover: $color-reversal;
 

+ 1 - 3
packages/app/src/styles/theme/christmas.scss

@@ -97,9 +97,7 @@ html[dark] {
   $bordercolor-inline-code: #ccc8c8; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
+  $bgcolor-dropdown-link-active: $themecolor;
 
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);

+ 0 - 7
packages/app/src/styles/theme/default.scss

@@ -97,8 +97,6 @@ html[light] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-reversal;
 
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
@@ -197,11 +195,6 @@ html[dark] {
   $border-color-theme: $gray-400;
   $bordercolor-inline-code: $secondary; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
-
   // admin theme box
   $color-theme-color-box: $primary;
 

+ 0 - 5
packages/app/src/styles/theme/fire-red.scss

@@ -67,10 +67,6 @@ html[light] {
   $border-color-theme: $primary;
   $bordercolor-inline-code: #ccc8c8; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-reversal;
-
   // admin theme box
   $color-theme-color-box: $primary;
 
@@ -169,7 +165,6 @@ html[dark] {
   $bordercolor-inline-code: #4d4d4d; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
   $color-dropdown-link-active: $color-global;
   $color-dropdown-link-hover: $color-reversal;
 

+ 0 - 5
packages/app/src/styles/theme/future.scss

@@ -80,11 +80,6 @@ html[dark] {
   $border-color-theme: #407483;
   $bordercolor-inline-code: #4d4d4d; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
-
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 

+ 0 - 1
packages/app/src/styles/theme/halloween.scss

@@ -99,7 +99,6 @@ html[dark] {
   $bordercolor-inline-code: #4d4d4d; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
   $color-dropdown-link-active: $color-reversal;
   $color-dropdown-link-hover: $color-global;
 

+ 2 - 5
packages/app/src/styles/theme/hufflepuff.scss

@@ -87,8 +87,6 @@ html[light] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: darken($primary, 5%);
@@ -230,9 +228,8 @@ html[dark] {
   $bordercolor-inline-code: #4d4d4d; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
+  $color-dropdown-link-active: $color-reversal;
+  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: $primary;

+ 0 - 2
packages/app/src/styles/theme/island.scss

@@ -77,8 +77,6 @@ html[dark] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: darken($primary, 15%);

+ 0 - 5
packages/app/src/styles/theme/jade-green.scss

@@ -67,10 +67,6 @@ html[light] {
   $border-color-theme: $primary;
   $bordercolor-inline-code: #ccc8c8; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-reversal;
-
   // admin theme box
   $color-theme-color-box: $primary;
 
@@ -169,7 +165,6 @@ html[dark] {
   $bordercolor-inline-code: #4d4d4d; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
   $color-dropdown-link-active: $color-global;
   $color-dropdown-link-hover: $color-reversal;
 

+ 0 - 2
packages/app/src/styles/theme/kibela.scss

@@ -79,8 +79,6 @@ html[dark] {
 
   // dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: lighten($bgcolor-theme, 20%);

+ 0 - 9
packages/app/src/styles/theme/mono-blue.scss

@@ -67,10 +67,6 @@ html[light] {
   $border-color-theme: $gray-300;
   $bordercolor-inline-code: #ccc8c8; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-reversal;
-
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
@@ -168,11 +164,6 @@ html[dark] {
   $border-color-theme: #146aa0;
   $bordercolor-inline-code: #4d4d4d; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $primary;
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
-
   // admin theme box
   $color-theme-color-box: $primary;
 

+ 0 - 5
packages/app/src/styles/theme/nature.scss

@@ -84,11 +84,6 @@ html[dark] {
   $border-color-theme: $gray-300;
   $bordercolor-inline-code: #ccc8c8; // optional
 
-  // Dropdown colors
-  $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
-
   // Table colors
   $border-color-table: $gray-400; // optional
 

+ 0 - 2
packages/app/src/styles/theme/spring.scss

@@ -86,8 +86,6 @@ html[dark] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: darken($primary, 20%);

+ 0 - 2
packages/app/src/styles/theme/wood.scss

@@ -104,8 +104,6 @@ html[dark] {
 
   // Dropdown colors
   $bgcolor-dropdown-link-active: $growi-blue;
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
 
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);

+ 117 - 60
yarn.lock

@@ -616,6 +616,13 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.13.8", "@babel/runtime@^7.8.7":
+  version "7.17.7"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
+  integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
@@ -2301,6 +2308,11 @@
   dependencies:
     "@octokit/openapi-types" "^10.0.0"
 
+"@popperjs/core@^2.8.6":
+  version "2.11.4"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
+  integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
+
 "@promster/express@^7.0.2":
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/@promster/express/-/express-7.0.2.tgz#d60b10d373fd572275714426dc90a7049fd26d4f"
@@ -2349,6 +2361,20 @@
   resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
   integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
 
+"@restart/hooks@^0.3.26":
+  version "0.3.27"
+  resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.27.tgz#91f356d66d4699a8cd8b3d008402708b6a9dc505"
+  integrity sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==
+  dependencies:
+    dequal "^2.0.2"
+
+"@restart/hooks@^0.4.0":
+  version "0.4.5"
+  resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.5.tgz#e7acbea237bfc9e479970500cf87538b41a1ed02"
+  integrity sha512-tLGtY0aHeIfT7aPwUkvQuhIy3+q3w4iqmUzFLPlOAf/vNUacLaBt1j/S//jv/dQhenRh8jvswyMojCwmLvJw8A==
+  dependencies:
+    dequal "^2.0.2"
+
 "@sematext/gc-stats@1.5.5":
   version "1.5.5"
   resolved "https://registry.yarnpkg.com/@sematext/gc-stats/-/gc-stats-1.5.5.tgz#3461e818454b95de26085b65f0d95417b9f183d6"
@@ -3216,6 +3242,15 @@
     "@types/prop-types" "*"
     csstype "^2.2.0"
 
+"@types/react@>=16.9.11":
+  version "17.0.40"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad"
+  integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==
+  dependencies:
+    "@types/prop-types" "*"
+    "@types/scheduler" "*"
+    csstype "^3.0.2"
+
 "@types/retry@^0.12.0":
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@@ -3226,6 +3261,11 @@
   resolved "https://registry.yarnpkg.com/@types/rewire/-/rewire-2.5.28.tgz#ff34de38c4269fe74e2597195d4918c25d42ebad"
   integrity sha512-uD0j/AQOa5le7afuK+u+woi8jNKF1vf3DN0H7LCJhft/lNNibUr7VcAesdgtWfEKveZol3ZG1CJqwx2Bhrnl8w==
 
+"@types/scheduler@*":
+  version "0.16.2"
+  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
+  integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
+
 "@types/serve-static@*":
   version "1.13.9"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
@@ -3276,6 +3316,11 @@
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
 
+"@types/warning@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
+  integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
+
 "@types/webidl-conversions@*":
   version "6.1.1"
   resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e"
@@ -5364,10 +5409,6 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
-chain-function@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
-
 chainsaw@~0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
@@ -5639,7 +5680,7 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-classnames@^2.2.0, classnames@^2.2.5:
+classnames@^2.2.0:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
 
@@ -6084,6 +6125,11 @@ compression@^1.7.4:
     safe-buffer "5.1.2"
     vary "~1.1.2"
 
+compute-scroll-into-view@^1.0.17:
+  version "1.0.17"
+  resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
+  integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
+
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -6592,14 +6638,6 @@ create-react-context@^0.1.5:
   resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.1.6.tgz#0f425931d907741127acc6e31acb4f9015dd9fdc"
   integrity sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw==
 
-create-react-context@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c"
-  integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==
-  dependencies:
-    gud "^1.0.0"
-    warning "^4.0.3"
-
 create-require@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@@ -6869,6 +6907,11 @@ csstype@^2.2.0:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
   integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
 
+csstype@^3.0.2:
+  version "3.0.11"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
+  integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
+
 csv-to-markdown-table@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/csv-to-markdown-table/-/csv-to-markdown-table-1.0.1.tgz#43da1b0c0c483faa10a23921abc5e47a48e0daba"
@@ -7220,6 +7263,11 @@ deprecation@^2.0.0, deprecation@^2.3.1:
   resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
   integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
 
+dequal@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
+  integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
+
 des.js@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -7372,10 +7420,6 @@ dom-accessibility-api@^0.5.9:
   resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c"
   integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==
 
-dom-helpers@^3.2.0, dom-helpers@^3.2.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
-
 dom-helpers@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
@@ -7383,6 +7427,14 @@ dom-helpers@^3.4.0:
   dependencies:
     "@babel/runtime" "^7.1.2"
 
+dom-helpers@^5.2.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+  integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+  dependencies:
+    "@babel/runtime" "^7.8.7"
+    csstype "^3.0.2"
+
 dom-serializer@0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -10733,6 +10785,13 @@ invariant@^2.2.1:
   dependencies:
     loose-envify "^1.0.0"
 
+invariant@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+  integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+  dependencies:
+    loose-envify "^1.0.0"
+
 invert-kv@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
@@ -12745,7 +12804,7 @@ lodash.uniqwith@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz#7a0cbf65f43b5928625a9d4d0dc54b18cadc7ef3"
   integrity sha1-egy/ZfQ7WShiWp1NDcVLGMrcfvM=
 
-lodash@4.17.21, lodash@4.x, lodash@>=4.17.15, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.21:
+lodash@4.17.21, lodash@4.x, lodash@>=4.17.15, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -16430,12 +16489,6 @@ promzard@^0.3.0:
   dependencies:
     read "1"
 
-prop-types-extra@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.0.1.tgz#a57bd4810e82d27a3ff4317ecc1b4ad005f79a82"
-  dependencies:
-    warning "^3.0.0"
-
 prop-types@^15.0.0, prop-types@^15.6.2:
   version "15.6.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
@@ -16789,20 +16842,21 @@ rc@>=1.2.8:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-react-bootstrap-typeahead@^3.4.7:
-  version "3.4.7"
-  resolved "https://registry.yarnpkg.com/react-bootstrap-typeahead/-/react-bootstrap-typeahead-3.4.7.tgz#27a3f17c6b1351a0c1b321ac133d5e762cf4dc2a"
-  integrity sha512-eUm3hqX12p+iM+1Y0HKF891/ACbKyGep7PsC2pjFGZL48r25Jlv3X2xmV5D8N0wE/YPFZF7iW913tyAlwqjw1Q==
+react-bootstrap-typeahead@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.2.2.tgz#5f8eeaa9444e622f36dbb0c01e4776ba33b4c99e"
+  integrity sha512-dnN6o+HlDOWy/PyPWI63gFJlojx7qhypj02pvOnBCUgl6XXj9+iIAS5jj5AD76wOnlVRfO5d7pL4Ordjt1rTzQ==
   dependencies:
+    "@babel/runtime" "^7.14.6"
+    "@restart/hooks" "^0.4.0"
     classnames "^2.2.0"
-    create-react-context "^0.3.0"
-    escape-string-regexp "^1.0.5"
+    fast-deep-equal "^3.1.1"
     invariant "^2.2.1"
-    lodash "^4.17.2"
+    lodash.debounce "^4.0.8"
     prop-types "^15.5.8"
-    prop-types-extra "^1.0.1"
-    react-overlays "^0.8.1"
+    react-overlays "^5.1.0"
     react-popper "^1.0.0"
+    scroll-into-view-if-needed "^2.2.20"
     warning "^4.0.1"
 
 react-card-flip@^1.0.10:
@@ -16945,16 +16999,19 @@ react-multiline-clamp@^2.0.0:
   resolved "https://registry.yarnpkg.com/react-multiline-clamp/-/react-multiline-clamp-2.0.0.tgz#913a2092368ef1b52c1c79364d506ba4af27e019"
   integrity sha512-iPm3HxFD6LO63lE5ZnThiqs+6A3c+LW3WbsEM0oa0iNTa0qN4SKx/LK/6ZToSmXundEcQXBFVNzKDvgmExawTw==
 
-react-overlays@^0.8.1:
-  version "0.8.3"
-  resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.3.tgz#fad65eea5b24301cca192a169f5dddb0b20d3ac5"
-  dependencies:
-    classnames "^2.2.5"
-    dom-helpers "^3.2.1"
-    prop-types "^15.5.10"
-    prop-types-extra "^1.0.1"
-    react-transition-group "^2.2.0"
-    warning "^3.0.0"
+react-overlays@^5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.1.1.tgz#2e7cf49744b56537c7828ccb94cfc63dd778ae4f"
+  integrity sha512-eCN2s2/+GVZzpnId4XVWtvDPYYBD2EtOGP74hE+8yDskPzFy9+pV1H3ZZihxuRdEbQzzacySaaDkR7xE0ydl4Q==
+  dependencies:
+    "@babel/runtime" "^7.13.8"
+    "@popperjs/core" "^2.8.6"
+    "@restart/hooks" "^0.3.26"
+    "@types/warning" "^3.0.0"
+    dom-helpers "^5.2.0"
+    prop-types "^15.7.2"
+    uncontrollable "^7.2.1"
+    warning "^4.0.3"
 
 react-popper@^1.0.0:
   version "1.3.3"
@@ -16997,17 +17054,6 @@ react-tagcloud@^2.1.1:
     randomcolor "^0.5.4"
     shuffle-array "^1.0.1"
 
-react-transition-group@^2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
-  dependencies:
-    chain-function "^1.0.0"
-    classnames "^2.2.5"
-    dom-helpers "^3.2.0"
-    loose-envify "^1.3.1"
-    prop-types "^15.5.8"
-    warning "^3.0.0"
-
 react-transition-group@^2.2.1, react-transition-group@^2.3.1:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@@ -18090,6 +18136,13 @@ schema-utils@^3.0.0:
     ajv "^6.12.5"
     ajv-keywords "^3.5.2"
 
+scroll-into-view-if-needed@^2.2.20:
+  version "2.2.29"
+  resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz#551791a84b7e2287706511f8c68161e4990ab885"
+  integrity sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==
+  dependencies:
+    compute-scroll-into-view "^1.0.17"
+
 secure-json-parse@^2.1.0, secure-json-parse@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.4.0.tgz#5aaeaaef85c7a417f76271a4f5b0cc3315ddca85"
@@ -20795,6 +20848,16 @@ unbox-primitive@^1.0.1:
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
+uncontrollable@^7.2.1:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
+  integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==
+  dependencies:
+    "@babel/runtime" "^7.6.3"
+    "@types/react" ">=16.9.11"
+    invariant "^2.2.4"
+    react-lifecycles-compat "^3.0.4"
+
 unherit@^1.0.4:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
@@ -21329,12 +21392,6 @@ walker@^1.0.7:
   dependencies:
     makeerror "1.0.x"
 
-warning@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
-  dependencies:
-    loose-envify "^1.0.0"
-
 warning@^4.0.1, warning@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607"