Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import React, { useCallback } from 'react';
  2. import TextareaAutosize from 'react-textarea-autosize';
  3. import { makeStyles } from 'tss-react/mui';
  4. import { isMobileBrowser } from '../../../environment/utils';
  5. import Icon from '../../../icons/components/Icon';
  6. import { IconCloseCircle } from '../../../icons/svg';
  7. import { withPixelLineHeight } from '../../../styles/functions.web';
  8. import { IInputProps } from '../types';
  9. interface IProps extends IInputProps {
  10. accessibilityLabel?: string;
  11. autoComplete?: string;
  12. autoFocus?: boolean;
  13. bottomLabel?: string;
  14. className?: string;
  15. iconClick?: () => void;
  16. id?: string;
  17. maxLength?: number;
  18. maxRows?: number;
  19. maxValue?: number;
  20. minRows?: number;
  21. minValue?: number;
  22. name?: string;
  23. onBlur?: (e: any) => void;
  24. onFocus?: (event: React.FocusEvent) => void;
  25. onKeyPress?: (e: React.KeyboardEvent) => void;
  26. readOnly?: boolean;
  27. required?: boolean;
  28. testId?: string;
  29. textarea?: boolean;
  30. type?: 'text' | 'email' | 'number' | 'password';
  31. }
  32. const useStyles = makeStyles()(theme => {
  33. return {
  34. inputContainer: {
  35. display: 'flex',
  36. flexDirection: 'column'
  37. },
  38. label: {
  39. color: theme.palette.text01,
  40. ...withPixelLineHeight(theme.typography.bodyShortRegular),
  41. marginBottom: theme.spacing(2),
  42. '&.is-mobile': {
  43. ...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
  44. }
  45. },
  46. fieldContainer: {
  47. position: 'relative',
  48. display: 'flex'
  49. },
  50. input: {
  51. backgroundColor: theme.palette.ui03,
  52. background: theme.palette.ui03,
  53. color: theme.palette.text01,
  54. ...withPixelLineHeight(theme.typography.bodyShortRegular),
  55. padding: '10px 16px',
  56. borderRadius: theme.shape.borderRadius,
  57. border: 0,
  58. height: '40px',
  59. boxSizing: 'border-box',
  60. width: '100%',
  61. '&::placeholder': {
  62. color: theme.palette.text02
  63. },
  64. '&:focus': {
  65. outline: 0,
  66. boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
  67. },
  68. '&:disabled': {
  69. color: theme.palette.text03
  70. },
  71. '&.is-mobile': {
  72. height: '48px',
  73. padding: '13px 16px',
  74. ...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
  75. },
  76. '&.icon-input': {
  77. paddingLeft: '46px'
  78. },
  79. '&.error': {
  80. boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
  81. }
  82. },
  83. 'input::-webkit-outer-spin-button, input::-webkit-inner-spin-button': {
  84. '-webkit-appearance': 'none',
  85. margin: 0
  86. },
  87. 'input[type=number]': {
  88. '-moz-appearance': 'textfield'
  89. },
  90. icon: {
  91. position: 'absolute',
  92. top: '50%',
  93. transform: 'translateY(-50%)',
  94. left: '16px'
  95. },
  96. iconClickable: {
  97. cursor: 'pointer'
  98. },
  99. clearableInput: {
  100. paddingRight: '46px'
  101. },
  102. clearButton: {
  103. position: 'absolute',
  104. right: '16px',
  105. top: '10px',
  106. cursor: 'pointer',
  107. backgroundColor: theme.palette.action03,
  108. border: 0,
  109. padding: 0
  110. },
  111. bottomLabel: {
  112. marginTop: theme.spacing(2),
  113. ...withPixelLineHeight(theme.typography.labelRegular),
  114. color: theme.palette.text02,
  115. '&.is-mobile': {
  116. ...withPixelLineHeight(theme.typography.bodyShortRegular)
  117. },
  118. '&.error': {
  119. color: theme.palette.textError
  120. }
  121. }
  122. };
  123. });
  124. const Input = React.forwardRef<any, IProps>(({
  125. accessibilityLabel,
  126. autoComplete,
  127. autoFocus,
  128. bottomLabel,
  129. className,
  130. clearable = false,
  131. disabled,
  132. error,
  133. icon,
  134. iconClick,
  135. id,
  136. label,
  137. maxValue,
  138. maxLength,
  139. maxRows,
  140. minValue,
  141. minRows,
  142. name,
  143. onBlur,
  144. onChange,
  145. onFocus,
  146. onKeyPress,
  147. placeholder,
  148. readOnly = false,
  149. required,
  150. testId,
  151. textarea = false,
  152. type = 'text',
  153. value
  154. }: IProps, ref) => {
  155. const { classes: styles, cx } = useStyles();
  156. const isMobile = isMobileBrowser();
  157. const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
  158. onChange?.(e.target.value), []);
  159. const clearInput = useCallback(() => onChange?.(''), []);
  160. return (
  161. <div className = { cx(styles.inputContainer, className) }>
  162. {label && <span className = { cx(styles.label, isMobile && 'is-mobile') }>{label}</span>}
  163. <div className = { styles.fieldContainer }>
  164. {icon && <Icon
  165. { ...(iconClick ? { tabIndex: 0 } : {}) }
  166. className = { cx(styles.icon, iconClick && styles.iconClickable) }
  167. onClick = { iconClick }
  168. size = { 20 }
  169. src = { icon } />}
  170. {textarea ? (
  171. <TextareaAutosize
  172. aria-label = { accessibilityLabel }
  173. autoComplete = { autoComplete }
  174. autoFocus = { autoFocus }
  175. className = { cx(styles.input, isMobile && 'is-mobile',
  176. error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
  177. disabled = { disabled }
  178. { ...(id ? { id } : {}) }
  179. maxLength = { maxLength }
  180. maxRows = { maxRows }
  181. minRows = { minRows }
  182. name = { name }
  183. onChange = { handleChange }
  184. onKeyPress = { onKeyPress }
  185. placeholder = { placeholder }
  186. readOnly = { readOnly }
  187. ref = { ref }
  188. required = { required }
  189. value = { value } />
  190. ) : (
  191. <input
  192. aria-label = { accessibilityLabel }
  193. autoComplete = { autoComplete }
  194. autoFocus = { autoFocus }
  195. className = { cx(styles.input, isMobile && 'is-mobile',
  196. error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
  197. data-testid = { testId }
  198. disabled = { disabled }
  199. { ...(id ? { id } : {}) }
  200. { ...(type === 'number' ? { max: maxValue } : {}) }
  201. maxLength = { maxLength }
  202. { ...(type === 'number' ? { min: minValue } : {}) }
  203. name = { name }
  204. onBlur = { onBlur }
  205. onChange = { handleChange }
  206. onFocus = { onFocus }
  207. onKeyPress = { onKeyPress }
  208. placeholder = { placeholder }
  209. readOnly = { readOnly }
  210. ref = { ref }
  211. required = { required }
  212. type = { type }
  213. value = { value } />
  214. )}
  215. {clearable && !disabled && value !== '' && <button className = { styles.clearButton }>
  216. <Icon
  217. onClick = { clearInput }
  218. size = { 20 }
  219. src = { IconCloseCircle } />
  220. </button>}
  221. </div>
  222. {bottomLabel && (
  223. <span className = { cx(styles.bottomLabel, isMobile && 'is-mobile', error && 'error') }>
  224. {bottomLabel}
  225. </span>
  226. )}
  227. </div>
  228. );
  229. });
  230. export default Input;