您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Input.tsx 8.5KB

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