envoc-form 5.0.3 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +163 -20
  2. package/es/ConfirmBaseForm/ConfirmBaseForm.js +2 -2
  3. package/es/DatePicker/DatePickerGroup.js +2 -2
  4. package/es/FieldArray/FieldArray.js +4 -4
  5. package/es/File/FileGroup.js +3 -3
  6. package/es/Form/Form.js +2 -2
  7. package/es/Group.js +3 -3
  8. package/es/Input/CheckboxGroup.d.ts +6 -0
  9. package/es/Input/CheckboxGroup.js +14 -0
  10. package/es/Input/CheckboxInputGroup.d.ts +13 -0
  11. package/es/Input/CheckboxInputGroup.js +41 -0
  12. package/es/Input/IconInputGroup.js +2 -2
  13. package/es/Input/InputGroup.js +2 -2
  14. package/es/Input/MoneyInputGroup.js +2 -2
  15. package/es/Input/NumberInputGroup.js +2 -2
  16. package/es/Input/PhoneNumberInputGroup.js +2 -2
  17. package/es/Input/StringInputGroup.js +2 -2
  18. package/es/Select/SelectGroup.js +3 -3
  19. package/es/StandardFormActions.js +3 -3
  20. package/es/SubmitFormButton.js +2 -2
  21. package/es/TextArea/TextAreaGroup.js +2 -2
  22. package/es/index.d.ts +2 -0
  23. package/es/index.js +1 -0
  24. package/lib/ConfirmBaseForm/ConfirmBaseForm.js +2 -5
  25. package/lib/DatePicker/DatePickerGroup.js +2 -2
  26. package/lib/FieldArray/FieldArray.js +4 -4
  27. package/lib/File/FileGroup.js +3 -3
  28. package/lib/Form/Form.js +2 -2
  29. package/lib/Group.js +3 -3
  30. package/lib/Input/CheckboxGroup.d.ts +6 -0
  31. package/lib/Input/CheckboxGroup.js +20 -0
  32. package/lib/Input/CheckboxInputGroup.d.ts +13 -0
  33. package/lib/Input/CheckboxInputGroup.js +46 -0
  34. package/lib/Input/IconInputGroup.js +2 -2
  35. package/lib/Input/InputGroup.js +2 -2
  36. package/lib/Input/MoneyInputGroup.js +2 -2
  37. package/lib/Input/NumberInputGroup.js +2 -2
  38. package/lib/Input/PhoneNumberInputGroup.js +2 -2
  39. package/lib/Input/StringInputGroup.js +2 -2
  40. package/lib/Select/SelectGroup.js +3 -3
  41. package/lib/StandardFormActions.js +3 -3
  42. package/lib/SubmitFormButton.js +2 -2
  43. package/lib/TextArea/TextAreaGroup.js +2 -2
  44. package/lib/index.d.ts +2 -0
  45. package/lib/index.js +3 -1
  46. package/package.json +111 -111
  47. package/src/AddressInput/AddressInput.test.tsx +27 -27
  48. package/src/AddressInput/AddressInput.tsx +82 -82
  49. package/src/AddressInput/UsStates.ts +55 -55
  50. package/src/AddressInput/__snapshots__/AddressInput.test.tsx.snap +182 -182
  51. package/src/ConfirmBaseForm/ConfirmBaseForm.test.tsx +24 -24
  52. package/src/ConfirmBaseForm/ConfirmBaseForm.tsx +74 -74
  53. package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.tsx.snap +23 -23
  54. package/src/ConfirmDeleteForm/ConfirmDeleteForm.test.tsx +24 -24
  55. package/src/ConfirmDeleteForm/ConfirmDeleteForm.tsx +87 -87
  56. package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.tsx.snap +25 -25
  57. package/src/DatePicker/DatePicker.test.tsx +48 -48
  58. package/src/DatePicker/DatePickerGroup.tsx +109 -115
  59. package/src/DatePicker/DatePickerHelper.ts +4 -4
  60. package/src/DatePicker/StringDateOnlyPickerGroup.tsx +28 -28
  61. package/src/DatePicker/StringDatePickerGroup.tsx +20 -20
  62. package/src/DatePicker/__snapshots__/DatePicker.test.tsx.snap +152 -152
  63. package/src/Field/CustomFieldInputProps.ts +10 -10
  64. package/src/Field/CustomFieldMetaProps.ts +5 -5
  65. package/src/Field/Field.tsx +113 -113
  66. package/src/Field/FieldErrorScrollTarget.tsx +12 -12
  67. package/src/Field/FieldNameContext.ts +6 -6
  68. package/src/Field/FieldSection.tsx +18 -18
  69. package/src/Field/InjectedFieldProps.ts +8 -8
  70. package/src/Field/StandAloneInput.tsx +55 -55
  71. package/src/Field/useStandardField.ts +125 -125
  72. package/src/FieldArray/FieldArray.tsx +154 -154
  73. package/src/File/FileGroup.test.tsx +35 -35
  74. package/src/File/FileGroup.tsx +82 -85
  75. package/src/File/FileList.tsx +21 -21
  76. package/src/File/__snapshots__/FileGroup.test.tsx.snap +34 -34
  77. package/src/File/humanFileSize.ts +8 -8
  78. package/src/Form/FocusError.tsx +55 -55
  79. package/src/Form/Form.test.tsx +14 -14
  80. package/src/Form/Form.tsx +234 -237
  81. package/src/Form/FormBasedPreventNavigation.tsx +56 -56
  82. package/src/Form/LegacyFormBasedPreventNavigation.tsx +77 -77
  83. package/src/Form/NewFormBasedPreventNavigation.tsx +59 -59
  84. package/src/Form/ServerErrorContext.ts +18 -18
  85. package/src/Form/__snapshots__/Form.test.tsx.snap +10 -10
  86. package/src/FormActions.tsx +47 -47
  87. package/src/FormDefaults.ts +2 -2
  88. package/src/Group.tsx +62 -62
  89. package/src/Input/CheckboxGroup.tsx +60 -0
  90. package/src/Input/CheckboxInputGroup.tsx +78 -0
  91. package/src/Input/IconInputGroup.tsx +54 -54
  92. package/src/Input/InputGroup.tsx +66 -72
  93. package/src/Input/MoneyInputGroup.tsx +47 -50
  94. package/src/Input/NumberInputGroup.tsx +45 -48
  95. package/src/Input/PhoneNumberInputGroup.tsx +45 -45
  96. package/src/Input/StringInputGroup.tsx +50 -53
  97. package/src/Input/__Tests__/CheckboxInputGroup.test.tsx +26 -0
  98. package/src/Input/__Tests__/IconInputGroup.test.tsx +35 -35
  99. package/src/Input/__Tests__/MoneyInputGroup.test.tsx +37 -37
  100. package/src/Input/__Tests__/NumberInputGroup.test.tsx +35 -35
  101. package/src/Input/__Tests__/PhoneNumberInputGroup.test.tsx +36 -36
  102. package/src/Input/__Tests__/StringInputGroup.test.tsx +27 -27
  103. package/src/Input/__Tests__/__snapshots__/CheckboxInputGroup.test.tsx.snap +33 -0
  104. package/src/Input/__Tests__/__snapshots__/IconInputGroup.test.tsx.snap +32 -32
  105. package/src/Input/__Tests__/__snapshots__/MoneyInputGroup.test.tsx.snap +34 -34
  106. package/src/Input/__Tests__/__snapshots__/NumberInputGroup.test.tsx.snap +32 -32
  107. package/src/Input/__Tests__/__snapshots__/PhoneNumberInputGroup.test.tsx.snap +33 -33
  108. package/src/Input/__Tests__/__snapshots__/StringInputGroup.test.tsx.snap +31 -31
  109. package/src/Normalization/NormalizationFunction.ts +4 -4
  110. package/src/Normalization/normalizers.ts +44 -44
  111. package/src/Select/BooleanSelectGroup.tsx +28 -28
  112. package/src/Select/NumberSelectGroup.tsx +16 -16
  113. package/src/Select/SelectGroup.tsx +124 -124
  114. package/src/Select/SelectGroupPropsHelper.ts +4 -4
  115. package/src/Select/StringSelectGroup.tsx +16 -16
  116. package/src/Select/__tests__/BooleanSelectGroup.test.tsx +35 -35
  117. package/src/Select/__tests__/NumberSelectGroup.test.tsx +87 -87
  118. package/src/Select/__tests__/StringSelectGroup.test.tsx +89 -89
  119. package/src/Select/__tests__/__snapshots__/BooleanSelectGroup.test.tsx.snap +98 -98
  120. package/src/Select/__tests__/__snapshots__/NumberSelectGroup.test.tsx.snap +195 -195
  121. package/src/Select/__tests__/__snapshots__/StringSelectGroup.test.tsx.snap +195 -195
  122. package/src/StandardFormActions.tsx +41 -41
  123. package/src/SubmitFormButton.tsx +54 -54
  124. package/src/TextArea/TextAreaGroup.tsx +64 -64
  125. package/src/Validation/ValidatedApiResult.ts +8 -8
  126. package/src/Validation/ValidationError.ts +6 -6
  127. package/src/Validation/ValidationFunction.ts +4 -4
  128. package/src/Validation/validators.test.tsx +81 -81
  129. package/src/Validation/validators.ts +97 -97
  130. package/src/__Tests__/FormTestBase.tsx +65 -64
  131. package/src/__Tests__/RealisticForm.test.tsx +82 -82
  132. package/src/__Tests__/StandardFormActions.test.tsx +17 -17
  133. package/src/__Tests__/SubmitFormButton.test.tsx +17 -17
  134. package/src/__Tests__/__snapshots__/StandardFormActions.test.tsx.snap +27 -27
  135. package/src/__Tests__/__snapshots__/SubmitFormButton.test.tsx.snap +20 -20
  136. package/src/__Tests__/index.ts +3 -3
  137. package/src/_variables.scss +11 -11
  138. package/src/index.ts +156 -153
  139. package/src/react-app-env.d.ts +1 -1
  140. package/src/setupTests.ts +1 -1
  141. package/src/utils/objectContainsNonSerializableProperty.test.tsx +49 -49
  142. package/src/utils/objectContainsNonSerializableProperty.ts +17 -17
  143. package/src/utils/objectToFormData.test.tsx +76 -76
  144. package/src/utils/objectToFormData.ts +105 -105
  145. package/src/utils/typeChecks.ts +18 -18
