SearchForm.tsx 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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. (e.nativeEvent as { preventDownshiftDefault?: boolean }).preventDownshiftDefault = true;
  41. }
  42. },
  43. [],
  44. );
  45. const inputOptions = useMemo(() => {
  46. return getInputProps({
  47. type: 'text',
  48. placeholder: 'Search...',
  49. className: 'form-control',
  50. ref: inputRef,
  51. value: searchKeyword,
  52. onChange: changeSearchTextHandler,
  53. onKeyDown: keyDownHandler,
  54. });
  55. }, [getInputProps, searchKeyword, changeSearchTextHandler, keyDownHandler]);
  56. useEffect(() => {
  57. if (inputRef.current != null) {
  58. inputRef.current.focus();
  59. }
  60. });
  61. return (
  62. <form
  63. className="w-100 position-relative"
  64. onSubmit={submitHandler}
  65. data-testid="search-form"
  66. >
  67. <input {...inputOptions} />
  68. <button
  69. type="button"
  70. className="btn btn-neutral-secondary text-muted position-absolute bottom-0 end-0 w-auto h-100 border-0"
  71. onClick={() => {
  72. onChange?.('');
  73. }}
  74. >
  75. <span className="material-symbols-outlined p-0">cancel</span>
  76. </button>
  77. </form>
  78. );
  79. };