1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import * as React from 'react';
    
  11. import {useEffect, useRef} from 'react';
    
  12. import Button from './Button';
    
  13. import ButtonIcon from './ButtonIcon';
    
  14. import Icon from './Icon';
    
  15. 
    
  16. import styles from './SearchInput.css';
    
  17. 
    
  18. type Props = {
    
  19.   goToNextResult: () => void,
    
  20.   goToPreviousResult: () => void,
    
  21.   placeholder: string,
    
  22.   search: (text: string) => void,
    
  23.   searchIndex: number,
    
  24.   searchResultsCount: number,
    
  25.   searchText: string,
    
  26.   testName?: ?string,
    
  27. };
    
  28. 
    
  29. export default function SearchInput({
    
  30.   goToNextResult,
    
  31.   goToPreviousResult,
    
  32.   placeholder,
    
  33.   search,
    
  34.   searchIndex,
    
  35.   searchResultsCount,
    
  36.   searchText,
    
  37.   testName,
    
  38. }: Props): React.Node {
    
  39.   const inputRef = useRef<HTMLInputElement | null>(null);
    
  40. 
    
  41.   const resetSearch = () => search('');
    
  42. 
    
  43.   // $FlowFixMe[missing-local-annot]
    
  44.   const handleChange = ({currentTarget}) => {
    
  45.     search(currentTarget.value);
    
  46.   };
    
  47.   // $FlowFixMe[missing-local-annot]
    
  48.   const handleKeyPress = ({key, shiftKey}) => {
    
  49.     if (key === 'Enter') {
    
  50.       if (shiftKey) {
    
  51.         goToPreviousResult();
    
  52.       } else {
    
  53.         goToNextResult();
    
  54.       }
    
  55.     }
    
  56.   };
    
  57. 
    
  58.   // Auto-focus search input
    
  59.   useEffect(() => {
    
  60.     if (inputRef.current === null) {
    
  61.       return () => {};
    
  62.     }
    
  63. 
    
  64.     const handleKeyDown = (event: KeyboardEvent) => {
    
  65.       const {key, metaKey} = event;
    
  66.       if (key === 'f' && metaKey) {
    
  67.         if (inputRef.current !== null) {
    
  68.           inputRef.current.focus();
    
  69.           event.preventDefault();
    
  70.           event.stopPropagation();
    
  71.         }
    
  72.       }
    
  73.     };
    
  74. 
    
  75.     // It's important to listen to the ownerDocument to support the browser extension.
    
  76.     // Here we use portals to render individual tabs (e.g. Profiler),
    
  77.     // and the root document might belong to a different window.
    
  78.     const ownerDocument = inputRef.current.ownerDocument;
    
  79.     ownerDocument.addEventListener('keydown', handleKeyDown);
    
  80. 
    
  81.     return () => ownerDocument.removeEventListener('keydown', handleKeyDown);
    
  82.   }, []);
    
  83. 
    
  84.   return (
    
  85.     <div className={styles.SearchInput} data-testname={testName}>
    
  86.       <Icon className={styles.InputIcon} type="search" />
    
  87.       <input
    
  88.         data-testname={testName ? `${testName}-Input` : undefined}
    
  89.         className={styles.Input}
    
  90.         onChange={handleChange}
    
  91.         onKeyPress={handleKeyPress}
    
  92.         placeholder={placeholder}
    
  93.         ref={inputRef}
    
  94.         value={searchText}
    
  95.       />
    
  96.       {!!searchText && (
    
  97.         <React.Fragment>
    
  98.           <span
    
  99.             className={styles.IndexLabel}
    
  100.             data-testname={testName ? `${testName}-ResultsCount` : undefined}>
    
  101.             {Math.min(searchIndex + 1, searchResultsCount)} |{' '}
    
  102.             {searchResultsCount}
    
  103.           </span>
    
  104.           <div className={styles.LeftVRule} />
    
  105.           <Button
    
  106.             data-testname={testName ? `${testName}-PreviousButton` : undefined}
    
  107.             disabled={!searchText}
    
  108.             onClick={goToPreviousResult}
    
  109.             title={
    
  110.               <React.Fragment>
    
  111.                 Scroll to previous search result (<kbd>Shift</kbd> +{' '}
    
  112.                 <kbd>Enter</kbd>)
    
  113.               </React.Fragment>
    
  114.             }>
    
  115.             <ButtonIcon type="up" />
    
  116.           </Button>
    
  117.           <Button
    
  118.             data-testname={testName ? `${testName}-NextButton` : undefined}
    
  119.             disabled={!searchText}
    
  120.             onClick={goToNextResult}
    
  121.             title={
    
  122.               <React.Fragment>
    
  123.                 Scroll to next search result (<kbd>Enter</kbd>)
    
  124.               </React.Fragment>
    
  125.             }>
    
  126.             <ButtonIcon type="down" />
    
  127.           </Button>
    
  128.           <Button
    
  129.             data-testname={testName ? `${testName}-ResetButton` : undefined}
    
  130.             disabled={!searchText}
    
  131.             onClick={resetSearch}
    
  132.             title="Reset search">
    
  133.             <ButtonIcon type="close" />
    
  134.           </Button>
    
  135.         </React.Fragment>
    
  136.       )}
    
  137.     </div>
    
  138.   );
    
  139. }