@availity/mui-controlled-form 0.2.5 → 0.3.0
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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/index.d.mts +122 -34
- package/dist/index.d.ts +122 -34
- package/dist/index.js +263 -225
- package/dist/index.mjs +279 -240
- package/package.json +2 -2
- package/src/index.ts +47 -0
- package/src/lib/AsyncAutocomplete.stories.tsx +21 -36
- package/src/lib/AsyncAutocomplete.test.tsx +17 -53
- package/src/lib/AsyncAutocomplete.tsx +23 -20
- package/src/lib/Autocomplete.stories.tsx +20 -33
- package/src/lib/Autocomplete.test.tsx +7 -37
- package/src/lib/Autocomplete.tsx +16 -15
- package/src/lib/Checkbox.stories.tsx +50 -43
- package/src/lib/Checkbox.test.tsx +14 -46
- package/src/lib/Checkbox.tsx +30 -15
- package/src/lib/CodesAutocomplete.stories.tsx +21 -35
- package/src/lib/CodesAutocomplete.test.tsx +20 -54
- package/src/lib/CodesAutocomplete.tsx +23 -20
- package/src/lib/ControlledForm.stories.tsx +1 -0
- package/src/lib/ControlledForm.tsx +8 -4
- package/src/lib/Datepicker.stories.tsx +19 -32
- package/src/lib/Datepicker.test.tsx +3 -35
- package/src/lib/Datepicker.tsx +18 -10
- package/src/lib/Input.stories.tsx +32 -33
- package/src/lib/Input.test.tsx +71 -7
- package/src/lib/Input.tsx +44 -24
- package/src/lib/OrganizationAutocomplete.stories.tsx +30 -35
- package/src/lib/OrganizationAutocomplete.test.tsx +23 -57
- package/src/lib/OrganizationAutocomplete.tsx +24 -23
- package/src/lib/ProviderAutocomplete.stories.tsx +20 -35
- package/src/lib/ProviderAutocomplete.test.tsx +29 -63
- package/src/lib/ProviderAutocomplete.tsx +22 -20
- package/src/lib/RadioGroup.stories.tsx +41 -36
- package/src/lib/RadioGroup.test.tsx +3 -35
- package/src/lib/RadioGroup.tsx +33 -25
- package/src/lib/Select.stories.tsx +78 -45
- package/src/lib/Select.test.tsx +8 -36
- package/src/lib/Select.tsx +44 -25
- package/src/lib/TextField.stories.tsx +26 -34
- package/src/lib/TextField.test.tsx +71 -5
- package/src/lib/TextField.tsx +55 -37
- package/src/lib/Types.tsx +2489 -0
- package/src/lib/UtilComponents.tsx +52 -0
- package/docs/propDefinitions.tsx +0 -31
|
@@ -1,74 +1,107 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { ControlledSelect } from './Select';
|
|
3
|
-
import { ControlledForm } from './ControlledForm';
|
|
4
3
|
import { Button } from '@availity/mui-button';
|
|
5
|
-
import { useFormContext } from 'react-hook-form';
|
|
6
4
|
import { Paper } from '@availity/mui-paper';
|
|
7
5
|
import { Typography } from '@availity/mui-typography';
|
|
8
6
|
import { Grid } from '@availity/mui-layout';
|
|
9
7
|
import { MenuItem } from '@availity/mui-menu';
|
|
10
8
|
import { FormControl, FormLabel } from '@availity/mui-form-utils';
|
|
9
|
+
import { AllControllerPropertiesCategorized, SelectPropsCategorized } from './Types';
|
|
10
|
+
import { FormProvider, useForm } from '..';
|
|
11
11
|
|
|
12
12
|
const meta: Meta<typeof ControlledSelect> = {
|
|
13
13
|
title: 'Form Components/Controlled Form/ControlledSelect',
|
|
14
14
|
component: ControlledSelect,
|
|
15
15
|
tags: ['autodocs'],
|
|
16
|
+
argTypes: {...AllControllerPropertiesCategorized, ...SelectPropsCategorized},
|
|
17
|
+
parameters: {
|
|
18
|
+
controls: {
|
|
19
|
+
exclude: [
|
|
20
|
+
'className',
|
|
21
|
+
'defaultChecked',
|
|
22
|
+
'onError',
|
|
23
|
+
'ref',
|
|
24
|
+
'style',
|
|
25
|
+
'tabIndex'
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
16
29
|
};
|
|
17
30
|
|
|
18
31
|
export default meta;
|
|
19
32
|
|
|
20
33
|
export const _ControlledSelect: StoryObj<typeof ControlledSelect> = {
|
|
21
34
|
render: (args) => {
|
|
22
|
-
const
|
|
23
|
-
const {
|
|
24
|
-
getValues,
|
|
25
|
-
formState: { isSubmitSuccessful },
|
|
26
|
-
} = useFormContext();
|
|
35
|
+
const methods = useForm();
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
return (
|
|
38
|
+
<FormProvider {...methods}>
|
|
39
|
+
<form onSubmit={methods.handleSubmit((data) => data)}>
|
|
40
|
+
<FormControl>
|
|
41
|
+
<FormLabel id={`${args.id}-label`}>{args.label}</FormLabel>
|
|
42
|
+
<ControlledSelect {...args} labelId={`${args.id}-label`}>
|
|
43
|
+
<MenuItem value={1}>Option 1</MenuItem>
|
|
44
|
+
<MenuItem value={2}>Option 2</MenuItem>
|
|
45
|
+
<MenuItem value={3}>Option 3</MenuItem>
|
|
46
|
+
</ControlledSelect>
|
|
47
|
+
</FormControl>
|
|
48
|
+
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
49
|
+
<Button disabled={!methods?.formState?.isSubmitSuccessful} children="Reset" color="secondary" onClick={() => methods.reset()} />
|
|
50
|
+
<Button type="submit" disabled={methods?.formState?.isSubmitSuccessful} children="Submit" />
|
|
51
|
+
</Grid>
|
|
52
|
+
{ methods?.formState?.isSubmitSuccessful ? (
|
|
53
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
54
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
55
|
+
<pre data-testid="result">{JSON.stringify(methods.getValues(), null, 2)}</pre>
|
|
56
|
+
</Paper>
|
|
57
|
+
) : null }
|
|
58
|
+
</form>
|
|
59
|
+
</FormProvider>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
args: {
|
|
63
|
+
name: 'controlledSelect',
|
|
64
|
+
required: true,
|
|
65
|
+
rules: { required:'This is required.' },
|
|
66
|
+
label: 'Select Label',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
formState: { isSubmitSuccessful },
|
|
40
|
-
} = useFormContext();
|
|
41
|
-
return (
|
|
42
|
-
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
43
|
-
<Button
|
|
44
|
-
disabled={!isSubmitSuccessful}
|
|
45
|
-
children="Reset"
|
|
46
|
-
color="secondary"
|
|
47
|
-
onClick={() => reset({ controlledSelect: undefined })}
|
|
48
|
-
/>
|
|
49
|
-
<Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
|
|
50
|
-
</Grid>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
70
|
+
export const _ControlledMultiSelect: StoryObj<typeof ControlledSelect> = {
|
|
71
|
+
render: (args) => {
|
|
72
|
+
const methods = useForm({values:{ [args.name]: [] }});
|
|
53
73
|
|
|
54
74
|
return (
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
<FormProvider {...methods}>
|
|
76
|
+
<form onSubmit={methods.handleSubmit((data) => data)}>
|
|
77
|
+
<FormControl>
|
|
78
|
+
<FormLabel id={`${args.id}-label`}>{args.label}</FormLabel>
|
|
79
|
+
<ControlledSelect {...args} labelId={`${args.id}-label`}>
|
|
80
|
+
<MenuItem value={1}>Option 1</MenuItem>
|
|
81
|
+
<MenuItem value={2}>Option 2</MenuItem>
|
|
82
|
+
<MenuItem value={3}>Option 3</MenuItem>
|
|
83
|
+
<MenuItem value={4}>Option 4</MenuItem>
|
|
84
|
+
<MenuItem value={5}>Option 5</MenuItem>
|
|
85
|
+
<MenuItem value={6}>Option 6</MenuItem>
|
|
86
|
+
</ControlledSelect>
|
|
87
|
+
</FormControl>
|
|
88
|
+
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
89
|
+
<Button disabled={!methods?.formState?.isSubmitSuccessful} children="Reset" color="secondary" onClick={() => methods.reset()} />
|
|
90
|
+
<Button type="submit" disabled={methods?.formState?.isSubmitSuccessful} children="Submit" />
|
|
91
|
+
</Grid>
|
|
92
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
93
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
94
|
+
<pre>{JSON.stringify(methods.getValues(), null, 2)}</pre>
|
|
95
|
+
</Paper>
|
|
96
|
+
</form>
|
|
97
|
+
</FormProvider>
|
|
67
98
|
);
|
|
68
99
|
},
|
|
69
100
|
args: {
|
|
70
|
-
name: '
|
|
71
|
-
required:
|
|
101
|
+
name: 'controlledMutliSelect',
|
|
102
|
+
required: true,
|
|
103
|
+
rules: { required:'This is required.' },
|
|
72
104
|
label: 'Select Label',
|
|
105
|
+
multiple: true,
|
|
73
106
|
},
|
|
74
107
|
};
|
package/src/lib/Select.test.tsx
CHANGED
|
@@ -1,52 +1,24 @@
|
|
|
1
1
|
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
-
import { useFormContext } from 'react-hook-form';
|
|
3
2
|
import { Paper } from '@availity/mui-paper';
|
|
4
3
|
import { Typography } from '@availity/mui-typography';
|
|
5
4
|
import { Grid } from '@availity/mui-layout';
|
|
6
5
|
import { Button } from '@availity/mui-button';
|
|
7
|
-
import { ControlledForm } from './ControlledForm';
|
|
8
6
|
import { ControlledSelect } from './Select';
|
|
9
7
|
import { MenuItem } from '@availity/mui-menu';
|
|
10
|
-
|
|
11
|
-
const SubmittedValues = () => {
|
|
12
|
-
const {
|
|
13
|
-
getValues,
|
|
14
|
-
formState: { isSubmitSuccessful },
|
|
15
|
-
} = useFormContext();
|
|
16
|
-
|
|
17
|
-
return isSubmitSuccessful ? (
|
|
18
|
-
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
19
|
-
<Typography variant="h2">Submitted Values</Typography>
|
|
20
|
-
<pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
|
|
21
|
-
</Paper>
|
|
22
|
-
) : null;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const Actions = () => {
|
|
26
|
-
const {
|
|
27
|
-
formState: { isSubmitSuccessful },
|
|
28
|
-
} = useFormContext();
|
|
29
|
-
return (
|
|
30
|
-
<Grid container direction="row" justifyContent="space-between">
|
|
31
|
-
<Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
|
|
32
|
-
</Grid>
|
|
33
|
-
);
|
|
34
|
-
};
|
|
8
|
+
import { TestForm } from './UtilComponents';
|
|
35
9
|
|
|
36
10
|
const onSubmit = jest.fn();
|
|
37
11
|
|
|
38
12
|
describe('ControlledSelect', () => {
|
|
39
13
|
test('should render the error styling if an error is returned', async () => {
|
|
40
14
|
const screen = render(
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<SubmittedValues />
|
|
49
|
-
</ControlledForm>
|
|
15
|
+
<TestForm UseFormOptions={{values: { controlledSelect: undefined }}} onSubmit={onSubmit}>
|
|
16
|
+
<ControlledSelect name="controlledSelect">
|
|
17
|
+
<MenuItem value={1}>Option 1</MenuItem>
|
|
18
|
+
<MenuItem value={2}>Option 2</MenuItem>
|
|
19
|
+
<MenuItem value={3}>Option 3</MenuItem>
|
|
20
|
+
</ControlledSelect>
|
|
21
|
+
</TestForm>
|
|
50
22
|
);
|
|
51
23
|
|
|
52
24
|
const dropdown = screen.getByRole('combobox');
|
package/src/lib/Select.tsx
CHANGED
|
@@ -1,52 +1,71 @@
|
|
|
1
1
|
import { Select, SelectProps } from '@availity/mui-form-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { RegisterOptions, FieldValues, Controller } from 'react-hook-form';
|
|
3
|
+
import { ControllerProps, DeprecatedRulesProps } from './Types';
|
|
3
4
|
|
|
4
|
-
export type ControlledSelectProps = Omit<SelectProps,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export type ControlledSelectProps = Omit<SelectProps,
|
|
6
|
+
'onBlur' | 'onChange' | 'value' | 'name' | 'required'
|
|
7
|
+
> & Pick<RegisterOptions<FieldValues, string>,
|
|
8
|
+
'onBlur' | 'onChange' | 'value'
|
|
9
|
+
> & ControllerProps
|
|
10
|
+
//TODO v1 - remove deprecated props
|
|
11
|
+
& Omit<DeprecatedRulesProps, 'required'> & {
|
|
12
|
+
/** If `true`, will add `aria-required` to `input`.
|
|
13
|
+
*
|
|
14
|
+
* @deprecated There has been a collision of properties. The boolean value
|
|
15
|
+
* to mark the input as required will remain in future versions, but the
|
|
16
|
+
* required object for `react-hook-form` has been moved to the `rules` prop.
|
|
17
|
+
*/
|
|
18
|
+
required?: boolean | RegisterOptions['required'];
|
|
19
|
+
};
|
|
8
20
|
|
|
9
21
|
export const ControlledSelect = ({
|
|
10
22
|
name,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
defaultValue,
|
|
24
|
+
deps,
|
|
25
|
+
disabled,
|
|
14
26
|
max,
|
|
27
|
+
maxLength,
|
|
15
28
|
min,
|
|
29
|
+
minLength,
|
|
30
|
+
onBlur,
|
|
31
|
+
onChange,
|
|
16
32
|
pattern,
|
|
33
|
+
required,
|
|
34
|
+
rules = {},
|
|
35
|
+
shouldUnregister,
|
|
17
36
|
validate,
|
|
18
|
-
setValueAs,
|
|
19
|
-
disabled,
|
|
20
|
-
onChange,
|
|
21
|
-
onBlur,
|
|
22
37
|
value,
|
|
23
|
-
shouldUnregister,
|
|
24
|
-
deps,
|
|
25
38
|
...rest
|
|
26
39
|
}: ControlledSelectProps) => {
|
|
27
|
-
const { register, getFieldState } = useFormContext();
|
|
28
|
-
|
|
29
40
|
return (
|
|
30
|
-
<
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
required,
|
|
41
|
+
<Controller
|
|
42
|
+
name={name}
|
|
43
|
+
defaultValue={defaultValue}
|
|
44
|
+
disabled={disabled}
|
|
45
|
+
rules={{
|
|
46
|
+
required: typeof required === 'boolean' ? undefined : required,
|
|
36
47
|
maxLength,
|
|
37
48
|
minLength,
|
|
38
49
|
max,
|
|
39
50
|
min,
|
|
40
51
|
pattern,
|
|
41
52
|
validate,
|
|
42
|
-
setValueAs,
|
|
43
|
-
disabled,
|
|
44
53
|
onChange,
|
|
45
54
|
onBlur,
|
|
46
55
|
value,
|
|
47
56
|
shouldUnregister,
|
|
48
57
|
deps,
|
|
49
|
-
|
|
58
|
+
...rules,
|
|
59
|
+
}}
|
|
60
|
+
shouldUnregister={shouldUnregister}
|
|
61
|
+
render={({ field, fieldState: { error } }) => (
|
|
62
|
+
<Select
|
|
63
|
+
{...rest}
|
|
64
|
+
{...field}
|
|
65
|
+
error={!!error}
|
|
66
|
+
required={!!required}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
50
69
|
/>
|
|
51
70
|
);
|
|
52
71
|
};
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { ControlledTextField, ControlledTextFieldProps } from './TextField';
|
|
3
|
-
import { ControlledForm } from './ControlledForm';
|
|
4
3
|
import { Button } from '@availity/mui-button';
|
|
5
|
-
import { useFormContext } from 'react-hook-form';
|
|
6
4
|
import { Paper } from '@availity/mui-paper';
|
|
7
5
|
import { Typography } from '@availity/mui-typography';
|
|
8
6
|
import { Grid } from '@availity/mui-layout';
|
|
7
|
+
import { AllControllerPropertiesCategorized, TextFieldPropsCategorized } from './Types';
|
|
8
|
+
import { FormProvider, useForm } from '..';
|
|
9
9
|
|
|
10
10
|
const meta: Meta<typeof ControlledTextField> = {
|
|
11
11
|
title: 'Form Components/Controlled Form/ControlledTextField',
|
|
12
12
|
component: ControlledTextField,
|
|
13
13
|
tags: ['autodocs'],
|
|
14
14
|
argTypes: {
|
|
15
|
+
...AllControllerPropertiesCategorized,
|
|
16
|
+
...TextFieldPropsCategorized,
|
|
15
17
|
helperText: {
|
|
16
18
|
type: 'string',
|
|
19
|
+
table: { category: 'Input Props' }
|
|
17
20
|
},
|
|
18
21
|
},
|
|
19
22
|
};
|
|
@@ -22,46 +25,35 @@ export default meta;
|
|
|
22
25
|
|
|
23
26
|
export const _ControlledTextField: StoryObj<typeof ControlledTextField> = {
|
|
24
27
|
render: (args: ControlledTextFieldProps) => {
|
|
25
|
-
const
|
|
26
|
-
const {
|
|
27
|
-
getValues,
|
|
28
|
-
formState: { isSubmitSuccessful },
|
|
29
|
-
} = useFormContext();
|
|
28
|
+
const methods = useForm({values:{ [args.name]: '' }});
|
|
30
29
|
|
|
31
|
-
return isSubmitSuccessful ? (
|
|
32
|
-
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
33
|
-
<Typography variant="h2">Submitted Values</Typography>
|
|
34
|
-
<pre>{JSON.stringify(getValues(), null, 2)}</pre>
|
|
35
|
-
</Paper>
|
|
36
|
-
) : null;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const Actions = () => {
|
|
40
|
-
const {
|
|
41
|
-
reset,
|
|
42
|
-
formState: { isSubmitSuccessful },
|
|
43
|
-
} = useFormContext();
|
|
44
|
-
return (
|
|
45
|
-
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
46
|
-
<Button disabled={!isSubmitSuccessful} children="Reset" color="secondary" onClick={() => reset()} />
|
|
47
|
-
<Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
|
|
48
|
-
</Grid>
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
30
|
return (
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
<FormProvider {...methods}>
|
|
32
|
+
<form onSubmit={methods.handleSubmit((data) => data)}>
|
|
33
|
+
<ControlledTextField {...args} />
|
|
34
|
+
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
35
|
+
<Button disabled={!methods?.formState?.isSubmitSuccessful} children="Reset" color="secondary" onClick={() => methods.reset()} />
|
|
36
|
+
<Button type="submit" disabled={methods?.formState?.isSubmitSuccessful} children="Submit" />
|
|
37
|
+
</Grid>
|
|
38
|
+
{ methods?.formState?.isSubmitSuccessful ? (
|
|
39
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
40
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
41
|
+
<pre data-testid="result">{JSON.stringify(methods.getValues(), null, 2)}</pre>
|
|
42
|
+
</Paper>
|
|
43
|
+
) : null }
|
|
44
|
+
</form>
|
|
45
|
+
</FormProvider>
|
|
57
46
|
);
|
|
58
47
|
},
|
|
59
48
|
args: {
|
|
60
49
|
name: 'controlledTextField',
|
|
61
50
|
helperText: 'This is some helper text',
|
|
62
51
|
placeholder: 'Name',
|
|
63
|
-
required:
|
|
64
|
-
|
|
52
|
+
required: true,
|
|
53
|
+
rules: {
|
|
54
|
+
required: 'This field is required.',
|
|
55
|
+
maxLength: { value: 10, message: 'Too long' }
|
|
56
|
+
},
|
|
65
57
|
label: 'TextField Label',
|
|
66
58
|
},
|
|
67
59
|
};
|
|
@@ -6,6 +6,7 @@ import { Grid } from '@availity/mui-layout';
|
|
|
6
6
|
import { Button } from '@availity/mui-button';
|
|
7
7
|
import { ControlledForm } from './ControlledForm';
|
|
8
8
|
import { ControlledTextField } from './TextField';
|
|
9
|
+
import { TestForm } from './UtilComponents';
|
|
9
10
|
|
|
10
11
|
const SubmittedValues = () => {
|
|
11
12
|
const {
|
|
@@ -33,11 +34,12 @@ const Actions = () => {
|
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const onSubmit = jest.fn();
|
|
37
|
+
const onSubmitDeprecated = jest.fn();
|
|
36
38
|
|
|
37
39
|
describe('ControlledTextField', () => {
|
|
38
|
-
test('should render the error styling if an error is returned', async () => {
|
|
40
|
+
test('Deprecated Check: should render the error styling if an error is returned', async () => {
|
|
39
41
|
const screen = render(
|
|
40
|
-
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={
|
|
42
|
+
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={onSubmitDeprecated}>
|
|
41
43
|
<ControlledTextField
|
|
42
44
|
name="controlledTextField"
|
|
43
45
|
helperText="This is some helper text"
|
|
@@ -59,14 +61,14 @@ describe('ControlledTextField', () => {
|
|
|
59
61
|
|
|
60
62
|
fireEvent.click(screen.getByText('Submit'));
|
|
61
63
|
|
|
62
|
-
await waitFor(() => expect(
|
|
64
|
+
await waitFor(() => expect(onSubmitDeprecated).toHaveBeenCalledTimes(0));
|
|
63
65
|
|
|
64
66
|
await waitFor(() => expect(screen.findByText('Too long')).toBeDefined());
|
|
65
67
|
});
|
|
66
68
|
|
|
67
|
-
test('should render the error styling if an error is returned', async () => {
|
|
69
|
+
test('Deprecated Check: should render the error styling if an error is returned', async () => {
|
|
68
70
|
const screen = render(
|
|
69
|
-
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={
|
|
71
|
+
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={onSubmitDeprecated}>
|
|
70
72
|
<ControlledTextField
|
|
71
73
|
name="controlledTextField"
|
|
72
74
|
helperText="This is some helper text"
|
|
@@ -88,6 +90,70 @@ describe('ControlledTextField', () => {
|
|
|
88
90
|
|
|
89
91
|
fireEvent.click(screen.getByText('Submit'));
|
|
90
92
|
|
|
93
|
+
await waitFor(() => expect(onSubmitDeprecated).toHaveBeenCalledTimes(1));
|
|
94
|
+
|
|
95
|
+
const result = screen.getByTestId('result');
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
const formValues = JSON.parse(result.innerHTML).controlledTextField;
|
|
98
|
+
expect(formValues).toBe('Some Text');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should render the error styling if an error is returned', async () => {
|
|
103
|
+
const screen = render(
|
|
104
|
+
<TestForm UseFormOptions={{values: { controlledTextField: undefined }}} onSubmit={onSubmit}>
|
|
105
|
+
<ControlledTextField
|
|
106
|
+
name="controlledTextField"
|
|
107
|
+
helperText="This is some helper text"
|
|
108
|
+
placeholder="Name"
|
|
109
|
+
rules= {{
|
|
110
|
+
required:"This field is required.",
|
|
111
|
+
maxLength:{ value: 10, message: 'Too long' }
|
|
112
|
+
|
|
113
|
+
}}
|
|
114
|
+
inputProps={{
|
|
115
|
+
'data-testid': 'testTextField',
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</TestForm>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const input = screen.getByTestId('testTextField');
|
|
122
|
+
|
|
123
|
+
fireEvent.change(input, { target: { value: 'This is way too much text' } });
|
|
124
|
+
|
|
125
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
126
|
+
|
|
127
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(0));
|
|
128
|
+
|
|
129
|
+
await waitFor(() => expect(screen.findByText('Too long')).toBeDefined());
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should not render the error styling if no error is returned', async () => {
|
|
133
|
+
const screen = render(
|
|
134
|
+
<TestForm UseFormOptions={{values: { controlledTextField: undefined }}} onSubmit={onSubmit}>
|
|
135
|
+
<ControlledTextField
|
|
136
|
+
name="controlledTextField"
|
|
137
|
+
helperText="This is some helper text"
|
|
138
|
+
placeholder="Name"
|
|
139
|
+
rules= {{
|
|
140
|
+
required:"This field is required.",
|
|
141
|
+
maxLength:{ value: 10, message: 'Too long' }
|
|
142
|
+
|
|
143
|
+
}}
|
|
144
|
+
inputProps={{
|
|
145
|
+
'data-testid': 'testTextField',
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
</TestForm>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const input = screen.getByTestId('testTextField');
|
|
152
|
+
|
|
153
|
+
fireEvent.change(input, { target: { value: 'Some Text' } });
|
|
154
|
+
|
|
155
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
156
|
+
|
|
91
157
|
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
|
92
158
|
|
|
93
159
|
const result = screen.getByTestId('result');
|
package/src/lib/TextField.tsx
CHANGED
|
@@ -1,65 +1,83 @@
|
|
|
1
1
|
import { TextField, TextFieldProps } from '@availity/mui-textfield';
|
|
2
|
-
import {
|
|
2
|
+
import { RegisterOptions, FieldValues, Controller } from 'react-hook-form';
|
|
3
|
+
import { ControllerProps, DeprecatedRulesProps } from './Types';
|
|
3
4
|
|
|
4
|
-
export type ControlledTextFieldProps = Omit<TextFieldProps,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export type ControlledTextFieldProps = Omit<TextFieldProps,
|
|
6
|
+
'onBlur' | 'onChange' | 'value' | 'name' | 'required'
|
|
7
|
+
> & Pick<RegisterOptions<FieldValues, string>,
|
|
8
|
+
'onBlur' | 'onChange' | 'value'
|
|
9
|
+
> & ControllerProps
|
|
10
|
+
//TODO v1 - remove deprecated props
|
|
11
|
+
& Omit<DeprecatedRulesProps, 'required'> & {
|
|
12
|
+
/** If `true`, will add required asterisk to `label` and `aria-required` to `input`.
|
|
13
|
+
*
|
|
14
|
+
* @deprecated There has been a collision of properties. The boolean value
|
|
15
|
+
* to mark the input as required will remain in future versions, but the
|
|
16
|
+
* required object for `react-hook-form` has been moved to the `rules` prop.
|
|
17
|
+
*/
|
|
18
|
+
required?: boolean | RegisterOptions['required'];
|
|
19
|
+
};
|
|
7
20
|
|
|
8
21
|
export const ControlledTextField = ({
|
|
9
22
|
name,
|
|
23
|
+
defaultValue,
|
|
24
|
+
deps,
|
|
25
|
+
disabled,
|
|
10
26
|
helperText,
|
|
11
|
-
required,
|
|
12
|
-
maxLength,
|
|
13
|
-
minLength,
|
|
14
27
|
max,
|
|
28
|
+
maxLength,
|
|
15
29
|
min,
|
|
30
|
+
minLength,
|
|
31
|
+
onBlur,
|
|
32
|
+
onChange,
|
|
16
33
|
pattern,
|
|
34
|
+
required,
|
|
35
|
+
rules = {},
|
|
36
|
+
shouldUnregister,
|
|
17
37
|
validate,
|
|
18
|
-
setValueAs,
|
|
19
|
-
disabled,
|
|
20
|
-
onChange,
|
|
21
|
-
onBlur,
|
|
22
38
|
value,
|
|
23
|
-
shouldUnregister,
|
|
24
|
-
deps,
|
|
25
39
|
...rest
|
|
26
40
|
}: ControlledTextFieldProps) => {
|
|
27
|
-
const { register, getFieldState } = useFormContext();
|
|
28
|
-
|
|
29
|
-
const errorMessage = getFieldState(name).error?.message;
|
|
30
|
-
|
|
31
41
|
return (
|
|
32
|
-
<
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
|
|
42
|
+
<Controller
|
|
43
|
+
name={name}
|
|
44
|
+
defaultValue={defaultValue}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
rules={{
|
|
47
|
+
required: typeof required === 'boolean' ? undefined : required,
|
|
37
48
|
maxLength,
|
|
38
49
|
minLength,
|
|
39
50
|
max,
|
|
40
51
|
min,
|
|
41
52
|
pattern,
|
|
42
53
|
validate,
|
|
43
|
-
setValueAs,
|
|
44
|
-
disabled,
|
|
45
54
|
onChange,
|
|
46
55
|
onBlur,
|
|
47
56
|
value,
|
|
48
57
|
shouldUnregister,
|
|
49
58
|
deps,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
...rules,
|
|
60
|
+
}}
|
|
61
|
+
shouldUnregister={shouldUnregister}
|
|
62
|
+
render={({ field : {ref, ...field}, fieldState: { error } }) => (
|
|
63
|
+
<TextField
|
|
64
|
+
{...field}
|
|
65
|
+
{...rest}
|
|
66
|
+
inputRef={ref}
|
|
67
|
+
error={!!error}
|
|
68
|
+
helperText={
|
|
69
|
+
error?.message ? (
|
|
70
|
+
<>
|
|
71
|
+
{error.message}
|
|
72
|
+
<br />
|
|
73
|
+
{helperText}
|
|
74
|
+
</>
|
|
75
|
+
) : (
|
|
76
|
+
helperText
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
63
81
|
/>
|
|
64
82
|
);
|
|
65
83
|
};
|