searchBar.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import {useCallback, useEffect, 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. // if query prop keeps changing we should treat this as
  35. // a controlled component and its internal state should be in sync
  36. useEffect(() => {
  37. if (typeof queryProp === 'string') {
  38. setQuery(queryProp);
  39. }
  40. }, [queryProp]);
  41. const onQueryChange = useCallback(
  42. (e: React.ChangeEvent<HTMLInputElement>) => {
  43. const {value} = e.target;
  44. setQuery(value);
  45. onChange?.(value);
  46. },
  47. [onChange]
  48. );
  49. const onSubmit = useCallback(
  50. (e: React.FormEvent<HTMLFormElement>) => {
  51. e.preventDefault();
  52. inputRef.current?.blur();
  53. onSearch?.(query);
  54. },
  55. [onSearch, query]
  56. );
  57. const clearSearch = useCallback(() => {
  58. setQuery('');
  59. onChange?.('');
  60. onSearch?.('');
  61. }, [onChange, onSearch]);
  62. return (
  63. <FormWrap onSubmit={onSubmit} className={className}>
  64. <InputGroup>
  65. <InputLeadingItems disablePointerEvents>
  66. <IconSearch color="subText" size={size === 'xs' ? 'xs' : 'sm'} />
  67. </InputLeadingItems>
  68. <StyledInput
  69. {...inputProps}
  70. ref={inputRef}
  71. type="text"
  72. name="query"
  73. autoComplete="off"
  74. value={query}
  75. onChange={onQueryChange}
  76. width={width}
  77. size={size}
  78. />
  79. <InputTrailingItems>
  80. {!!query && (
  81. <SearchClearButton
  82. type="button"
  83. size="zero"
  84. borderless
  85. onClick={clearSearch}
  86. icon={<IconClose size="xs" />}
  87. aria-label={t('Clear')}
  88. />
  89. )}
  90. </InputTrailingItems>
  91. </InputGroup>
  92. </FormWrap>
  93. );
  94. }
  95. const FormWrap = styled('form')`
  96. display: block;
  97. position: relative;
  98. `;
  99. const StyledInput = styled(Input)`
  100. ${p => p.width && `width: ${p.width};`}
  101. `;
  102. const SearchClearButton = styled(Button)`
  103. color: ${p => p.theme.subText};
  104. padding: ${space(0.5)};
  105. `;
  106. export default SearchBar;