TextField.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import React, { FC, useEffect, useState } from 'react';
  2. import classNames from 'classnames';
  3. import { Input, Form, InputNumber, Button } from 'antd';
  4. import { FieldUpdaterFunc } from '../../types/config-section';
  5. import { StatusState } from '../../utils/input-statuses';
  6. import { FormStatusIndicator } from './FormStatusIndicator';
  7. import { PASSWORD_COMPLEXITY_RULES, REGEX_PASSWORD } from '../../utils/config-constants';
  8. export const TEXTFIELD_TYPE_TEXT = 'default';
  9. export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
  10. export const TEXTFIELD_TYPE_NUMBER = 'numeric'; // InputNumber
  11. export const TEXTFIELD_TYPE_TEXTAREA = 'textarea'; // Input.TextArea
  12. export const TEXTFIELD_TYPE_URL = 'url';
  13. export type TextFieldProps = {
  14. fieldName: string;
  15. onSubmit?: () => void;
  16. onPressEnter?: () => void;
  17. onHandleSubmit?: () => void;
  18. className?: string;
  19. disabled?: boolean;
  20. label?: string;
  21. maxLength?: number;
  22. pattern?: string;
  23. placeholder?: string;
  24. required?: boolean;
  25. status?: StatusState;
  26. tip?: string;
  27. type?: string;
  28. useTrim?: boolean;
  29. useTrimLead?: boolean;
  30. value?: string | number;
  31. hasComplexityRequirements?: boolean;
  32. onBlur?: FieldUpdaterFunc;
  33. onChange?: FieldUpdaterFunc;
  34. };
  35. export const TextField: FC<TextFieldProps> = ({
  36. className,
  37. disabled,
  38. fieldName,
  39. label,
  40. maxLength,
  41. onBlur,
  42. onChange,
  43. onPressEnter,
  44. onHandleSubmit,
  45. pattern,
  46. placeholder,
  47. required,
  48. status,
  49. tip,
  50. type,
  51. useTrim,
  52. value,
  53. hasComplexityRequirements,
  54. }) => {
  55. const [hasPwdChanged, setHasPwdChanged] = useState(false);
  56. const [showPwdButton, setShowPwdButton] = useState(false);
  57. const [form] = Form.useForm();
  58. const handleChange = (e: any) => {
  59. // if an extra onChange handler was sent in as a prop, let's run that too.
  60. if (onChange) {
  61. const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value;
  62. setShowPwdButton(true);
  63. if (hasComplexityRequirements && REGEX_PASSWORD.test(val)) {
  64. setHasPwdChanged(true);
  65. } else {
  66. setHasPwdChanged(false);
  67. }
  68. onChange({ fieldName, value: useTrim ? val.trim() : val });
  69. }
  70. };
  71. useEffect(() => {
  72. form.setFieldsValue({ inputFieldPassword: value });
  73. }, [value]);
  74. // if you blur a required field with an empty value, restore its original value in state (parent's state), if an onChange from parent is available.
  75. const handleBlur = (e: any) => {
  76. const val = e.target.value;
  77. if (onBlur) {
  78. onBlur({ value: val });
  79. }
  80. };
  81. const handlePressEnter = () => {
  82. if (onPressEnter) {
  83. onPressEnter();
  84. }
  85. };
  86. // Password Complexity rules
  87. const passwordComplexityRules = [];
  88. // display the appropriate Ant text field
  89. let Field = Input as
  90. | typeof Input
  91. | typeof InputNumber
  92. | typeof Input.TextArea
  93. | typeof Input.Password;
  94. let fieldProps = {};
  95. if (type === TEXTFIELD_TYPE_TEXTAREA) {
  96. Field = Input.TextArea;
  97. fieldProps = {
  98. autoSize: true,
  99. };
  100. } else if (type === TEXTFIELD_TYPE_PASSWORD) {
  101. PASSWORD_COMPLEXITY_RULES.forEach(element => {
  102. passwordComplexityRules.push(element);
  103. });
  104. Field = Input.Password;
  105. fieldProps = {
  106. visibilityToggle: true,
  107. };
  108. } else if (type === TEXTFIELD_TYPE_NUMBER) {
  109. Field = InputNumber;
  110. fieldProps = {
  111. type: 'number',
  112. min: 1,
  113. max: 10 ** maxLength - 1,
  114. };
  115. } else if (type === TEXTFIELD_TYPE_URL) {
  116. fieldProps = {
  117. type: 'url',
  118. pattern,
  119. };
  120. }
  121. const fieldId = `field-${fieldName}`;
  122. const { type: statusType } = status || {};
  123. const containerClass = classNames({
  124. 'formfield-container': true,
  125. 'textfield-container': true,
  126. [`type-${type}`]: true,
  127. required,
  128. [`status-${statusType}`]: status,
  129. });
  130. return (
  131. <div className={containerClass}>
  132. {label ? (
  133. <div className="label-side">
  134. <label htmlFor={fieldId} className="formfield-label">
  135. {label}
  136. </label>
  137. </div>
  138. ) : null}
  139. {!hasComplexityRequirements ? (
  140. <div className="input-side">
  141. <div className="input-group">
  142. <Field
  143. id={fieldId}
  144. className={`field ${className} ${fieldId}`}
  145. {...fieldProps}
  146. {...(type !== TEXTFIELD_TYPE_NUMBER && { allowClear: true })}
  147. placeholder={placeholder}
  148. maxLength={maxLength}
  149. onChange={handleChange}
  150. onBlur={handleBlur}
  151. onPressEnter={handlePressEnter}
  152. disabled={disabled}
  153. value={value as number | (readonly string[] & number)}
  154. />
  155. </div>
  156. <FormStatusIndicator status={status} />
  157. <p className="field-tip">{tip}</p>
  158. </div>
  159. ) : (
  160. <div className="input-side">
  161. <div className="input-group">
  162. <Form
  163. name="basic"
  164. form={form}
  165. initialValues={{ inputFieldPassword: value }}
  166. style={{ width: '100%' }}
  167. >
  168. <Form.Item name="inputFieldPassword" rules={passwordComplexityRules}>
  169. <Input.Password
  170. id={fieldId}
  171. className={`field ${className} ${fieldId}`}
  172. onChange={handleChange}
  173. onBlur={handleBlur}
  174. placeholder={placeholder}
  175. onPressEnter={handlePressEnter}
  176. disabled={disabled}
  177. value={value as number | (readonly string[] & number)}
  178. />
  179. </Form.Item>
  180. {showPwdButton && (
  181. <div style={{ display: 'flex', flexDirection: 'row-reverse' }}>
  182. <Button
  183. type="primary"
  184. size="small"
  185. className="submit-button"
  186. onClick={onHandleSubmit}
  187. disabled={!hasPwdChanged}
  188. >
  189. Update
  190. </Button>
  191. </div>
  192. )}
  193. <FormStatusIndicator status={status} />
  194. <p className="field-tip">{tip}</p>
  195. </Form>
  196. </div>
  197. </div>
  198. )}
  199. </div>
  200. );
  201. };
  202. TextField.defaultProps = {
  203. className: '',
  204. disabled: false,
  205. label: '',
  206. maxLength: 255,
  207. placeholder: '',
  208. required: false,
  209. status: null,
  210. tip: '',
  211. type: TEXTFIELD_TYPE_TEXT,
  212. value: '',
  213. pattern: '',
  214. useTrim: false,
  215. useTrimLead: false,
  216. hasComplexityRequirements: false,
  217. onSubmit: () => {},
  218. onBlur: () => {},
  219. onChange: () => {},
  220. onPressEnter: () => {},
  221. onHandleSubmit: () => {},
  222. };