JoyEasyForms.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import React from 'react';
  2. import { Dropdown, DropdownItemProps, DropdownProps } from 'semantic-ui-react';
  3. import { FormikProps, Field } from 'formik';
  4. import * as JoyForms from '@polkadot/joy-utils/forms';
  5. import { SubmittableResult } from '@polkadot/api';
  6. import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
  7. import { OnTxButtonClick } from '@polkadot/joy-utils/TxButton';
  8. import isEqual from 'lodash/isEqual';
  9. import { componentName } from '@polkadot/joy-utils/react/helpers';
  10. export type FormCallbacks = {
  11. onSubmit: OnTxButtonClick;
  12. onTxSuccess: TxCallback;
  13. onTxFailed: TxFailedCallback;
  14. };
  15. export type GenericEasyProp<FormValues> = {
  16. id: keyof FormValues;
  17. type: string;
  18. name: string;
  19. description?: string;
  20. required?: boolean;
  21. minItems?: number;
  22. maxItems?: number;
  23. minTextLength?: number;
  24. maxTextLength?: number;
  25. classId?: any;
  26. };
  27. type BaseFieldProps<OuterProps, FormValues> = OuterProps & FormikProps<FormValues> & {
  28. field: GenericEasyProp<FormValues>;
  29. };
  30. type EasyTextProps<OuterProps, FormValues> =
  31. BaseFieldProps<OuterProps, FormValues> & JoyForms.LabelledProps<FormValues>;
  32. type EasyFieldProps<OuterProps, FormValues> =
  33. BaseFieldProps<OuterProps, FormValues> &
  34. JoyForms.LabelledProps<FormValues> & {
  35. fieldProps: any;
  36. }
  37. type EasyDropdownProps<OuterProps, FormValues> =
  38. BaseFieldProps<OuterProps, FormValues> &
  39. {
  40. options: DropdownItemProps[];
  41. };
  42. type FormFields<OuterProps, FormValues> = {
  43. LabelledText: React.FunctionComponent<JoyForms.LabelledProps<FormValues>>;
  44. LabelledField: React.FunctionComponent<JoyForms.LabelledProps<FormValues>>;
  45. EasyText: React.FunctionComponent<EasyTextProps<OuterProps, FormValues>>;
  46. EasyField: React.FunctionComponent<EasyFieldProps<OuterProps, FormValues>>;
  47. EasyDropdown: React.FunctionComponent<EasyDropdownProps<OuterProps, FormValues>>;
  48. };
  49. export type EasyFormProps<OuterProps, FormValues> =
  50. OuterProps &
  51. FormikProps<FormValues> &
  52. FormFields<OuterProps, FormValues> &
  53. FormCallbacks & {
  54. isFieldChanged: (field: keyof FormValues | GenericEasyProp<FormValues>) => boolean;
  55. };
  56. export function withEasyForm<OuterProps, FormValues>
  57. (Component: React.ComponentType<EasyFormProps<OuterProps, FormValues>>) {
  58. type FieldName = keyof FormValues
  59. type FieldObject = GenericEasyProp<FormValues>
  60. const LabelledText = JoyForms.LabelledText<FormValues>();
  61. const LabelledField = JoyForms.LabelledField<FormValues>();
  62. function EasyText (props: EasyTextProps<OuterProps, FormValues>) {
  63. const { field: f } = props;
  64. return !f ? null : <LabelledText name={f.id} label={f.name} tooltip={f.description} required={f.required} {...props} />;
  65. }
  66. const EasyField = (props: EasyFieldProps<OuterProps, FormValues>) => {
  67. const { field: f, fieldProps = {}, placeholder, className, style, ...otherProps } = props;
  68. const { id } = f;
  69. const allFieldProps = {
  70. name: id,
  71. id,
  72. placeholder,
  73. className,
  74. style,
  75. disabled: otherProps.isSubmitting,
  76. ...fieldProps
  77. };
  78. return !f ? null : (
  79. <LabelledField name={id} label={f.name} tooltip={f.description} required={f.required} {...props}>
  80. <Field {...allFieldProps} />
  81. </LabelledField>
  82. );
  83. };
  84. const EasyDropdown = (props: EasyDropdownProps<OuterProps, FormValues>) => {
  85. const { field: f, options = [] } = props;
  86. const id = f.id as string;
  87. const value = (props.values as any)[id] || '';
  88. return <EasyField {...props} fieldProps={{
  89. component: Dropdown,
  90. selection: true,
  91. search: true,
  92. options,
  93. value,
  94. onBlur: (_event: any, _data: DropdownProps) => {
  95. props.setFieldTouched(id, true);
  96. },
  97. onChange: (_event: any, data: DropdownProps) => {
  98. props.setFieldValue(id, data.value);
  99. }
  100. }} />;
  101. };
  102. const ResultComponent: React.FunctionComponent<EasyFormProps<OuterProps, FormValues>> =
  103. (props: EasyFormProps<OuterProps, FormValues>) => {
  104. const {
  105. initialValues,
  106. values,
  107. dirty,
  108. touched,
  109. errors,
  110. isValid,
  111. setSubmitting
  112. } = props;
  113. const isFieldChanged = (field: FieldName | FieldObject): boolean => {
  114. const fieldName = typeof field === 'string' ? field : (field as FieldObject).id;
  115. return (
  116. dirty &&
  117. touched[fieldName] === true &&
  118. !isEqual(values[fieldName], initialValues[fieldName])
  119. );
  120. };
  121. const onSubmit = (sendTx: () => void) => {
  122. if (isValid) {
  123. sendTx();
  124. } else {
  125. console.log('Form is invalid. Errors:', errors);
  126. }
  127. };
  128. const onTxSuccess: TxCallback = (_txResult: SubmittableResult) => {
  129. setSubmitting(false);
  130. };
  131. const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
  132. setSubmitting(false);
  133. if (txResult === null) {
  134. // Tx cancelled
  135. }
  136. };
  137. const allProps = {
  138. ...props,
  139. // Callbacks:
  140. onSubmit,
  141. onTxSuccess,
  142. onTxFailed,
  143. // Components:
  144. LabelledText,
  145. LabelledField,
  146. EasyText,
  147. EasyField,
  148. EasyDropdown,
  149. // Other
  150. isFieldChanged
  151. };
  152. return <Component {...allProps} />;
  153. };
  154. ResultComponent.displayName = `withEasyForm(${componentName(Component)})`;
  155. return ResultComponent;
  156. }