searchBar.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import {useCallback, useRef, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button from 'sentry/components/button';
  4. import {
  5. Input,
  6. InputGroup,
  7. InputLeadingItems,
  8. InputProps,
  9. InputTrailingItems,
  10. } from 'sentry/components/inputGroup';
  11. import {IconSearch} from 'sentry/icons';
  12. import {IconClose} from 'sentry/icons/iconClose';
  13. import {t} from 'sentry/locale';
  14. import space from 'sentry/styles/space';
  15. interface SearchBarProps extends Omit<InputProps, 'onChange'> {
  16. defaultQuery?: string;
  17. onChange?: (query: string) => void;
  18. onSearch?: (query: string) => void;
  19. query?: string;
  20. width?: string;
  21. }
  22. function SearchBar({
  23. query: queryProp,
  24. defaultQuery = '',
  25. onChange,
  26. onSearch,
  27. width,
  28. size,
  29. className,
  30. ...inputProps
  31. }: SearchBarProps) {
  32. const inputRef = useRef<HTMLInputElement>(null);
  33. const [query, setQuery] = useState(queryProp ?? defaultQuery);
  34. const onQueryChange = useCallback(
  35. (e: React.ChangeEvent<HTMLInputElement>) => {
  36. const {value} = e.target;
  37. setQuery(value);
  38. onChange?.(value);
  39. },
  40. [onChange]
  41. );
  42. const onSubmit = useCallback(
  43. (e: React.FormEvent<HTMLFormElement>) => {
  44. e.preventDefault();
  45. inputRef.current?.blur();
  46. onSearch?.(query);
  47. },
  48. [onSearch, query]
  49. );
  50. const clearSearch = useCallback(() => {
  51. setQuery('');
  52. onChange?.('');
  53. onSearch?.('');
  54. }, [onChange, onSearch]);
  55. return (
  56. <FormWrap onSubmit={onSubmit} className={className}>
  57. <InputGroup>
  58. <InputLeadingItems disablePointerEvents>
  59. <IconSearch color="subText" size={size === 'xs' ? 'xs' : 'sm'} />
  60. </InputLeadingItems>
  61. <StyledInput
  62. {...inputProps}
  63. ref={inputRef}
  64. type="text"
  65. name="query"
  66. autoComplete="off"
  67. value={query}
  68. onChange={onQueryChange}
  69. width={width}
  70. size={size}
  71. />
  72. <InputTrailingItems>
  73. {!!query && (
  74. <SearchClearButton
  75. type="button"
  76. size="zero"
  77. borderless
  78. onClick={clearSearch}
  79. icon={<IconClose size="xs" />}
  80. aria-label={t('Clear')}
  81. />
  82. )}
  83. </InputTrailingItems>
  84. </InputGroup>
  85. </FormWrap>
  86. );
  87. }
  88. const FormWrap = styled('form')`
  89. display: block;
  90. position: relative;
  91. `;
  92. const StyledInput = styled(Input)`
  93. ${p => p.width && `width: ${p.width};`}
  94. `;
  95. const SearchClearButton = styled(Button)`
  96. color: ${p => p.theme.subText};
  97. padding: ${space(0.5)};
  98. `;
  99. export default SearchBar;