@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +65 -0
  3. package/dist/index.d.mts +20 -0
  4. package/dist/index.d.ts +20 -0
  5. package/dist/index.js +71 -0
  6. package/dist/index.mjs +47 -0
  7. package/docs/propDefinitions.tsx +31 -0
  8. package/introduction.stories.mdx +7 -0
  9. package/jest.config.js +7 -0
  10. package/package.json +66 -0
  11. package/project.json +41 -0
  12. package/src/index.ts +1 -0
  13. package/src/lib/AsyncAutocomplete.stories.tsx +113 -0
  14. package/src/lib/AsyncAutocomplete.test.tsx +162 -0
  15. package/src/lib/AsyncAutocomplete.tsx +92 -0
  16. package/src/lib/Autocomplete.stories.tsx +60 -0
  17. package/src/lib/Autocomplete.test.tsx +70 -0
  18. package/src/lib/Autocomplete.tsx +96 -0
  19. package/src/lib/Checkbox.stories.tsx +67 -0
  20. package/src/lib/Checkbox.test.tsx +73 -0
  21. package/src/lib/Checkbox.tsx +37 -0
  22. package/src/lib/CodesAutocomplete.stories.tsx +79 -0
  23. package/src/lib/CodesAutocomplete.test.tsx +128 -0
  24. package/src/lib/CodesAutocomplete.tsx +76 -0
  25. package/src/lib/ControlledForm.stories.tsx +74 -0
  26. package/src/lib/ControlledForm.test.tsx +77 -0
  27. package/src/lib/ControlledForm.tsx +35 -0
  28. package/src/lib/Datepicker.stories.tsx +63 -0
  29. package/src/lib/Datepicker.test.tsx +73 -0
  30. package/src/lib/Datepicker.tsx +49 -0
  31. package/src/lib/Input.stories.tsx +60 -0
  32. package/src/lib/Input.test.tsx +98 -0
  33. package/src/lib/Input.tsx +54 -0
  34. package/src/lib/OrganizationAutocomplete.stories.tsx +77 -0
  35. package/src/lib/OrganizationAutocomplete.test.tsx +125 -0
  36. package/src/lib/OrganizationAutocomplete.tsx +75 -0
  37. package/src/lib/ProviderAutocomplete.stories.tsx +79 -0
  38. package/src/lib/ProviderAutocomplete.test.tsx +128 -0
  39. package/src/lib/ProviderAutocomplete.tsx +80 -0
  40. package/src/lib/RadioGroup.stories.tsx +63 -0
  41. package/src/lib/RadioGroup.test.tsx +66 -0
  42. package/src/lib/RadioGroup.tsx +68 -0
  43. package/src/lib/Select.stories.tsx +74 -0
  44. package/src/lib/Select.test.tsx +68 -0
  45. package/src/lib/Select.tsx +55 -0
  46. package/src/lib/TextField.stories.tsx +67 -0
  47. package/src/lib/TextField.test.tsx +99 -0
  48. package/src/lib/TextField.tsx +67 -0
  49. package/tsconfig.json +5 -0
  50. package/tsconfig.spec.json +10 -0
