envoc-form 5.0.3 → 5.0.5
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.
- package/README.md +158 -15
- package/es/Input/CheckboxGroup.d.ts +6 -0
- package/es/Input/CheckboxGroup.js +14 -0
- package/es/Input/CheckboxInputGroup.d.ts +13 -0
- package/es/Input/CheckboxInputGroup.js +41 -0
- package/es/index.d.ts +2 -0
- package/es/index.js +1 -0
- package/lib/Input/CheckboxGroup.d.ts +6 -0
- package/lib/Input/CheckboxGroup.js +20 -0
- package/lib/Input/CheckboxInputGroup.d.ts +13 -0
- package/lib/Input/CheckboxInputGroup.js +46 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -1
- package/package.json +111 -111
- package/src/AddressInput/AddressInput.test.tsx +27 -27
- package/src/AddressInput/AddressInput.tsx +82 -82
- package/src/AddressInput/UsStates.ts +55 -55
- package/src/AddressInput/__snapshots__/AddressInput.test.tsx.snap +182 -182
- package/src/ConfirmBaseForm/ConfirmBaseForm.test.tsx +24 -24
- package/src/ConfirmBaseForm/ConfirmBaseForm.tsx +74 -74
- package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.tsx.snap +23 -23
- package/src/ConfirmDeleteForm/ConfirmDeleteForm.test.tsx +24 -24
- package/src/ConfirmDeleteForm/ConfirmDeleteForm.tsx +87 -87
- package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.tsx.snap +25 -25
- package/src/DatePicker/DatePicker.test.tsx +48 -48
- package/src/DatePicker/DatePickerGroup.tsx +115 -115
- package/src/DatePicker/DatePickerHelper.ts +4 -4
- package/src/DatePicker/StringDateOnlyPickerGroup.tsx +28 -28
- package/src/DatePicker/StringDatePickerGroup.tsx +20 -20
- package/src/DatePicker/__snapshots__/DatePicker.test.tsx.snap +152 -152
- package/src/Field/CustomFieldInputProps.ts +10 -10
- package/src/Field/CustomFieldMetaProps.ts +5 -5
- package/src/Field/Field.tsx +113 -113
- package/src/Field/FieldErrorScrollTarget.tsx +12 -12
- package/src/Field/FieldNameContext.ts +6 -6
- package/src/Field/FieldSection.tsx +18 -18
- package/src/Field/InjectedFieldProps.ts +8 -8
- package/src/Field/StandAloneInput.tsx +55 -55
- package/src/Field/useStandardField.ts +125 -125
- package/src/FieldArray/FieldArray.tsx +154 -154
- package/src/File/FileGroup.test.tsx +35 -35
- package/src/File/FileGroup.tsx +85 -85
- package/src/File/FileList.tsx +21 -21
- package/src/File/__snapshots__/FileGroup.test.tsx.snap +34 -34
- package/src/File/humanFileSize.ts +8 -8
- package/src/Form/FocusError.tsx +55 -55
- package/src/Form/Form.test.tsx +14 -14
- package/src/Form/Form.tsx +237 -237
- package/src/Form/FormBasedPreventNavigation.tsx +56 -56
- package/src/Form/LegacyFormBasedPreventNavigation.tsx +77 -77
- package/src/Form/NewFormBasedPreventNavigation.tsx +59 -59
- package/src/Form/ServerErrorContext.ts +18 -18
- package/src/Form/__snapshots__/Form.test.tsx.snap +10 -10
- package/src/FormActions.tsx +47 -47
- package/src/FormDefaults.ts +2 -2
- package/src/Group.tsx +62 -62
- package/src/Input/CheckboxGroup.tsx +60 -0
- package/src/Input/CheckboxInputGroup.tsx +78 -0
- package/src/Input/IconInputGroup.tsx +54 -54
- package/src/Input/InputGroup.tsx +72 -72
- package/src/Input/MoneyInputGroup.tsx +50 -50
- package/src/Input/NumberInputGroup.tsx +48 -48
- package/src/Input/PhoneNumberInputGroup.tsx +45 -45
- package/src/Input/StringInputGroup.tsx +53 -53
- package/src/Input/__Tests__/CheckboxInputGroup.test.tsx +26 -0
- package/src/Input/__Tests__/IconInputGroup.test.tsx +35 -35
- package/src/Input/__Tests__/MoneyInputGroup.test.tsx +37 -37
- package/src/Input/__Tests__/NumberInputGroup.test.tsx +35 -35
- package/src/Input/__Tests__/PhoneNumberInputGroup.test.tsx +36 -36
- package/src/Input/__Tests__/StringInputGroup.test.tsx +27 -27
- package/src/Input/__Tests__/__snapshots__/CheckboxInputGroup.test.tsx.snap +33 -0
- package/src/Input/__Tests__/__snapshots__/IconInputGroup.test.tsx.snap +32 -32
- package/src/Input/__Tests__/__snapshots__/MoneyInputGroup.test.tsx.snap +34 -34
- package/src/Input/__Tests__/__snapshots__/NumberInputGroup.test.tsx.snap +32 -32
- package/src/Input/__Tests__/__snapshots__/PhoneNumberInputGroup.test.tsx.snap +33 -33
- package/src/Input/__Tests__/__snapshots__/StringInputGroup.test.tsx.snap +31 -31
- package/src/Normalization/NormalizationFunction.ts +4 -4
- package/src/Normalization/normalizers.ts +44 -44
- package/src/Select/BooleanSelectGroup.tsx +28 -28
- package/src/Select/NumberSelectGroup.tsx +16 -16
- package/src/Select/SelectGroup.tsx +124 -124
- package/src/Select/SelectGroupPropsHelper.ts +4 -4
- package/src/Select/StringSelectGroup.tsx +16 -16
- package/src/Select/__tests__/BooleanSelectGroup.test.tsx +35 -35
- package/src/Select/__tests__/NumberSelectGroup.test.tsx +87 -87
- package/src/Select/__tests__/StringSelectGroup.test.tsx +89 -89
- package/src/Select/__tests__/__snapshots__/BooleanSelectGroup.test.tsx.snap +98 -98
- package/src/Select/__tests__/__snapshots__/NumberSelectGroup.test.tsx.snap +195 -195
- package/src/Select/__tests__/__snapshots__/StringSelectGroup.test.tsx.snap +195 -195
- package/src/StandardFormActions.tsx +41 -41
- package/src/SubmitFormButton.tsx +54 -54
- package/src/TextArea/TextAreaGroup.tsx +64 -64
- package/src/Validation/ValidatedApiResult.ts +8 -8
- package/src/Validation/ValidationError.ts +6 -6
- package/src/Validation/ValidationFunction.ts +4 -4
- package/src/Validation/validators.test.tsx +81 -81
- package/src/Validation/validators.ts +97 -97
- package/src/__Tests__/FormTestBase.tsx +65 -64
- package/src/__Tests__/RealisticForm.test.tsx +82 -82
- package/src/__Tests__/StandardFormActions.test.tsx +17 -17
- package/src/__Tests__/SubmitFormButton.test.tsx +17 -17
- package/src/__Tests__/__snapshots__/StandardFormActions.test.tsx.snap +27 -27
- package/src/__Tests__/__snapshots__/SubmitFormButton.test.tsx.snap +20 -20
- package/src/__Tests__/index.ts +3 -3
- package/src/_variables.scss +11 -11
- package/src/index.ts +156 -153
- package/src/react-app-env.d.ts +1 -1
- package/src/setupTests.ts +1 -1
- package/src/utils/objectContainsNonSerializableProperty.test.tsx +49 -49
- package/src/utils/objectContainsNonSerializableProperty.ts +17 -17
- package/src/utils/objectToFormData.test.tsx +76 -76
- package/src/utils/objectToFormData.ts +105 -105
- 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
|
+
});
|
package/src/File/FileGroup.tsx
CHANGED
@@ -1,85 +1,85 @@
|
|
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 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;
|
package/src/File/FileList.tsx
CHANGED
@@ -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
|
+
}
|
package/src/Form/FocusError.tsx
CHANGED
@@ -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
|
+
};
|
package/src/Form/Form.test.tsx
CHANGED
@@ -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
|
+
});
|