@@ -1,35 +1,35 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import { FileGroup } from '../';
4
- import FormTestBase from '../__Tests__/FormTestBase';
5
-
6
- describe('FileGroup', () => {
7
- it('renders without crashing', () => {
8
- render(
9
- <FormTestBase>
10
- {({ Field }) => (
11
- <Field
12
- name="profileImage"
13
- Component={FileGroup}
14
- label="Profile Image"
15
- />
16
- )}
17
- </FormTestBase>
18
- );
19
- });
20
-
21
- it('has matching snapshot', () => {
22
- const renderResult = render(
23
- <FormTestBase>
24
- {({ Field }) => (
25
- <Field
26
- name="profileImage"
27
- Component={FileGroup}
28
- label="Profile Image"
29
- />
30
- )}
31
- </FormTestBase>
32
- );
33
- expect(renderResult.asFragment()).toMatchSnapshot();
34
- });
35
- });
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { FileGroup } from '../';
4
+ import FormTestBase from '../__Tests__/FormTestBase';
5
+
6
+ describe('FileGroup', () => {
7
+ it('renders without crashing', () => {
8
+ render(
9
+ <FormTestBase>
10
+ {({ Field }) => (
11
+ <Field
12
+ name="profileImage"
13
+ Component={FileGroup}
14
+ label="Profile Image"
15
+ />
16
+ )}
17
+ </FormTestBase>
18
+ );
19
+ });
20
+
21
+ it('has matching snapshot', () => {
22
+ const renderResult = render(
23
+ <FormTestBase>
24
+ {({ Field }) => (
25
+ <Field
26
+ name="profileImage"
27
+ Component={FileGroup}
28
+ label="Profile Image"
29
+ />
30
+ )}
31
+ </FormTestBase>
32
+ );
33
+ expect(renderResult.asFragment()).toMatchSnapshot();
34
+ });
35
+ });
@@ -1,85 +1,82 @@
1
- import React, { ComponentType, LegacyRef } from 'react';
2
- import classNames from 'classnames';
3
- import FileList from './FileList';
4
- import { InjectedFieldProps } from '../Field/InjectedFieldProps';
5
- import { FormDefaults } from '../FormDefaults';
6
- import Group, { GroupProps } from '../Group';
7
-
8
- export interface FileGroupProps
9
- // note: file props are of type "any" with the current type generation
10
- extends InjectedFieldProps<any | undefined | null>,
11
- Omit<GroupProps, keyof InjectedFieldProps<any> | 'children'>,
12
- Omit<
13
- React.HTMLProps<HTMLInputElement>,
14
- keyof InjectedFieldProps<any> | 'children' | 'className' | 'label'
15
- > {
16
- /** Allow multiple files to be uploaded. */
17
- multiple?: boolean | undefined;
18
- }
19
-
20
- function FileGroup(
21
- {
22
- input,
23
- meta,
24
- label,
25
- helpText,
26
- className,
27
- required,
28
- disabled,
29
- multiple,
30
- ...rest
31
- }: FileGroupProps,
32
- ref: LegacyRef<HTMLInputElement>
33
- ) {
34
- return (
35
- <Group
36
- input={input}
37
- meta={meta}
38
- label={label}
39
- helpText={helpText}
40
- className={classNames(
41
- className,
42
- { [FormDefaults.cssClassPrefix + 'multiple']: multiple },
43
- FormDefaults.cssClassPrefix + 'file-group'
44
- )}
45
- required={required}
46
- disabled={disabled}>
47
- <input
48
- {...input}
49
- {...rest}
50
- multiple={multiple}
51
- onChange={(e) => {
52
- if (!e?.target?.files?.length) {
53
- input.onChange(undefined);
54
- } else {
55
- const files: File[] = [];
56
- for (let i = 0; i < e.target.files.length; i++) {
57
- files.push(e.target.files[i]);
58
- }
59
- if (!multiple) {
60
- input.onChange(files[0]);
61
- } else {
62
- input.onChange(files);
63
- }
64
- }
65
- }}
66
- value={undefined}
67
- ref={ref}
68
- type="file"
69
- className={classNames(
70
- className,
71
- FormDefaults.cssClassPrefix + 'file-group'
72
- )}
73
- />
74
- {/* Note: because input.value is any - due to how files are currently handled - type safeness isn't great here */}
75
- <FileList files={input.value} />
76
- </Group>
77
- );
78
- }
79
-
80
- /** File upload input group. */
81
- const FileGroupWithRef = React.forwardRef(
82
- FileGroup
83
- ) as ComponentType<FileGroupProps>;
84
-
85
- export default FileGroupWithRef;
1
+ import React, { ComponentType, LegacyRef } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import FileList from './FileList';
4
+ import { InjectedFieldProps } from '../Field/InjectedFieldProps';
5
+ import { FormDefaults } from '../FormDefaults';
6
+ import Group, { GroupProps } from '../Group';
7
+
8
+ export interface FileGroupProps
9
+ // note: file props are of type "any" with the current type generation
10
+ extends InjectedFieldProps<any | undefined | null>,
11
+ Omit<GroupProps, keyof InjectedFieldProps<any> | 'children'>,
12
+ Omit<
13
+ React.HTMLProps<HTMLInputElement>,
14
+ keyof InjectedFieldProps<any> | 'children' | 'className' | 'label'
15
+ > {
16
+ /** Allow multiple files to be uploaded. */
17
+ multiple?: boolean | undefined;
18
+ }
19
+
20
+ function FileGroup(
21
+ {
22
+ input,
23
+ meta,
24
+ label,
25
+ helpText,
26
+ className,
27
+ required,
28
+ disabled,
29
+ multiple,
30
+ ...rest
31
+ }: FileGroupProps,
32
+ ref: LegacyRef<HTMLInputElement>
33
+ ) {
34
+ return (
35
+ <Group
36
+ input={input}
37
+ meta={meta}
38
+ label={label}
39
+ helpText={helpText}
40
+ className={clsx(
41
+ className,
42
+ { [FormDefaults.cssClassPrefix + 'multiple']: multiple },
43
+ FormDefaults.cssClassPrefix + 'file-group'
44
+ )}
45
+ required={required}
46
+ disabled={disabled}>
47
+ <input
48
+ {...input}
49
+ {...rest}
50
+ multiple={multiple}
51
+ onChange={(e) => {
52
+ if (!e?.target?.files?.length) {
53
+ input.onChange(undefined);
54
+ } else {
55
+ const files: File[] = [];
56
+ for (let i = 0; i < e.target.files.length; i++) {
57
+ files.push(e.target.files[i]);
58
+ }
59
+ if (!multiple) {
60
+ input.onChange(files[0]);
61
+ } else {
62
+ input.onChange(files);
63
+ }
64
+ }
65
+ }}
66
+ value={undefined}
67
+ ref={ref}
68
+ type="file"
69
+ className={clsx(className, FormDefaults.cssClassPrefix + 'file-group')}
70
+ />
71
+ {/* Note: because input.value is any - due to how files are currently handled - type safeness isn't great here */}
72
+ <FileList files={input.value} />
73
+ </Group>
74
+ );
75
+ }
76
+
77
+ /** File upload input group. */
78
+ const FileGroupWithRef = React.forwardRef(
79
+ FileGroup
80
+ ) as ComponentType<FileGroupProps>;
81
+
82
+ export default FileGroupWithRef;
@@ -1,21 +1,21 @@
1
- import { FormDefaults } from '../FormDefaults';
2
-
3
- export interface FileListProps {
4
- files?: File | File[] | undefined | null;
5
- rejectedFiles?: File | File[] | undefined | null;
6
- }
7
- export default function FileList({ files, rejectedFiles }: FileListProps) {
8
- return (
9
- <div className={FormDefaults.cssClassPrefix + 'file-list'}>
10
- {!files ? null : Array.isArray(files) ? (
11
- files.map((x, i) => <File file={x} key={i} />)
12
- ) : (
13
- <File file={files} />
14
- )}
15
- </div>
16
- );
17
- }
18
-
19
- function File({ file }: { file: File }) {
20
- return null;
21
- }
1
+ import { FormDefaults } from '../FormDefaults';
2
+
3
+ export interface FileListProps {
4
+ files?: File | File[] | undefined | null;
5
+ rejectedFiles?: File | File[] | undefined | null;
6
+ }
7
+ export default function FileList({ files, rejectedFiles }: FileListProps) {
8
+ return (
9
+ <div className={FormDefaults.cssClassPrefix + 'file-list'}>
10
+ {!files ? null : Array.isArray(files) ? (
11
+ files.map((x, i) => <File file={x} key={i} />)
12
+ ) : (
13
+ <File file={files} />
14
+ )}
15
+ </div>
16
+ );
17
+ }
18
+
19
+ function File({ file }: { file: File }) {
20
+ return null;
21
+ }
@@ -1,34 +1,34 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`FileGroup has matching snapshot 1`] = `
4
- <DocumentFragment>
5
- <form
6
- action="#"
7
- class="envoc-form-form"
8
- >
9
- <div
10
- class="envoc-form-file-group envoc-form-group"
11
- >
12
- <div
13
- id="profileimage-error-scroll-target"
14
- style="display: none;"
15
- />
16
- <label
17
- for="profileImage"
18
- >
19
- Profile Image
20
- </label>
21
- <input
22
- class="envoc-form-file-group"
23
- id="profileImage"
24
- name="profileImage"
25
- type="file"
26
- value=""
27
- />
28
- <div
29
- class="envoc-form-file-list"
30
- />
31
- </div>
32
- </form>
33
- </DocumentFragment>
34
- `;
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`FileGroup has matching snapshot 1`] = `
4
+ <DocumentFragment>
5
+ <form
6
+ action="#"
7
+ class="envoc-form-form"
8
+ >
9
+ <div
10
+ class="envoc-form-file-group envoc-form-group"
11
+ >
12
+ <div
13
+ id="profileimage-error-scroll-target"
14
+ style="display: none;"
15
+ />
16
+ <label
17
+ for="profileImage"
18
+ >
19
+ Profile Image
20
+ </label>
21
+ <input
22
+ class="envoc-form-file-group"
23
+ id="profileImage"
24
+ name="profileImage"
25
+ type="file"
26
+ value=""
27
+ />
28
+ <div
29
+ class="envoc-form-file-list"
30
+ />
31
+ </div>
32
+ </form>
33
+ </DocumentFragment>
34
+ `;
@@ -1,8 +1,8 @@
1
- export function humanFileSize(size: number) {
2
- const i = Math.floor(Math.log(size) / Math.log(1024));
3
- return (
4
- (size / Math.pow(1024, i)).toFixed(2) +
5
- ' ' +
6
- ['B', 'KB', 'MB', 'GB', 'TB'][i]
7
- );
8
- }
1
+ export function humanFileSize(size: number) {
2
+ const i = Math.floor(Math.log(size) / Math.log(1024));
3
+ return (
4
+ (size / Math.pow(1024, i)).toFixed(2) +
5
+ ' ' +
6
+ ['B', 'KB', 'MB', 'GB', 'TB'][i]
7
+ );
8
+ }
@@ -1,55 +1,55 @@
1
- import { useEffect } from 'react';
2
- import { useFormikContext } from 'formik';
3
- import smoothscroll from 'smoothscroll-polyfill';
4
- import { ServerErrorContextProps } from './ServerErrorContext';
5
-
6
- export interface FocusErrorProps {
7
- /** Validation errors that have been received from the server. */
8
- serverErrors: ServerErrorContextProps;
9
- }
10
-
11
- /** Function to scroll to the field that has an error. */
12
- export default function FocusError(props: FocusErrorProps) {
13
- const { errors, isSubmitting, isValidating } = useFormikContext();
14
- smoothscroll.polyfill();
15
- useEffect(() => {
16
- if (!isSubmitting || isValidating) {
17
- return;
18
- }
19
- //This block handles any front-end input validation errors
20
- //e.g. required fields, max characters, etc
21
- const keys = Object.keys(errors);
22
- if (keys.length > 0) {
23
- return scrollToErrorElement(keys);
24
- }
25
- //This block handles any input-specific server-side errors
26
- //e.g. improper email formatting, invalid phone number, etc.
27
- if (
28
- props.serverErrors.errors &&
29
- Object.values(props.serverErrors.errors).some((x) => !!x)
30
- ) {
31
- const names = Object.keys(props.serverErrors.errors);
32
- return scrollToErrorElement(names);
33
- }
34
- }, [errors, isSubmitting, isValidating, props]);
35
- return null;
36
- }
37
-
38
- const scrollToErrorElement = (keys: string[]) => {
39
- let firstErrorElement: HTMLElement | null = document.getElementById(
40
- `${keys[0].toLowerCase()}-error-scroll-target`
41
- );
42
- if (!firstErrorElement || !firstErrorElement.parentNode) {
43
- return;
44
- }
45
- firstErrorElement = firstErrorElement.parentNode as HTMLElement;
46
- const headerOffset = -110;
47
- const y =
48
- firstErrorElement.getBoundingClientRect().top +
49
- window.pageYOffset +
50
- headerOffset;
51
- window.scrollTo({ top: y, behavior: 'smooth' });
52
- setTimeout(() => {
53
- firstErrorElement?.focus();
54
- }, 500);
55
- };
1
+ import { useEffect } from 'react';
2
+ import { useFormikContext } from 'formik';
3
+ import smoothscroll from 'smoothscroll-polyfill';
4
+ import { ServerErrorContextProps } from './ServerErrorContext';
5
+
6
+ export interface FocusErrorProps {
7
+ /** Validation errors that have been received from the server. */
8
+ serverErrors: ServerErrorContextProps;
9
+ }
10
+
11
+ /** Function to scroll to the field that has an error. */
12
+ export default function FocusError(props: FocusErrorProps) {
13
+ const { errors, isSubmitting, isValidating } = useFormikContext();
14
+ smoothscroll.polyfill();
15
+ useEffect(() => {
16
+ if (!isSubmitting || isValidating) {
17
+ return;
18
+ }
19
+ //This block handles any front-end input validation errors
20
+ //e.g. required fields, max characters, etc
21
+ const keys = Object.keys(errors);
22
+ if (keys.length > 0) {
23
+ return scrollToErrorElement(keys);
24
+ }
25
+ //This block handles any input-specific server-side errors
26
+ //e.g. improper email formatting, invalid phone number, etc.
27
+ if (
28
+ props.serverErrors.errors &&
29
+ Object.values(props.serverErrors.errors).some((x) => !!x)
30
+ ) {
31
+ const names = Object.keys(props.serverErrors.errors);
32
+ return scrollToErrorElement(names);
33
+ }
34
+ }, [errors, isSubmitting, isValidating, props]);
35
+ return null;
36
+ }
37
+
38
+ const scrollToErrorElement = (keys: string[]) => {
39
+ let firstErrorElement: HTMLElement | null = document.getElementById(
40
+ `${keys[0].toLowerCase()}-error-scroll-target`
41
+ );
42
+ if (!firstErrorElement || !firstErrorElement.parentNode) {
43
+ return;
44
+ }
45
+ firstErrorElement = firstErrorElement.parentNode as HTMLElement;
46
+ const headerOffset = -110;
47
+ const y =
48
+ firstErrorElement.getBoundingClientRect().top +
49
+ window.pageYOffset +
50
+ headerOffset;
51
+ window.scrollTo({ top: y, behavior: 'smooth' });
52
+ setTimeout(() => {
53
+ firstErrorElement?.focus();
54
+ }, 500);
55
+ };
@@ -1,14 +1,14 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import FormTestBase from '../__Tests__/FormTestBase';
4
-
5
- describe('FormTestBase', () => {
6
- it('renders without crashing', () => {
7
- render(<FormTestBase>{() => <></>}</FormTestBase>);
8
- });
9
-
10
- it('has matching snapshot', () => {
11
- const renderResult = render(<FormTestBase>{() => <></>}</FormTestBase>);
12
- expect(renderResult.asFragment()).toMatchSnapshot();
13
- });
14
- });
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import FormTestBase from '../__Tests__/FormTestBase';
4
+
5
+ describe('FormTestBase', () => {
6
+ it('renders without crashing', () => {
7
+ render(<FormTestBase>{() => <></>}</FormTestBase>);
8
+ });
9
+
10
+ it('has matching snapshot', () => {
11
+ const renderResult = render(<FormTestBase>{() => <></>}</FormTestBase>);
12
+ expect(renderResult.asFragment()).toMatchSnapshot();
13
+ });
14
+ });