searchBar.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {createRef, PureComponent} from 'react';
  2. import styled from '@emotion/styled';
  3. import classNames from 'classnames';
  4. import Button from 'sentry/components/button';
  5. import Input, {InputProps} from 'sentry/components/forms/controls/input';
  6. import {IconSearch} from 'sentry/icons';
  7. import {IconClose} from 'sentry/icons/iconClose';
  8. import {t} from 'sentry/locale';
  9. import {callIfFunction} from 'sentry/utils/callIfFunction';
  10. interface SearchBarProps extends Omit<InputProps, 'onChange'> {
  11. defaultQuery: string;
  12. onSearch: (query: string) => void;
  13. query: string;
  14. onChange?: (query: string) => void;
  15. width?: string;
  16. }
  17. type State = {
  18. dropdownVisible: boolean;
  19. query: string;
  20. };
  21. class SearchBar extends PureComponent<SearchBarProps, State> {
  22. static defaultProps: Pick<SearchBarProps, 'query' | 'defaultQuery' | 'onSearch'> = {
  23. query: '',
  24. defaultQuery: '',
  25. onSearch: function () {},
  26. };
  27. state: State = {
  28. query: this.props.query || this.props.defaultQuery,
  29. dropdownVisible: false,
  30. };
  31. UNSAFE_componentWillReceiveProps(nextProps: SearchBarProps) {
  32. if (nextProps.query !== this.props.query) {
  33. this.setState({
  34. query: nextProps.query,
  35. });
  36. }
  37. }
  38. searchInputRef = createRef<HTMLInputElement>();
  39. blur = () => {
  40. if (this.searchInputRef.current) {
  41. this.searchInputRef.current.blur();
  42. }
  43. };
  44. onSubmit = (evt: React.FormEvent<HTMLFormElement>) => {
  45. evt.preventDefault();
  46. this.blur();
  47. this.props.onSearch(this.state.query);
  48. };
  49. clearSearch = () => {
  50. this.setState({query: this.props.defaultQuery}, () => {
  51. this.props.onSearch(this.state.query);
  52. callIfFunction(this.props.onChange, this.state.query);
  53. });
  54. };
  55. onQueryFocus = () => {
  56. this.setState({
  57. dropdownVisible: true,
  58. });
  59. };
  60. onQueryBlur = () => {
  61. this.setState({dropdownVisible: false});
  62. };
  63. onQueryChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
  64. const {value} = evt.target;
  65. this.setState({query: value});
  66. callIfFunction(this.props.onChange, value);
  67. };
  68. render() {
  69. // Remove keys that should not be passed into Input
  70. const {
  71. className,
  72. width,
  73. query: _q,
  74. defaultQuery,
  75. onChange: _oC,
  76. onSearch: _oS,
  77. ...inputProps
  78. } = this.props;
  79. return (
  80. <div className={classNames('search', className)}>
  81. <form className="form-horizontal" onSubmit={this.onSubmit}>
  82. <div>
  83. <StyledInput
  84. {...inputProps}
  85. type="text"
  86. className="search-input"
  87. name="query"
  88. ref={this.searchInputRef}
  89. autoComplete="off"
  90. value={this.state.query}
  91. onBlur={this.onQueryBlur}
  92. onChange={this.onQueryChange}
  93. width={width}
  94. />
  95. <StyledIconSearch className="search-input-icon" size="sm" color="gray300" />
  96. {this.state.query !== defaultQuery && (
  97. <SearchClearButton
  98. type="button"
  99. className="search-clear-form"
  100. priority="link"
  101. onClick={this.clearSearch}
  102. size="xsmall"
  103. icon={<IconClose />}
  104. aria-label={t('Clear')}
  105. />
  106. )}
  107. </div>
  108. </form>
  109. </div>
  110. );
  111. }
  112. }
  113. const StyledInput = styled(Input)`
  114. width: ${p => (p.width ? p.width : undefined)};
  115. &.focus-visible {
  116. box-shadow: 0 0 0 1px ${p => p.theme.focusBorder};
  117. border-color: ${p => p.theme.focusBorder};
  118. outline: none;
  119. }
  120. `;
  121. const StyledIconSearch = styled(IconSearch)`
  122. position: absolute;
  123. top: 50%;
  124. transform: translateY(-50%);
  125. left: 14px;
  126. `;
  127. const SearchClearButton = styled(Button)`
  128. position: absolute;
  129. top: 50%;
  130. height: 16px;
  131. transform: translateY(-50%);
  132. right: 10px;
  133. font-size: ${p => p.theme.fontSizeLarge};
  134. color: ${p => p.theme.gray200};
  135. &:hover {
  136. color: ${p => p.theme.gray300};
  137. }
  138. `;
  139. export default SearchBar;