Input.tsx 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import React, { PureComponent } from 'react';
  2. import classNames from 'classnames';
  3. import { ValidationEvents, ValidationRule } from 'app/types';
  4. import { validate, hasValidationEvent } from 'app/core/utils/validate';
  5. export enum InputStatus {
  6. Invalid = 'invalid',
  7. Valid = 'valid',
  8. }
  9. export enum InputTypes {
  10. Text = 'text',
  11. Number = 'number',
  12. Password = 'password',
  13. Email = 'email',
  14. }
  15. export enum EventsWithValidation {
  16. onBlur = 'onBlur',
  17. onFocus = 'onFocus',
  18. onChange = 'onChange',
  19. }
  20. interface Props extends React.HTMLProps<HTMLInputElement> {
  21. validationEvents?: ValidationEvents;
  22. hideErrorMessage?: boolean;
  23. // Override event props and append status as argument
  24. onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
  25. onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
  26. onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
  27. }
  28. export class Input extends PureComponent<Props> {
  29. static defaultProps = {
  30. className: '',
  31. };
  32. state = {
  33. error: null,
  34. };
  35. get status() {
  36. return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
  37. }
  38. get isInvalid() {
  39. return this.status === InputStatus.Invalid;
  40. }
  41. validatorAsync = (validationRules: ValidationRule[]) => {
  42. return evt => {
  43. const errors = validate(evt.target.value, validationRules);
  44. this.setState(prevState => {
  45. return {
  46. ...prevState,
  47. error: errors ? errors[0] : null,
  48. };
  49. });
  50. };
  51. };
  52. populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
  53. const inputElementProps = { ...restProps };
  54. Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => {
  55. if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) {
  56. inputElementProps[eventName] = async evt => {
  57. evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
  58. if (hasValidationEvent(eventName, validationEvents)) {
  59. await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
  60. }
  61. if (restProps[eventName]) {
  62. restProps[eventName].apply(null, [evt, this.status]);
  63. }
  64. };
  65. }
  66. });
  67. return inputElementProps;
  68. };
  69. render() {
  70. const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
  71. const { error } = this.state;
  72. const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
  73. const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
  74. return (
  75. <div className="our-custom-wrapper-class">
  76. <input {...inputElementProps} className={inputClassName} />
  77. {error && !hideErrorMessage && <span>{error}</span>}
  78. </div>
  79. );
  80. }
  81. }