@@ -0,0 +1,79 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ControlledCodesAutocomplete } from './CodesAutocomplete';
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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
10
+ import { missingRHFprops } from '../../docs/propDefinitions';
11
+
12
+ const meta: Meta<typeof ControlledCodesAutocomplete> = {
13
+ title: 'Form Components/Controlled Form/Autocomplete/ControlledCodesAutocomplete',
14
+ component: ControlledCodesAutocomplete,
15
+ tags: ['autodocs'],
16
+ argTypes: missingRHFprops,
17
+ };
18
+
19
+ export default meta;
20
+
21
+ const client = new QueryClient({
22
+ defaultOptions: {
23
+ queries: {
24
+ refetchOnWindowFocus: false,
25
+ },
26
+ },
27
+ });
28
+
29
+ export const _ControlledCodesAutoComplete: StoryObj<typeof ControlledCodesAutocomplete> = {
30
+ render: (args) => {
31
+ const SubmittedValues = () => {
32
+ const {
33
+ getValues,
34
+ formState: { isSubmitSuccessful },
35
+ } = useFormContext();
36
+
37
+ return isSubmitSuccessful ? (
38
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
39
+ <Typography variant="h2">Submitted Values</Typography>
40
+ <pre>{JSON.stringify(getValues(), null, 2)}</pre>
41
+ </Paper>
42
+ ) : null;
43
+ };
44
+
45
+ const Actions = () => {
46
+ const {
47
+ reset,
48
+ formState: { isSubmitSuccessful },
49
+ } = useFormContext();
50
+ return (
51
+ <Grid container direction="row" justifyContent="space-between" marginTop={1}>
52
+ <Button disabled={!isSubmitSuccessful} children="Reset" color="secondary" onClick={() => reset()} />
53
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
54
+ </Grid>
55
+ );
56
+ };
57
+ return (
58
+ <QueryClientProvider client={client}>
59
+ <ControlledForm values={{}} onSubmit={(data) => data}>
60
+ <ControlledCodesAutocomplete {...args} />
61
+ <Actions />
62
+ <SubmittedValues />
63
+ </ControlledForm>
64
+ </QueryClientProvider>
65
+ );
66
+ },
67
+ args: {
68
+ name: 'controlledCodesAutocomplete',
69
+ list: 'ABC',
70
+ FieldProps: {
71
+ label: 'Code Select',
72
+ helperText: 'Select a code from the list',
73
+ placeholder: 'Select...',
74
+ fullWidth: false,
75
+ },
76
+ limit: 15,
77
+ required: 'This is required.',
78
+ },
79
+ };
@@ -0,0 +1,128 @@
1
+ import { fireEvent, render, waitFor } from '@testing-library/react';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { Paper } from '@availity/mui-paper';
4
+ import { Typography } from '@availity/mui-typography';
5
+ import { useFormContext } from 'react-hook-form';
6
+ import { Grid } from '@availity/mui-layout';
7
+ import { Button } from '@availity/mui-button';
8
+ // eslint-disable-next-line @nx/enforce-module-boundaries
9
+ import { server } from '@availity/mock/src/lib/server';
10
+ import { ControlledForm } from './ControlledForm';
11
+ import { ControlledCodesAutocomplete } from './CodesAutocomplete';
12
+
13
+ const SubmittedValues = () => {
14
+ const {
15
+ getValues,
16
+ formState: { isSubmitSuccessful },
17
+ } = useFormContext();
18
+
19
+ return isSubmitSuccessful ? (
20
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
21
+ <Typography variant="h2">Submitted Values</Typography>
22
+ <pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
23
+ </Paper>
24
+ ) : null;
25
+ };
26
+
27
+ const Actions = () => {
28
+ const {
29
+ formState: { isSubmitSuccessful },
30
+ } = useFormContext();
31
+ return (
32
+ <Grid container direction="row" justifyContent="space-between">
33
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
34
+ </Grid>
35
+ );
36
+ };
37
+
38
+ const onSubmit = jest.fn();
39
+
40
+ describe('ControlledAsyncAutocomplete', () => {
41
+ beforeAll(() => {
42
+ // Start the interception.
43
+ server.listen();
44
+ });
45
+
46
+ afterEach(() => {
47
+ // Remove any handlers you may have added
48
+ // in individual tests (runtime handlers).
49
+ server.resetHandlers();
50
+ jest.restoreAllMocks();
51
+ });
52
+
53
+ const client = new QueryClient({
54
+ defaultOptions: {
55
+ queries: {
56
+ refetchOnWindowFocus: false,
57
+ },
58
+ },
59
+ });
60
+
61
+ test('should loadOptions successfully', async () => {
62
+ const screen = render(
63
+ <QueryClientProvider client={client}>
64
+ <ControlledForm values={{}} onSubmit={onSubmit}>
65
+ <ControlledCodesAutocomplete
66
+ name="controlledCodesAutocomplete"
67
+ list="ABC"
68
+ FieldProps={{
69
+ label: 'Code Select',
70
+ helperText: 'Select a code from the list',
71
+ placeholder: 'Select...',
72
+ fullWidth: false,
73
+ }}
74
+ limit={15}
75
+ />
76
+ <Actions />
77
+ <SubmittedValues />
78
+ </ControlledForm>
79
+ </QueryClientProvider>
80
+ );
81
+
82
+ const dropdown = screen.getByRole('combobox');
83
+ fireEvent.click(dropdown);
84
+ fireEvent.keyDown(dropdown, { key: 'ArrowDown' });
85
+
86
+ await waitFor(() => expect(screen.getByText('171100000X - Acupuncturist')).toBeDefined());
87
+ });
88
+
89
+ test('should set the value and submit the form data', async () => {
90
+ const screen = render(
91
+ <QueryClientProvider client={client}>
92
+ <ControlledForm values={{}} onSubmit={onSubmit}>
93
+ <ControlledCodesAutocomplete
94
+ name="controlledCodesAutocomplete"
95
+ list="ABC"
96
+ FieldProps={{
97
+ label: 'Code Select',
98
+ helperText: 'Select a code from the list',
99
+ placeholder: 'Select...',
100
+ fullWidth: false,
101
+ }}
102
+ limit={15}
103
+ />
104
+ <Actions />
105
+ <SubmittedValues />
106
+ </ControlledForm>
107
+ </QueryClientProvider>
108
+ );
109
+
110
+ const dropdown = screen.getByRole('combobox');
111
+ fireEvent.click(dropdown);
112
+ fireEvent.keyDown(dropdown, { key: 'ArrowDown' });
113
+
114
+ await waitFor(() => screen.getByText('171100000X - Acupuncturist'));
115
+
116
+ fireEvent.click(screen.getByText('171100000X - Acupuncturist'));
117
+
118
+ fireEvent.click(screen.getByText('Submit'));
119
+
120
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
121
+ const result = screen.getByTestId('result');
122
+ await waitFor(() => {
123
+ const controlledCodesAutocompleteValue = JSON.parse(result.innerHTML).controlledCodesAutocomplete;
124
+ expect(controlledCodesAutocompleteValue.code).toBe('171100000X');
125
+ expect(controlledCodesAutocompleteValue.value).toBe('Acupuncturist');
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,76 @@
1
+ import { CodesAutocomplete, CodesAutocompleteProps } from '@availity/mui-autocomplete';
2
+ import { useFormContext, Controller, RegisterOptions, ControllerProps, FieldValues } from 'react-hook-form';
3
+
4
+ type ControlledCodesAutocompleteProps = Omit<CodesAutocompleteProps, 'name'> &
5
+ Omit<RegisterOptions<FieldValues, string>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'> &
6
+ Pick<ControllerProps, 'defaultValue' | 'shouldUnregister' | 'name'>;
7
+
8
+ export const ControlledCodesAutocomplete = ({
9
+ name,
10
+ defaultValue,
11
+ deps,
12
+ max,
13
+ maxLength,
14
+ onBlur,
15
+ onChange,
16
+ pattern,
17
+ required,
18
+ shouldUnregister,
19
+ validate,
20
+ value,
21
+ FieldProps,
22
+ ...rest
23
+ }: ControlledCodesAutocompleteProps) => {
24
+ const {
25
+ control,
26
+ formState: { errors },
27
+ } = useFormContext();
28
+ const errorMessage = errors[name]?.message;
29
+ return (
30
+ <Controller
31
+ name={name}
32
+ control={control}
33
+ defaultValue={defaultValue}
34
+ rules={{
35
+ deps,
36
+ max,
37
+ maxLength,
38
+ onBlur,
39
+ onChange,
40
+ pattern,
41
+ required,
42
+ shouldUnregister,
43
+ validate,
44
+ value,
45
+ }}
46
+ shouldUnregister={shouldUnregister}
47
+ render={({ field: { onChange, value, onBlur } }) => (
48
+ <CodesAutocomplete
49
+ {...rest}
50
+ FieldProps={{
51
+ ...FieldProps,
52
+ error: !!errorMessage,
53
+ helperText:
54
+ errorMessage && typeof errorMessage === 'string' ? (
55
+ <>
56
+ {errorMessage}
57
+ <br />
58
+ {FieldProps?.helperText}
59
+ </>
60
+ ) : (
61
+ FieldProps?.helperText
62
+ ),
63
+ }}
64
+ onChange={(event, value, reason) => {
65
+ if (reason === 'clear') {
66
+ onChange(null);
67
+ }
68
+ onChange(value);
69
+ }}
70
+ onBlur={onBlur}
71
+ value={value || null}
72
+ />
73
+ )}
74
+ />
75
+ );
76
+ };
@@ -0,0 +1,74 @@
1
+ // Each exported component in the package should have its own stories file
2
+
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+ import { useFormContext } from 'react-hook-form';
5
+ import { Paper } from '@availity/mui-paper';
6
+ import { Typography } from '@availity/mui-typography';
7
+ import { Grid } from '@availity/mui-layout';
8
+ import { Button } from '@availity/mui-button';
9
+ import { ControlledForm, ControlledFormProps } from './ControlledForm';
10
+ import { ControlledTextField } from './TextField';
11
+ import * as yup from 'yup';
12
+ import { yupResolver } from '@hookform/resolvers/yup';
13
+
14
+ const meta: Meta<typeof ControlledForm> = {
15
+ title: 'Form Components/Controlled Form/ControlledForm',
16
+ component: ControlledForm,
17
+ tags: ['autodocs'],
18
+ };
19
+
20
+ export default meta;
21
+
22
+ export const _ControlledForm: StoryObj<typeof ControlledForm> = {
23
+ render: ({ values, onSubmit, ...args }: ControlledFormProps) => {
24
+ const SubmittedValues = () => {
25
+ const {
26
+ getValues,
27
+ formState: { isSubmitSuccessful },
28
+ } = useFormContext();
29
+
30
+ return isSubmitSuccessful ? (
31
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
32
+ <Typography variant="h2">Submitted Values</Typography>
33
+ <pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
34
+ </Paper>
35
+ ) : null;
36
+ };
37
+
38
+ const Actions = () => {
39
+ const {
40
+ reset,
41
+ formState: { isSubmitSuccessful },
42
+ } = useFormContext();
43
+ return (
44
+ <Grid container direction="row" justifyContent="space-between">
45
+ <Button disabled={!isSubmitSuccessful} children="Reset" color="secondary" onClick={() => reset()} />
46
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
47
+ </Grid>
48
+ );
49
+ };
50
+ return (
51
+ <ControlledForm
52
+ values={values}
53
+ onSubmit={onSubmit}
54
+ schema={yup.object({ controlledTextField: yup.string().max(10) })}
55
+ validationResolver={yupResolver}
56
+ {...args}
57
+ >
58
+ <ControlledTextField
59
+ name="controlledTextField"
60
+ placeholder="Name"
61
+ inputProps={{
62
+ 'data-testid': 'testTextField',
63
+ }}
64
+ />
65
+ <Actions />
66
+ <SubmittedValues />
67
+ </ControlledForm>
68
+ );
69
+ },
70
+ args: {
71
+ values: { controlledTextField: undefined },
72
+ onSubmit: (data) => data,
73
+ },
74
+ };
@@ -0,0 +1,77 @@
1
+ import { render, fireEvent, waitFor } from '@testing-library/react';
2
+ import * as yup from 'yup';
3
+ import { ControlledForm } from './ControlledForm';
4
+ import { ControlledTextField } from './TextField';
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 { Button } from '@availity/mui-button';
10
+ import { yupResolver } from '@hookform/resolvers/yup';
11
+
12
+ const SubmittedValues = () => {
13
+ const {
14
+ getValues,
15
+ formState: { isSubmitSuccessful },
16
+ } = useFormContext();
17
+
18
+ return isSubmitSuccessful ? (
19
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
20
+ <Typography variant="h2">Submitted Values</Typography>
21
+ <pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
22
+ </Paper>
23
+ ) : null;
24
+ };
25
+
26
+ const Actions = () => {
27
+ const {
28
+ formState: { isSubmitSuccessful },
29
+ } = useFormContext();
30
+ return (
31
+ <Grid container direction="row" justifyContent="space-between">
32
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
33
+ </Grid>
34
+ );
35
+ };
36
+
37
+ const onSubmit = jest.fn();
38
+
39
+ describe('ControlledForm', () => {
40
+ test('should render successfully', () => {
41
+ const { getByText } = render(
42
+ <ControlledForm onSubmit={(data) => data} values={{}}>
43
+ Test
44
+ </ControlledForm>
45
+ );
46
+ expect(getByText('Test')).toBeTruthy();
47
+ });
48
+ test('should handle yup schema resolver', async () => {
49
+ const screen = render(
50
+ <ControlledForm
51
+ values={{ controlledTextField: undefined }}
52
+ onSubmit={onSubmit}
53
+ schema={yup.object({ controlledTextField: yup.string().max(10) })}
54
+ validationResolver={yupResolver}
55
+ >
56
+ <ControlledTextField
57
+ name="controlledTextField"
58
+ placeholder="Name"
59
+ inputProps={{
60
+ 'data-testid': 'testTextField',
61
+ }}
62
+ />
63
+ <Actions />
64
+ <SubmittedValues />
65
+ </ControlledForm>
66
+ );
67
+
68
+ const input = screen.getByTestId('testTextField');
69
+
70
+ fireEvent.change(input, { target: { value: 'This is too much text' } });
71
+
72
+ fireEvent.click(screen.getByText('Submit'));
73
+
74
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(0));
75
+ await waitFor(() => expect(screen.findByText('controlledTextField must be at most 10 characters')).toBeDefined());
76
+ });
77
+ });
@@ -0,0 +1,35 @@
1
+ import { FormHTMLAttributes } from 'react';
2
+ import { useForm, SubmitHandler, FormProvider, Resolver } from 'react-hook-form';
3
+
4
+ export type ControlledFormProps = {
5
+ /** This function will receive the form data if form validation is successful. */
6
+ onSubmit: SubmitHandler<any>;
7
+ /** Reactive values to update the form values. */
8
+ values: Record<string, any>;
9
+ /** The schema used by the validationResolver. */
10
+ schema?: unknown;
11
+ /** Integrates with your preferred schema validation library.
12
+ * More information on react-hook-form's resolvers can be
13
+ * found here: https://github.com/react-hook-form/resolvers#quickstart
14
+ */
15
+ validationResolver?: (schema: unknown) => Resolver;
16
+ } & FormHTMLAttributes<HTMLFormElement>;
17
+
18
+ type UseFormOptions = {
19
+ values: Record<string, any>;
20
+ resolver?: Resolver;
21
+ };
22
+
23
+ export const ControlledForm = ({ onSubmit, values, schema, validationResolver, ...rest }: ControlledFormProps) => {
24
+ const useFormOptions: UseFormOptions = { values };
25
+ if (schema && validationResolver) {
26
+ useFormOptions.resolver = validationResolver(schema);
27
+ }
28
+ const methods = useForm(useFormOptions);
29
+
30
+ return (
31
+ <FormProvider {...methods}>
32
+ <form onSubmit={methods.handleSubmit(onSubmit)} {...rest} />
33
+ </FormProvider>
34
+ );
35
+ };
@@ -0,0 +1,63 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ControlledDatepicker, ControlledDatepickerProps } from './Datepicker';
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 ControlledDatepicker> = {
11
+ title: 'Form Components/Controlled Form/ControlledDatepicker',
12
+ component: ControlledDatepicker,
13
+ tags: ['autodocs'],
14
+ };
15
+
16
+ export default meta;
17
+
18
+ export const _ControlledInput: StoryObj<typeof ControlledDatepicker> = {
19
+ render: (args: ControlledDatepickerProps) => {
20
+ const SubmittedValues = () => {
21
+ const {
22
+ getValues,
23
+ formState: { isSubmitSuccessful },
24
+ } = useFormContext();
25
+
26
+ return isSubmitSuccessful ? (
27
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
28
+ <Typography variant="h2">Submitted Values</Typography>
29
+ <pre>{JSON.stringify(getValues(), null, 2)}</pre>
30
+ </Paper>
31
+ ) : null;
32
+ };
33
+
34
+ const Actions = () => {
35
+ const {
36
+ reset,
37
+ formState: { isSubmitSuccessful },
38
+ } = useFormContext();
39
+ return (
40
+ <Grid container direction="row" justifyContent="space-between" marginTop={1}>
41
+ <Button disabled={!isSubmitSuccessful} children="Reset" color="secondary" onClick={() => reset()} />
42
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
43
+ </Grid>
44
+ );
45
+ };
46
+ return (
47
+ <ControlledForm values={{ controlledInput: undefined }} onSubmit={(data) => data}>
48
+ <ControlledDatepicker {...args} />
49
+ <Actions />
50
+ <SubmittedValues />
51
+ </ControlledForm>
52
+ );
53
+ },
54
+ args: {
55
+ name: 'controlledDatepicker',
56
+ FieldProps: {
57
+ fullWidth: false,
58
+ helperText: 'Help text for the field',
59
+ helpTopicId: '1234',
60
+ label: 'Date',
61
+ },
62
+ },
63
+ };
@@ -0,0 +1,73 @@
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 { ThemeProvider } from '@availity/theme-provider';
8
+ import dayjs from 'dayjs';
9
+ import { ControlledForm } from './ControlledForm';
10
+ import { ControlledDatepicker } from './Datepicker';
11
+
12
+ const SubmittedValues = () => {
13
+ const {
14
+ getValues,
15
+ formState: { isSubmitSuccessful },
16
+ } = useFormContext();
17
+
18
+ return isSubmitSuccessful ? (
19
+ <Paper sx={{ padding: '1.5rem', marginTop: '1.5rem' }}>
20
+ <Typography variant="h2">Submitted Values</Typography>
21
+ <pre data-testid="result">{JSON.stringify(getValues(), null, 2)}</pre>
22
+ </Paper>
23
+ ) : null;
24
+ };
25
+
26
+ const Actions = () => {
27
+ const {
28
+ formState: { isSubmitSuccessful },
29
+ } = useFormContext();
30
+ return (
31
+ <Grid container direction="row" justifyContent="space-between">
32
+ <Button type="submit" disabled={isSubmitSuccessful} children="Submit" />
33
+ </Grid>
34
+ );
35
+ };
36
+
37
+ const onSubmit = jest.fn();
38
+
39
+ describe('Datepicker', () => {
40
+ test('should render successfully and submit selection', async () => {
41
+ const screen = render(
42
+ <ThemeProvider>
43
+ <ControlledForm values={{ controlledDatepicker: undefined }} onSubmit={onSubmit}>
44
+ <ControlledDatepicker
45
+ name="controlledDatepicker"
46
+ FieldProps={{
47
+ fullWidth: false,
48
+ helperText: 'Help text for the field',
49
+ helpTopicId: '1234',
50
+ label: 'Date',
51
+ }}
52
+ />
53
+ <Actions />
54
+ <SubmittedValues />
55
+ </ControlledForm>
56
+ </ThemeProvider>
57
+ );
58
+ expect(screen.getAllByText('Date')).toBeTruthy();
59
+ const input = screen.getByLabelText('Choose date');
60
+ fireEvent.click(input);
61
+ const date = screen.getAllByRole('gridcell');
62
+ fireEvent.click(date[7]); // The 8th gridcell will always be a day because it is the first day of the second week of the month.
63
+
64
+ fireEvent.click(screen.getByText('Submit'));
65
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
66
+ const result = screen.getByTestId('result');
67
+ await waitFor(() => {
68
+ const controlledDatepickerValue = JSON.parse(result.innerHTML).controlledDatepicker;
69
+ expect(controlledDatepickerValue).toBeDefined();
70
+ expect(dayjs(controlledDatepickerValue).isValid()).toBeTruthy();
71
+ });
72
+ }, 10000);
73
+ });
@@ -0,0 +1,49 @@
1
+ import { Datepicker, DatepickerProps } from '@availity/mui-datepicker';
2
+ import { useFormContext, RegisterOptions, FieldValues, Controller, ControllerProps } from 'react-hook-form';
3
+
4
+ export type ControlledDatepickerProps = DatepickerProps &
5
+ Omit<RegisterOptions<FieldValues, string>, 'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'> &
6
+ Pick<ControllerProps, 'defaultValue' | 'shouldUnregister' | 'name'>;
7
+
8
+ export const ControlledDatepicker = ({
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
+ ...rest
24
+ }: ControlledDatepickerProps) => {
25
+ const { control } = useFormContext();
26
+ return (
27
+ <Controller
28
+ name={name}
29
+ control={control}
30
+ defaultValue={defaultValue}
31
+ rules={{
32
+ deps,
33
+ max,
34
+ maxLength,
35
+ min,
36
+ minLength,
37
+ onBlur,
38
+ onChange,
39
+ pattern,
40
+ required,
41
+ shouldUnregister,
42
+ validate,
43
+ value,
44
+ }}
45
+ shouldUnregister={shouldUnregister}
46
+ render={({ field: { onChange, value } }) => <Datepicker {...rest} onChange={onChange} value={value || null} />}
47
+ />
48
+ );
49
+ };