searchBar.tsx 3.8 KB

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