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 6.8KB

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