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

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