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.3KB

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