You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Input.tsx 7.5KB

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