@availity/mui-controlled-form 0.1.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 +26 -0
- package/README.md +65 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +71 -0
- package/dist/index.mjs +47 -0
- package/docs/propDefinitions.tsx +31 -0
- package/introduction.stories.mdx +7 -0
- package/jest.config.js +7 -0
- package/package.json +66 -0
- package/project.json +41 -0
- package/src/index.ts +1 -0
- package/src/lib/AsyncAutocomplete.stories.tsx +113 -0
- package/src/lib/AsyncAutocomplete.test.tsx +162 -0
- package/src/lib/AsyncAutocomplete.tsx +92 -0
- package/src/lib/Autocomplete.stories.tsx +60 -0
- package/src/lib/Autocomplete.test.tsx +70 -0
- package/src/lib/Autocomplete.tsx +96 -0
- package/src/lib/Checkbox.stories.tsx +67 -0
- package/src/lib/Checkbox.test.tsx +73 -0
- package/src/lib/Checkbox.tsx +37 -0
- package/src/lib/CodesAutocomplete.stories.tsx +79 -0
- package/src/lib/CodesAutocomplete.test.tsx +128 -0
- package/src/lib/CodesAutocomplete.tsx +76 -0
- package/src/lib/ControlledForm.stories.tsx +74 -0
- package/src/lib/ControlledForm.test.tsx +77 -0
- package/src/lib/ControlledForm.tsx +35 -0
- package/src/lib/Datepicker.stories.tsx +63 -0
- package/src/lib/Datepicker.test.tsx +73 -0
- package/src/lib/Datepicker.tsx +49 -0
- package/src/lib/Input.stories.tsx +60 -0
- package/src/lib/Input.test.tsx +98 -0
- package/src/lib/Input.tsx +54 -0
- package/src/lib/OrganizationAutocomplete.stories.tsx +77 -0
- package/src/lib/OrganizationAutocomplete.test.tsx +125 -0
- package/src/lib/OrganizationAutocomplete.tsx +75 -0
- package/src/lib/ProviderAutocomplete.stories.tsx +79 -0
- package/src/lib/ProviderAutocomplete.test.tsx +128 -0
- package/src/lib/ProviderAutocomplete.tsx +80 -0
- package/src/lib/RadioGroup.stories.tsx +63 -0
- package/src/lib/RadioGroup.test.tsx +66 -0
- package/src/lib/RadioGroup.tsx +68 -0
- package/src/lib/Select.stories.tsx +74 -0
- package/src/lib/Select.test.tsx +68 -0
- package/src/lib/Select.tsx +55 -0
- package/src/lib/TextField.stories.tsx +67 -0
- package/src/lib/TextField.test.tsx +99 -0
- package/src/lib/TextField.tsx +67 -0
- package/tsconfig.json +5 -0
- package/tsconfig.spec.json +10 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ProviderAutocomplete, ProviderAutocompleteProps } from '@availity/mui-autocomplete';
|
|
2
|
+
import { useFormContext, Controller, RegisterOptions, FieldValues, ControllerProps } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
type ControlledProviderAutocompleteProps = Omit<ProviderAutocompleteProps, 'name'> &
|
|
5
|
+
Omit<RegisterOptions<FieldValues, string>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'> &
|
|
6
|
+
Pick<ControllerProps, 'defaultValue' | 'shouldUnregister' | 'name'>;
|
|
7
|
+
|
|
8
|
+
export const ControlledProviderAutocomplete = ({
|
|
9
|
+
name,
|
|
10
|
+
defaultValue,
|
|
11
|
+
deps,
|
|
12
|
+
max,
|
|
13
|
+
maxLength,
|
|
14
|
+
min,
|
|
15
|
+
minLength,
|
|
16
|
+
onBlur,
|
|
17
|
+
onChange,
|
|
18
|
+
pattern,
|
|
19
|
+
required,
|
|
20
|
+
shouldUnregister,
|
|
21
|
+
validate,
|
|
22
|
+
value,
|
|
23
|
+
FieldProps,
|
|
24
|
+
...rest
|
|
25
|
+
}: ControlledProviderAutocompleteProps) => {
|
|
26
|
+
const {
|
|
27
|
+
control,
|
|
28
|
+
formState: { errors },
|
|
29
|
+
} = useFormContext();
|
|
30
|
+
const errorMessage = errors[name]?.message;
|
|
31
|
+
return (
|
|
32
|
+
<Controller
|
|
33
|
+
name={name}
|
|
34
|
+
control={control}
|
|
35
|
+
defaultValue={defaultValue}
|
|
36
|
+
rules={{
|
|
37
|
+
deps,
|
|
38
|
+
max,
|
|
39
|
+
maxLength,
|
|
40
|
+
min,
|
|
41
|
+
minLength,
|
|
42
|
+
onBlur,
|
|
43
|
+
onChange,
|
|
44
|
+
pattern,
|
|
45
|
+
required,
|
|
46
|
+
shouldUnregister,
|
|
47
|
+
validate,
|
|
48
|
+
value,
|
|
49
|
+
}}
|
|
50
|
+
shouldUnregister={shouldUnregister}
|
|
51
|
+
render={({ field: { onChange, value, onBlur } }) => (
|
|
52
|
+
<ProviderAutocomplete
|
|
53
|
+
{...rest}
|
|
54
|
+
FieldProps={{
|
|
55
|
+
...FieldProps,
|
|
56
|
+
error: !!errorMessage,
|
|
57
|
+
helperText:
|
|
58
|
+
errorMessage && typeof errorMessage === 'string' ? (
|
|
59
|
+
<>
|
|
60
|
+
{errorMessage}
|
|
61
|
+
<br />
|
|
62
|
+
{FieldProps?.helperText}
|
|
63
|
+
</>
|
|
64
|
+
) : (
|
|
65
|
+
FieldProps?.helperText
|
|
66
|
+
),
|
|
67
|
+
}}
|
|
68
|
+
onChange={(event, value, reason) => {
|
|
69
|
+
if (reason === 'clear') {
|
|
70
|
+
onChange(null);
|
|
71
|
+
}
|
|
72
|
+
onChange(value);
|
|
73
|
+
}}
|
|
74
|
+
onBlur={onBlur}
|
|
75
|
+
value={value || null}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ControlledRadioGroup, ControlledRadioGroupProps } from './RadioGroup';
|
|
3
|
+
import { ControlledForm } from './ControlledForm';
|
|
4
|
+
import { Button } from '@availity/mui-button';
|
|
5
|
+
import { useFormContext } from 'react-hook-form';
|
|
6
|
+
import { Paper } from '@availity/mui-paper';
|
|
7
|
+
import { Typography } from '@availity/mui-typography';
|
|
8
|
+
import { FormControlLabel, Radio } from '@availity/mui-form-utils';
|
|
9
|
+
import { Grid } from '@availity/mui-layout';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof ControlledRadioGroup> = {
|
|
12
|
+
title: 'Form Components/Controlled Form/ControlledRadioGroup',
|
|
13
|
+
component: ControlledRadioGroup,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
|
|
19
|
+
export const _ControlledRadioGroup: StoryObj<typeof ControlledRadioGroup> = {
|
|
20
|
+
render: (args: ControlledRadioGroupProps) => {
|
|
21
|
+
const SubmittedValues = () => {
|
|
22
|
+
const {
|
|
23
|
+
getValues,
|
|
24
|
+
formState: { isSubmitSuccessful },
|
|
25
|
+
} = useFormContext();
|
|
26
|
+
|
|
27
|
+
return isSubmitSuccessful ? (
|
|
28
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
29
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
30
|
+
<pre>{JSON.stringify(getValues(), null, 2)}</pre>
|
|
31
|
+
</Paper>
|
|
32
|
+
) : null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const Actions = () => {
|
|
36
|
+
const {
|
|
37
|
+
reset,
|
|
38
|
+
formState: { isSubmitSuccessful },
|
|
39
|
+
} = useFormContext();
|
|
40
|
+
return (
|
|
41
|
+
<Grid container direction="row" justifyContent="space-between" marginTop={1}>
|
|
42
|
+
<Button disabled={!isSubmitSuccessful} children="Reset" color="secondary" onClick={() => reset()} />
|
|
43
|
+
<Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
|
|
44
|
+
</Grid>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
return (
|
|
48
|
+
<ControlledForm onSubmit={(data) => data} values={{ controlledRadioGroup: 'N/A' }}>
|
|
49
|
+
<ControlledRadioGroup {...args}>
|
|
50
|
+
<FormControlLabel control={<Radio />} label="N/A" value="N/A" />
|
|
51
|
+
<FormControlLabel control={<Radio />} label="Yes" value="Yes" />
|
|
52
|
+
<FormControlLabel control={<Radio />} label="No" value="No" />
|
|
53
|
+
</ControlledRadioGroup>
|
|
54
|
+
<Actions />
|
|
55
|
+
<SubmittedValues />
|
|
56
|
+
</ControlledForm>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
args: {
|
|
60
|
+
name: 'controlledRadioGroup',
|
|
61
|
+
label: 'Radio Group',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { FormControlLabel, Radio } from '@availity/mui-form-utils';
|
|
3
|
+
import { useFormContext } from 'react-hook-form';
|
|
4
|
+
import { Paper } from '@availity/mui-paper';
|
|
5
|
+
import { Typography } from '@availity/mui-typography';
|
|
6
|
+
import { Grid } from '@availity/mui-layout';
|
|
7
|
+
import { Button } from '@availity/mui-button';
|
|
8
|
+
import { ControlledForm } from './ControlledForm';
|
|
9
|
+
import { ControlledRadioGroup } from './RadioGroup';
|
|
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
|
+
};
|
|
35
|
+
|
|
36
|
+
const onSubmit = jest.fn();
|
|
37
|
+
|
|
38
|
+
describe('ControlledRadioGroup', () => {
|
|
39
|
+
test('should set the value and submit the form data', async () => {
|
|
40
|
+
const screen = render(
|
|
41
|
+
<ControlledForm onSubmit={onSubmit} values={{ controlledRadioGroup: undefined }}>
|
|
42
|
+
<ControlledRadioGroup name="controlledRadioGroup" label="Radio Group" value="N/A">
|
|
43
|
+
<FormControlLabel control={<Radio />} label="N/A" value="N/A" />
|
|
44
|
+
<FormControlLabel control={<Radio />} label="Yes" value="Yes" />
|
|
45
|
+
<FormControlLabel control={<Radio />} label="No" value="No" />
|
|
46
|
+
</ControlledRadioGroup>
|
|
47
|
+
<Actions />
|
|
48
|
+
<SubmittedValues />
|
|
49
|
+
</ControlledForm>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const option1 = screen.getByDisplayValue('Yes');
|
|
53
|
+
|
|
54
|
+
fireEvent.click(option1);
|
|
55
|
+
|
|
56
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
57
|
+
|
|
58
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
|
59
|
+
|
|
60
|
+
const result = screen.getByTestId('result');
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
const formValues = JSON.parse(result.innerHTML).controlledRadioGroup;
|
|
63
|
+
expect(formValues).toBe('Yes');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { RadioGroup, RadioGroupProps } from '@availity/mui-form-utils';
|
|
2
|
+
import { useFormContext, ControllerProps, Controller, RegisterOptions, FieldValues } from 'react-hook-form';
|
|
3
|
+
import { FormControl, FormLabel, FormHelperText } from '@availity/mui-form-utils';
|
|
4
|
+
|
|
5
|
+
export type ControlledRadioGroupProps = RadioGroupProps & {
|
|
6
|
+
name: string;
|
|
7
|
+
label: string;
|
|
8
|
+
helperText?: string;
|
|
9
|
+
} & Omit<
|
|
10
|
+
RegisterOptions<FieldValues, string>,
|
|
11
|
+
| 'disabled'
|
|
12
|
+
| 'valueAsNumber'
|
|
13
|
+
| 'valueAsDate'
|
|
14
|
+
| 'setValueAs'
|
|
15
|
+
| 'max'
|
|
16
|
+
| 'maxLength'
|
|
17
|
+
| 'min'
|
|
18
|
+
| 'minLength'
|
|
19
|
+
| 'pattern'
|
|
20
|
+
| 'validate'
|
|
21
|
+
> &
|
|
22
|
+
Pick<ControllerProps, 'defaultValue' | 'shouldUnregister' | 'name'>;
|
|
23
|
+
|
|
24
|
+
export const ControlledRadioGroup = ({
|
|
25
|
+
name,
|
|
26
|
+
helperText,
|
|
27
|
+
label,
|
|
28
|
+
defaultValue,
|
|
29
|
+
deps,
|
|
30
|
+
onBlur,
|
|
31
|
+
onChange,
|
|
32
|
+
required,
|
|
33
|
+
shouldUnregister,
|
|
34
|
+
value,
|
|
35
|
+
...rest
|
|
36
|
+
}: ControlledRadioGroupProps) => {
|
|
37
|
+
const {
|
|
38
|
+
control,
|
|
39
|
+
formState: { errors },
|
|
40
|
+
} = useFormContext();
|
|
41
|
+
const errorMessage = errors[name]?.message;
|
|
42
|
+
return (
|
|
43
|
+
<Controller
|
|
44
|
+
control={control}
|
|
45
|
+
name={name}
|
|
46
|
+
defaultValue={defaultValue}
|
|
47
|
+
rules={{ deps, onBlur, onChange, required, shouldUnregister, value }}
|
|
48
|
+
shouldUnregister={shouldUnregister}
|
|
49
|
+
render={({ field }) => (
|
|
50
|
+
<FormControl error={!!errorMessage}>
|
|
51
|
+
<FormLabel>{label}</FormLabel>
|
|
52
|
+
<RadioGroup {...field} {...rest} />
|
|
53
|
+
<FormHelperText>
|
|
54
|
+
{errorMessage && typeof errorMessage === 'string' ? (
|
|
55
|
+
<>
|
|
56
|
+
{errorMessage}
|
|
57
|
+
<br />
|
|
58
|
+
{helperText}
|
|
59
|
+
</>
|
|
60
|
+
) : (
|
|
61
|
+
helperText
|
|
62
|
+
)}
|
|
63
|
+
</FormHelperText>
|
|
64
|
+
</FormControl>
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ControlledSelect } from './Select';
|
|
3
|
+
import { ControlledForm } from './ControlledForm';
|
|
4
|
+
import { Button } from '@availity/mui-button';
|
|
5
|
+
import { useFormContext } from 'react-hook-form';
|
|
6
|
+
import { Paper } from '@availity/mui-paper';
|
|
7
|
+
import { Typography } from '@availity/mui-typography';
|
|
8
|
+
import { Grid } from '@availity/mui-layout';
|
|
9
|
+
import { MenuItem } from '@availity/mui-menu';
|
|
10
|
+
import { FormControl, FormLabel } from '@availity/mui-form-utils';
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof ControlledSelect> = {
|
|
13
|
+
title: 'Form Components/Controlled Form/ControlledSelect',
|
|
14
|
+
component: ControlledSelect,
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
|
|
20
|
+
export const _ControlledSelect: StoryObj<typeof ControlledSelect> = {
|
|
21
|
+
render: (args) => {
|
|
22
|
+
const SubmittedValues = () => {
|
|
23
|
+
const {
|
|
24
|
+
getValues,
|
|
25
|
+
formState: { isSubmitSuccessful },
|
|
26
|
+
} = useFormContext();
|
|
27
|
+
|
|
28
|
+
return isSubmitSuccessful ? (
|
|
29
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
30
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
31
|
+
<pre>{JSON.stringify(getValues(), null, 2)}</pre>
|
|
32
|
+
</Paper>
|
|
33
|
+
) : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const Actions = () => {
|
|
37
|
+
const {
|
|
38
|
+
reset,
|
|
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
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ControlledForm values={{ controlledSelect: undefined }} onSubmit={(data) => data}>
|
|
56
|
+
<FormControl>
|
|
57
|
+
<FormLabel id={`${args.id}-label`}>{args.label}</FormLabel>
|
|
58
|
+
<ControlledSelect {...args} labelId={`${args.id}-label`}>
|
|
59
|
+
<MenuItem value={1}>Option 1</MenuItem>
|
|
60
|
+
<MenuItem value={2}>Option 2</MenuItem>
|
|
61
|
+
<MenuItem value={3}>Option 3</MenuItem>
|
|
62
|
+
</ControlledSelect>
|
|
63
|
+
<Actions />
|
|
64
|
+
<SubmittedValues />
|
|
65
|
+
</FormControl>
|
|
66
|
+
</ControlledForm>
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
args: {
|
|
70
|
+
name: 'controlledSelect',
|
|
71
|
+
required: 'This is required.',
|
|
72
|
+
label: 'Select Label',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useFormContext } from 'react-hook-form';
|
|
3
|
+
import { Paper } from '@availity/mui-paper';
|
|
4
|
+
import { Typography } from '@availity/mui-typography';
|
|
5
|
+
import { Grid } from '@availity/mui-layout';
|
|
6
|
+
import { Button } from '@availity/mui-button';
|
|
7
|
+
import { ControlledForm } from './ControlledForm';
|
|
8
|
+
import { ControlledSelect } from './Select';
|
|
9
|
+
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
|
+
};
|
|
35
|
+
|
|
36
|
+
const onSubmit = jest.fn();
|
|
37
|
+
|
|
38
|
+
describe('ControlledSelect', () => {
|
|
39
|
+
test('should render the error styling if an error is returned', async () => {
|
|
40
|
+
const screen = render(
|
|
41
|
+
<ControlledForm values={{ controlledSelect: undefined }} onSubmit={onSubmit}>
|
|
42
|
+
<ControlledSelect name="controlledSelect">
|
|
43
|
+
<MenuItem value={1}>Option 1</MenuItem>
|
|
44
|
+
<MenuItem value={2}>Option 2</MenuItem>
|
|
45
|
+
<MenuItem value={3}>Option 3</MenuItem>
|
|
46
|
+
</ControlledSelect>
|
|
47
|
+
<Actions />
|
|
48
|
+
<SubmittedValues />
|
|
49
|
+
</ControlledForm>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const dropdown = screen.getByRole('combobox');
|
|
53
|
+
fireEvent.click(dropdown);
|
|
54
|
+
fireEvent.keyDown(dropdown, { key: 'ArrowDown' });
|
|
55
|
+
|
|
56
|
+
fireEvent.click(screen.getByText('Option 1'));
|
|
57
|
+
|
|
58
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
59
|
+
|
|
60
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
|
61
|
+
|
|
62
|
+
const result = screen.getByTestId('result');
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
const controlledSelectValue = JSON.parse(result.innerHTML).controlledSelect;
|
|
65
|
+
expect(controlledSelectValue).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Select, SelectProps } from '@availity/mui-form-utils';
|
|
2
|
+
import { useFormContext, RegisterOptions, FieldValues } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
type ControlledSelectProps = Omit<SelectProps, 'error' | 'required'> & { name: string } & RegisterOptions<
|
|
5
|
+
FieldValues,
|
|
6
|
+
string
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
export const ControlledSelect = ({
|
|
10
|
+
name,
|
|
11
|
+
required,
|
|
12
|
+
maxLength,
|
|
13
|
+
minLength,
|
|
14
|
+
max,
|
|
15
|
+
min,
|
|
16
|
+
pattern,
|
|
17
|
+
validate,
|
|
18
|
+
setValueAs,
|
|
19
|
+
disabled,
|
|
20
|
+
onChange,
|
|
21
|
+
onBlur,
|
|
22
|
+
value,
|
|
23
|
+
shouldUnregister,
|
|
24
|
+
deps,
|
|
25
|
+
...rest
|
|
26
|
+
}: ControlledSelectProps) => {
|
|
27
|
+
const {
|
|
28
|
+
register,
|
|
29
|
+
formState: { errors },
|
|
30
|
+
} = useFormContext();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Select
|
|
34
|
+
{...rest}
|
|
35
|
+
error={!!errors[name]}
|
|
36
|
+
required={!!required}
|
|
37
|
+
{...register(name, {
|
|
38
|
+
required,
|
|
39
|
+
maxLength,
|
|
40
|
+
minLength,
|
|
41
|
+
max,
|
|
42
|
+
min,
|
|
43
|
+
pattern,
|
|
44
|
+
validate,
|
|
45
|
+
setValueAs,
|
|
46
|
+
disabled,
|
|
47
|
+
onChange,
|
|
48
|
+
onBlur,
|
|
49
|
+
value,
|
|
50
|
+
shouldUnregister,
|
|
51
|
+
deps,
|
|
52
|
+
})}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ControlledTextField, ControlledTextFieldProps } from './TextField';
|
|
3
|
+
import { ControlledForm } from './ControlledForm';
|
|
4
|
+
import { Button } from '@availity/mui-button';
|
|
5
|
+
import { useFormContext } from 'react-hook-form';
|
|
6
|
+
import { Paper } from '@availity/mui-paper';
|
|
7
|
+
import { Typography } from '@availity/mui-typography';
|
|
8
|
+
import { Grid } from '@availity/mui-layout';
|
|
9
|
+
|
|
10
|
+
const meta: Meta<typeof ControlledTextField> = {
|
|
11
|
+
title: 'Form Components/Controlled Form/ControlledTextField',
|
|
12
|
+
component: ControlledTextField,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
helperText: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
export const _ControlledTextField: StoryObj<typeof ControlledTextField> = {
|
|
24
|
+
render: (args: ControlledTextFieldProps) => {
|
|
25
|
+
const SubmittedValues = () => {
|
|
26
|
+
const {
|
|
27
|
+
getValues,
|
|
28
|
+
formState: { isSubmitSuccessful },
|
|
29
|
+
} = useFormContext();
|
|
30
|
+
|
|
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
|
+
return (
|
|
52
|
+
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={(data) => data}>
|
|
53
|
+
<ControlledTextField {...args} />
|
|
54
|
+
<Actions />
|
|
55
|
+
<SubmittedValues />
|
|
56
|
+
</ControlledForm>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
args: {
|
|
60
|
+
name: 'controlledTextField',
|
|
61
|
+
helperText: 'This is some helper text',
|
|
62
|
+
placeholder: 'Name',
|
|
63
|
+
required: 'This field is required.',
|
|
64
|
+
maxLength: { value: 10, message: 'Too long' },
|
|
65
|
+
label: 'TextField Label',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useFormContext } from 'react-hook-form';
|
|
3
|
+
import { Paper } from '@availity/mui-paper';
|
|
4
|
+
import { Typography } from '@availity/mui-typography';
|
|
5
|
+
import { Grid } from '@availity/mui-layout';
|
|
6
|
+
import { Button } from '@availity/mui-button';
|
|
7
|
+
import { ControlledForm } from './ControlledForm';
|
|
8
|
+
import { ControlledTextField } from './TextField';
|
|
9
|
+
|
|
10
|
+
const SubmittedValues = () => {
|
|
11
|
+
const {
|
|
12
|
+
getValues,
|
|
13
|
+
formState: { isSubmitSuccessful },
|
|
14
|
+
} = useFormContext();
|
|
15
|
+
|
|
16
|
+
return isSubmitSuccessful ? (
|
|
17
|
+
<Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
|
|
18
|
+
<Typography variant="h2">Submitted Values</Typography>
|
|
19
|
+
<pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
|
|
20
|
+
</Paper>
|
|
21
|
+
) : null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const Actions = () => {
|
|
25
|
+
const {
|
|
26
|
+
formState: { isSubmitSuccessful },
|
|
27
|
+
} = useFormContext();
|
|
28
|
+
return (
|
|
29
|
+
<Grid container direction="row" justifyContent="space-between">
|
|
30
|
+
<Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
|
|
31
|
+
</Grid>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onSubmit = jest.fn();
|
|
36
|
+
|
|
37
|
+
describe('ControlledTextField', () => {
|
|
38
|
+
test('should render the error styling if an error is returned', async () => {
|
|
39
|
+
const screen = render(
|
|
40
|
+
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={(data) => data}>
|
|
41
|
+
<ControlledTextField
|
|
42
|
+
name="controlledTextField"
|
|
43
|
+
helperText="This is some helper text"
|
|
44
|
+
placeholder="Name"
|
|
45
|
+
required="This field is required."
|
|
46
|
+
maxLength={{ value: 10, message: 'Too long' }}
|
|
47
|
+
inputProps={{
|
|
48
|
+
'data-testid': 'testTextField',
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
<Actions />
|
|
52
|
+
<SubmittedValues />
|
|
53
|
+
</ControlledForm>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const input = screen.getByTestId('testTextField');
|
|
57
|
+
|
|
58
|
+
fireEvent.change(input, { target: { value: 'This is way too much text' } });
|
|
59
|
+
|
|
60
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
61
|
+
|
|
62
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(0));
|
|
63
|
+
|
|
64
|
+
await waitFor(() => expect(screen.findByText('Too long')).toBeDefined());
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('should render the error styling if an error is returned', async () => {
|
|
68
|
+
const screen = render(
|
|
69
|
+
<ControlledForm values={{ controlledTextField: undefined }} onSubmit={onSubmit}>
|
|
70
|
+
<ControlledTextField
|
|
71
|
+
name="controlledTextField"
|
|
72
|
+
helperText="This is some helper text"
|
|
73
|
+
placeholder="Name"
|
|
74
|
+
required="This field is required."
|
|
75
|
+
maxLength={{ value: 10, message: 'Too long' }}
|
|
76
|
+
inputProps={{
|
|
77
|
+
'data-testid': 'testTextField',
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
<Actions />
|
|
81
|
+
<SubmittedValues />
|
|
82
|
+
</ControlledForm>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const input = screen.getByTestId('testTextField');
|
|
86
|
+
|
|
87
|
+
fireEvent.change(input, { target: { value: 'Some Text' } });
|
|
88
|
+
|
|
89
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
90
|
+
|
|
91
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
|
92
|
+
|
|
93
|
+
const result = screen.getByTestId('result');
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
const formValues = JSON.parse(result.innerHTML).controlledTextField;
|
|
96
|
+
expect(formValues).toBe('Some Text');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|