SearchInput.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { InputAdornment, TextField } from '@mui/material';
  2. import { makeStyles } from '@mui/styles';
  3. import { useRef, FC, useState, useEffect, useMemo } from 'react';
  4. import { useTranslation } from 'react-i18next';
  5. import Icons from '../icons/Icons';
  6. import { SearchType } from './Types';
  7. const useSearchStyles = makeStyles(theme => ({
  8. wrapper: {
  9. display: 'flex',
  10. },
  11. input: {
  12. backgroundColor: '#fff',
  13. padding: theme.spacing(1),
  14. width: '240px',
  15. border: '1px solid #e9e9ed',
  16. fontSize: '14px',
  17. transition: 'all 0.2s',
  18. '& .MuiAutocomplete-endAdornment': {
  19. right: theme.spacing(0.5),
  20. },
  21. '& .MuiInput-underline:before': {
  22. border: 'none',
  23. },
  24. '& .MuiInput-underline:after': {
  25. border: 'none',
  26. },
  27. /**
  28. * when input focus
  29. * 1. change parent wrapper border color
  30. * 2. hide input start search icon
  31. */
  32. '&:focus-within': {
  33. border: `1px solid ${theme.palette.primary.main}`,
  34. '& $searchIcon': {
  35. width: 0,
  36. },
  37. },
  38. },
  39. textfield: {
  40. padding: 0,
  41. height: '16px',
  42. '&:focus': {
  43. caretColor: theme.palette.primary.main,
  44. },
  45. },
  46. searchIcon: {
  47. color: theme.palette.attuGrey.main,
  48. cursor: 'pointer',
  49. fontSize: '20px',
  50. width: (props: { searched: boolean }) => `${props.searched ? 0 : '20px'}`,
  51. transition: 'width 0.2s',
  52. },
  53. clearIcon: {
  54. color: theme.palette.primary.main,
  55. cursor: 'pointer',
  56. },
  57. iconWrapper: {
  58. opacity: (props: { searched: boolean }) => `${props.searched ? 1 : 0}`,
  59. transition: 'opacity 0.2s',
  60. },
  61. searchWrapper: {
  62. display: 'flex',
  63. justifyContent: 'center',
  64. alignItems: 'center',
  65. },
  66. }));
  67. const SearchInput: FC<SearchType> = props => {
  68. const { searchText = '', onClear = () => {}, onSearch = () => {} } = props;
  69. const [searchValue, setSearchValue] = useState<string>(searchText || '');
  70. const searched = useMemo(() => searchValue !== '', [searchValue]);
  71. const classes = useSearchStyles({ searched });
  72. const { t: commonTrans } = useTranslation();
  73. const inputRef = useRef<any>(null);
  74. const handleSearch = (value: string) => {
  75. onSearch(value);
  76. };
  77. useEffect(() => {
  78. let hashPart = window.location.hash.substring(1);
  79. // remove search part from hash part, include the '?'
  80. hashPart = hashPart.replace(/(\?search=)[^&]+(&?)/, '$2');
  81. let searchPart = !searchValue
  82. ? ''
  83. : `?search=${encodeURIComponent(searchValue)}`;
  84. hashPart = `${hashPart}${searchPart}`;
  85. const newUrl = `${window.location.pathname}#${hashPart}`;
  86. window.history.replaceState(null, '', newUrl);
  87. handleSearch(searchValue);
  88. }, [searchValue]);
  89. return (
  90. <div className={classes.wrapper}>
  91. <TextField
  92. inputRef={inputRef}
  93. variant="standard"
  94. classes={{ root: classes.input }}
  95. InputProps={{
  96. disableUnderline: true,
  97. classes: { input: classes.textfield },
  98. endAdornment: (
  99. <InputAdornment position="end">
  100. <span
  101. data-testid="clear-icon"
  102. className={`flex-center ${classes.iconWrapper}`}
  103. onClick={e => {
  104. setSearchValue('');
  105. inputRef.current.focus();
  106. onClear();
  107. }}
  108. >
  109. {Icons.clear({ classes: { root: classes.clearIcon } })}
  110. </span>
  111. </InputAdornment>
  112. ),
  113. startAdornment: (
  114. <InputAdornment position="start">
  115. <span
  116. className={classes.searchWrapper}
  117. onClick={() => handleSearch(searchValue)}
  118. >
  119. {Icons.search({ classes: { root: classes.searchIcon } })}
  120. </span>
  121. </InputAdornment>
  122. ),
  123. }}
  124. onChange={e => {
  125. const value = e.target.value.trim();
  126. setSearchValue(value);
  127. if (value === '') {
  128. onClear();
  129. }
  130. }}
  131. onKeyPress={e => {
  132. if (e.key === 'Enter') {
  133. // Do code here
  134. handleSearch(searchValue);
  135. e.preventDefault();
  136. }
  137. }}
  138. value={searchValue || ''}
  139. placeholder={commonTrans('search')}
  140. />
  141. </div>
  142. );
  143. };
  144. export default SearchInput;