SearchForm.tsx 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import React, {
  2. type JSX,
  3. useCallback,
  4. useEffect,
  5. useMemo,
  6. useRef,
  7. } from 'react';
  8. import type { GetInputProps } from '../interfaces/downshift';
  9. type Props = {
  10. searchKeyword: string;
  11. onChange?: (text: string) => void;
  12. onSubmit?: () => void;
  13. getInputProps: GetInputProps;
  14. };
  15. export const SearchForm = (props: Props): JSX.Element => {
  16. const { searchKeyword, onChange, onSubmit, getInputProps } = props;
  17. const inputRef = useRef<HTMLInputElement>(null);
  18. const changeSearchTextHandler = useCallback(
  19. (e: React.ChangeEvent<HTMLInputElement>) => {
  20. onChange?.(e.target.value);
  21. },
  22. [onChange],
  23. );
  24. const submitHandler = useCallback(
  25. (e: React.FormEvent<HTMLFormElement>) => {
  26. e.preventDefault();
  27. const isEmptyKeyword = searchKeyword.trim().length === 0;
  28. if (isEmptyKeyword) {
  29. return;
  30. }
  31. onSubmit?.();
  32. },
  33. [searchKeyword, onSubmit],
  34. );
  35. // Prevent Downshift from intercepting Home/End keys so they move
  36. // the cursor within the input field instead of navigating the list
  37. const keyDownHandler = useCallback(
  38. (e: React.KeyboardEvent<HTMLInputElement>) => {
  39. if (e.key === 'Home' || e.key === 'End') {
  40. (
  41. e.nativeEvent as { preventDownshiftDefault?: boolean }
  42. ).preventDownshiftDefault = true;
  43. }
  44. },
  45. [],
  46. );
  47. const inputOptions = useMemo(() => {
  48. return getInputProps({
  49. type: 'text',
  50. placeholder: 'Search...',
  51. className: 'form-control',
  52. ref: inputRef,
  53. value: searchKeyword,
  54. onChange: changeSearchTextHandler,
  55. onKeyDown: keyDownHandler,
  56. });
  57. }, [getInputProps, searchKeyword, changeSearchTextHandler, keyDownHandler]);
  58. useEffect(() => {
  59. if (inputRef.current != null) {
  60. inputRef.current.focus();
  61. }
  62. });
  63. return (
  64. <form
  65. className="w-100 position-relative"
  66. onSubmit={submitHandler}
  67. data-testid="search-form"
  68. >
  69. <input {...inputOptions} />
  70. <button
  71. type="button"
  72. className="btn btn-neutral-secondary text-muted position-absolute bottom-0 end-0 w-auto h-100 border-0"
  73. onClick={() => {
  74. onChange?.('');
  75. }}
  76. >
  77. <span className="material-symbols-outlined p-0">cancel</span>
  78. </button>
  79. </form>
  80. );
  81. };