envoc-form 5.0.3 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +163 -20
  2. package/es/ConfirmBaseForm/ConfirmBaseForm.js +2 -2
  3. package/es/DatePicker/DatePickerGroup.js +2 -2
  4. package/es/FieldArray/FieldArray.js +4 -4
  5. package/es/File/FileGroup.js +3 -3
  6. package/es/Form/Form.js +2 -2
  7. package/es/Group.js +3 -3
  8. package/es/Input/CheckboxGroup.d.ts +6 -0
  9. package/es/Input/CheckboxGroup.js +14 -0
  10. package/es/Input/CheckboxInputGroup.d.ts +13 -0
  11. package/es/Input/CheckboxInputGroup.js +41 -0
  12. package/es/Input/IconInputGroup.js +2 -2
  13. package/es/Input/InputGroup.js +2 -2
  14. package/es/Input/MoneyInputGroup.js +2 -2
  15. package/es/Input/NumberInputGroup.js +2 -2
  16. package/es/Input/PhoneNumberInputGroup.js +2 -2
  17. package/es/Input/StringInputGroup.js +2 -2
  18. package/es/Select/SelectGroup.js +3 -3
  19. package/es/StandardFormActions.js +3 -3
  20. package/es/SubmitFormButton.js +2 -2
  21. package/es/TextArea/TextAreaGroup.js +2 -2
  22. package/es/index.d.ts +2 -0
  23. package/es/index.js +1 -0
  24. package/lib/ConfirmBaseForm/ConfirmBaseForm.js +2 -5
  25. package/lib/DatePicker/DatePickerGroup.js +2 -2
  26. package/lib/FieldArray/FieldArray.js +4 -4
  27. package/lib/File/FileGroup.js +3 -3
  28. package/lib/Form/Form.js +2 -2
  29. package/lib/Group.js +3 -3
  30. package/lib/Input/CheckboxGroup.d.ts +6 -0
  31. package/lib/Input/CheckboxGroup.js +20 -0
  32. package/lib/Input/CheckboxInputGroup.d.ts +13 -0
  33. package/lib/Input/CheckboxInputGroup.js +46 -0
  34. package/lib/Input/IconInputGroup.js +2 -2
  35. package/lib/Input/InputGroup.js +2 -2
  36. package/lib/Input/MoneyInputGroup.js +2 -2
  37. package/lib/Input/NumberInputGroup.js +2 -2
  38. package/lib/Input/PhoneNumberInputGroup.js +2 -2
  39. package/lib/Input/StringInputGroup.js +2 -2
  40. package/lib/Select/SelectGroup.js +3 -3
  41. package/lib/StandardFormActions.js +3 -3
  42. package/lib/SubmitFormButton.js +2 -2
  43. package/lib/TextArea/TextAreaGroup.js +2 -2
  44. package/lib/index.d.ts +2 -0
  45. package/lib/index.js +3 -1
  46. package/package.json +111 -111
  47. package/src/AddressInput/AddressInput.test.tsx +27 -27
  48. package/src/AddressInput/AddressInput.tsx +82 -82
  49. package/src/AddressInput/UsStates.ts +55 -55
  50. package/src/AddressInput/__snapshots__/AddressInput.test.tsx.snap +182 -182
  51. package/src/ConfirmBaseForm/ConfirmBaseForm.test.tsx +24 -24
  52. package/src/ConfirmBaseForm/ConfirmBaseForm.tsx +74 -74
  53. package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.tsx.snap +23 -23
  54. package/src/ConfirmDeleteForm/ConfirmDeleteForm.test.tsx +24 -24
  55. package/src/ConfirmDeleteForm/ConfirmDeleteForm.tsx +87 -87
  56. package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.tsx.snap +25 -25
  57. package/src/DatePicker/DatePicker.test.tsx +48 -48
  58. package/src/DatePicker/DatePickerGroup.tsx +109 -115
  59. package/src/DatePicker/DatePickerHelper.ts +4 -4
  60. package/src/DatePicker/StringDateOnlyPickerGroup.tsx +28 -28
  61. package/src/DatePicker/StringDatePickerGroup.tsx +20 -20
  62. package/src/DatePicker/__snapshots__/DatePicker.test.tsx.snap +152 -152
  63. package/src/Field/CustomFieldInputProps.ts +10 -10
  64. package/src/Field/CustomFieldMetaProps.ts +5 -5
  65. package/src/Field/Field.tsx +113 -113
  66. package/src/Field/FieldErrorScrollTarget.tsx +12 -12
  67. package/src/Field/FieldNameContext.ts +6 -6
  68. package/src/Field/FieldSection.tsx +18 -18
  69. package/src/Field/InjectedFieldProps.ts +8 -8
  70. package/src/Field/StandAloneInput.tsx +55 -55
  71. package/src/Field/useStandardField.ts +125 -125
  72. package/src/FieldArray/FieldArray.tsx +154 -154
  73. package/src/File/FileGroup.test.tsx +35 -35
  74. package/src/File/FileGroup.tsx +82 -85
  75. package/src/File/FileList.tsx +21 -21
  76. package/src/File/__snapshots__/FileGroup.test.tsx.snap +34 -34
  77. package/src/File/humanFileSize.ts +8 -8
  78. package/src/Form/FocusError.tsx +55 -55
  79. package/src/Form/Form.test.tsx +14 -14
  80. package/src/Form/Form.tsx +234 -237
  81. package/src/Form/FormBasedPreventNavigation.tsx +56 -56
  82. package/src/Form/LegacyFormBasedPreventNavigation.tsx +77 -77
  83. package/src/Form/NewFormBasedPreventNavigation.tsx +59 -59
  84. package/src/Form/ServerErrorContext.ts +18 -18
  85. package/src/Form/__snapshots__/Form.test.tsx.snap +10 -10
  86. package/src/FormActions.tsx +47 -47
  87. package/src/FormDefaults.ts +2 -2
  88. package/src/Group.tsx +62 -62
  89. package/src/Input/CheckboxGroup.tsx +60 -0
  90. package/src/Input/CheckboxInputGroup.tsx +78 -0
  91. package/src/Input/IconInputGroup.tsx +54 -54
  92. package/src/Input/InputGroup.tsx +66 -72
  93. package/src/Input/MoneyInputGroup.tsx +47 -50
  94. package/src/Input/NumberInputGroup.tsx +45 -48
  95. package/src/Input/PhoneNumberInputGroup.tsx +45 -45
  96. package/src/Input/StringInputGroup.tsx +50 -53
  97. package/src/Input/__Tests__/CheckboxInputGroup.test.tsx +26 -0
  98. package/src/Input/__Tests__/IconInputGroup.test.tsx +35 -35
  99. package/src/Input/__Tests__/MoneyInputGroup.test.tsx +37 -37
  100. package/src/Input/__Tests__/NumberInputGroup.test.tsx +35 -35
  101. package/src/Input/__Tests__/PhoneNumberInputGroup.test.tsx +36 -36
  102. package/src/Input/__Tests__/StringInputGroup.test.tsx +27 -27
  103. package/src/Input/__Tests__/__snapshots__/CheckboxInputGroup.test.tsx.snap +33 -0
  104. package/src/Input/__Tests__/__snapshots__/IconInputGroup.test.tsx.snap +32 -32
  105. package/src/Input/__Tests__/__snapshots__/MoneyInputGroup.test.tsx.snap +34 -34
  106. package/src/Input/__Tests__/__snapshots__/NumberInputGroup.test.tsx.snap +32 -32
  107. package/src/Input/__Tests__/__snapshots__/PhoneNumberInputGroup.test.tsx.snap +33 -33
  108. package/src/Input/__Tests__/__snapshots__/StringInputGroup.test.tsx.snap +31 -31
  109. package/src/Normalization/NormalizationFunction.ts +4 -4
  110. package/src/Normalization/normalizers.ts +44 -44
  111. package/src/Select/BooleanSelectGroup.tsx +28 -28
  112. package/src/Select/NumberSelectGroup.tsx +16 -16
  113. package/src/Select/SelectGroup.tsx +124 -124
  114. package/src/Select/SelectGroupPropsHelper.ts +4 -4
  115. package/src/Select/StringSelectGroup.tsx +16 -16
  116. package/src/Select/__tests__/BooleanSelectGroup.test.tsx +35 -35
  117. package/src/Select/__tests__/NumberSelectGroup.test.tsx +87 -87
  118. package/src/Select/__tests__/StringSelectGroup.test.tsx +89 -89
  119. package/src/Select/__tests__/__snapshots__/BooleanSelectGroup.test.tsx.snap +98 -98
  120. package/src/Select/__tests__/__snapshots__/NumberSelectGroup.test.tsx.snap +195 -195
  121. package/src/Select/__tests__/__snapshots__/StringSelectGroup.test.tsx.snap +195 -195
  122. package/src/StandardFormActions.tsx +41 -41
  123. package/src/SubmitFormButton.tsx +54 -54
  124. package/src/TextArea/TextAreaGroup.tsx +64 -64
  125. package/src/Validation/ValidatedApiResult.ts +8 -8
  126. package/src/Validation/ValidationError.ts +6 -6
  127. package/src/Validation/ValidationFunction.ts +4 -4
  128. package/src/Validation/validators.test.tsx +81 -81
  129. package/src/Validation/validators.ts +97 -97
  130. package/src/__Tests__/FormTestBase.tsx +65 -64
  131. package/src/__Tests__/RealisticForm.test.tsx +82 -82
  132. package/src/__Tests__/StandardFormActions.test.tsx +17 -17
  133. package/src/__Tests__/SubmitFormButton.test.tsx +17 -17
  134. package/src/__Tests__/__snapshots__/StandardFormActions.test.tsx.snap +27 -27
  135. package/src/__Tests__/__snapshots__/SubmitFormButton.test.tsx.snap +20 -20
  136. package/src/__Tests__/index.ts +3 -3
  137. package/src/_variables.scss +11 -11
  138. package/src/index.ts +156 -153
  139. package/src/react-app-env.d.ts +1 -1
  140. package/src/setupTests.ts +1 -1
  141. package/src/utils/objectContainsNonSerializableProperty.test.tsx +49 -49
  142. package/src/utils/objectContainsNonSerializableProperty.ts +17 -17
  143. package/src/utils/objectToFormData.test.tsx +76 -76
  144. package/src/utils/objectToFormData.ts +105 -105
  145. package/src/utils/typeChecks.ts +18 -18
@@ -1,87 +1,87 @@
1
- import React from 'react';
2
- import { useNavigate, useParams } from 'react-router-dom';
3
- import { toast } from 'react-toastify';
4
- import { useAxiosRequestProps } from 'envoc-request';
5
- import ConfirmBaseForm, {
6
- ConfirmBaseFormProps,
7
- } from '../ConfirmBaseForm/ConfirmBaseForm';
8
- import { FormDefaults } from '../FormDefaults';
9
-
10
- export interface ConfirmDeleteFormProps
11
- extends Pick<ConfirmBaseFormProps, 'style'> {
12
- /** Url to navigate to on success. */
13
- successUrl?: string;
14
- /** Form name (key) to apply the confirmation on. */
15
- form: string;
16
- /** Custom message to display.
17
- * @defaultValue `Are you sure you want to delete this?`
18
- */
19
- title?: string;
20
- /** Custom function when the axios request succeeds. */
21
- handleComplete?: Function;
22
- /** Custom function when the axios request fails. */
23
- handleError?: Function;
24
- /** Any components to be rendered in between the title and the buttons. */
25
- children?: React.ReactNode;
26
- }
27
-
28
- /**
29
- * Deletion confirmation. Navigates to a different route to allow the user to either confirm or cancel an action.
30
- *
31
- * Wraps `<ConfirmBaseForm/>`.
32
- */
33
- export default function ConfirmDeleteForm({
34
- successUrl,
35
- form,
36
- title,
37
- handleComplete,
38
- handleError,
39
- children,
40
- ...props
41
- }: ConfirmDeleteFormProps) {
42
- const navigate = useNavigate();
43
- const { entityId } = useParams();
44
- const onComplete =
45
- handleComplete ??
46
- function () {
47
- toast.success('Deleted!');
48
- goBack();
49
- };
50
- const onError =
51
- handleError ??
52
- function (error: any) {
53
- toast.error(
54
- error.response?.data?.validationFailures[0]?.errorMessage ??
55
- 'Something went wrong talking to the portal. Contact support if this continues.'
56
- );
57
- };
58
-
59
- const request = {
60
- method: 'delete',
61
- url: `/api/${form}/${entityId}`,
62
- onComplete: onComplete,
63
- onError: onError,
64
- } as useAxiosRequestProps;
65
-
66
- return (
67
- <ConfirmBaseForm
68
- request={request}
69
- handleCancel={goBack}
70
- title={title ?? 'Are you sure you want to delete this?'}
71
- confirmButtonText="Yes"
72
- confirmButtonClass={
73
- FormDefaults.cssClassPrefix + 'confirm-delete-form-yes-button'
74
- }
75
- {...props}>
76
- {children}
77
- </ConfirmBaseForm>
78
- );
79
-
80
- function goBack() {
81
- if (successUrl) {
82
- navigate(successUrl);
83
- } else {
84
- navigate(-1);
85
- }
86
- }
87
- }
1
+ import React from 'react';
2
+ import { useNavigate, useParams } from 'react-router-dom';
3
+ import { toast } from 'react-toastify';
4
+ import { useAxiosRequestProps } from 'envoc-request';
5
+ import ConfirmBaseForm, {
6
+ ConfirmBaseFormProps,
7
+ } from '../ConfirmBaseForm/ConfirmBaseForm';
8
+ import { FormDefaults } from '../FormDefaults';
9
+
10
+ export interface ConfirmDeleteFormProps
11
+ extends Pick<ConfirmBaseFormProps, 'style'> {
12
+ /** Url to navigate to on success. */
13
+ successUrl?: string;
14
+ /** Form name (key) to apply the confirmation on. */
15
+ form: string;
16
+ /** Custom message to display.
17
+ * @defaultValue `Are you sure you want to delete this?`
18
+ */
19
+ title?: string;
20
+ /** Custom function when the axios request succeeds. */
21
+ handleComplete?: Function;
22
+ /** Custom function when the axios request fails. */
23
+ handleError?: Function;
24
+ /** Any components to be rendered in between the title and the buttons. */
25
+ children?: React.ReactNode;
26
+ }
27
+
28
+ /**
29
+ * Deletion confirmation. Navigates to a different route to allow the user to either confirm or cancel an action.
30
+ *
31
+ * Wraps `<ConfirmBaseForm/>`.
32
+ */
33
+ export default function ConfirmDeleteForm({
34
+ successUrl,
35
+ form,
36
+ title,
37
+ handleComplete,
38
+ handleError,
39
+ children,
40
+ ...props
41
+ }: ConfirmDeleteFormProps) {
42
+ const navigate = useNavigate();
43
+ const { entityId } = useParams();
44
+ const onComplete =
45
+ handleComplete ??
46
+ function () {
47
+ toast.success('Deleted!');
48
+ goBack();
49
+ };
50
+ const onError =
51
+ handleError ??
52
+ function (error: any) {
53
+ toast.error(
54
+ error.response?.data?.validationFailures[0]?.errorMessage ??
55
+ 'Something went wrong talking to the portal. Contact support if this continues.'
56
+ );
57
+ };
58
+
59
+ const request = {
60
+ method: 'delete',
61
+ url: `/api/${form}/${entityId}`,
62
+ onComplete: onComplete,
63
+ onError: onError,
64
+ } as useAxiosRequestProps;
65
+
66
+ return (
67
+ <ConfirmBaseForm
68
+ request={request}
69
+ handleCancel={goBack}
70
+ title={title ?? 'Are you sure you want to delete this?'}
71
+ confirmButtonText="Yes"
72
+ confirmButtonClass={
73
+ FormDefaults.cssClassPrefix + 'confirm-delete-form-yes-button'
74
+ }
75
+ {...props}>
76
+ {children}
77
+ </ConfirmBaseForm>
78
+ );
79
+
80
+ function goBack() {
81
+ if (successUrl) {
82
+ navigate(successUrl);
83
+ } else {
84
+ navigate(-1);
85
+ }
86
+ }
87
+ }
@@ -1,25 +1,25 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`ConfirmDeleteForm has matching snapshot 1`] = `
4
- <DocumentFragment>
5
- <div
6
- style="text-align: center;"
7
- >
8
- <h3>
9
- Are you sure you want to delete this?
10
- </h3>
11
- <button
12
- class="envoc-form-confirm-delete-form-yes-button"
13
- type="button"
14
- >
15
- Yes
16
- </button>
17
- <button
18
- class="envoc-form-confirm-base-form-cancel-button"
19
- type="button"
20
- >
21
- Cancel
22
- </button>
23
- </div>
24
- </DocumentFragment>
25
- `;
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ConfirmDeleteForm has matching snapshot 1`] = `
4
+ <DocumentFragment>
5
+ <div
6
+ style="text-align: center;"
7
+ >
8
+ <h3>
9
+ Are you sure you want to delete this?
10
+ </h3>
11
+ <button
12
+ class="envoc-form-confirm-delete-form-yes-button"
13
+ type="button"
14
+ >
15
+ Yes
16
+ </button>
17
+ <button
18
+ class="envoc-form-confirm-base-form-cancel-button"
19
+ type="button"
20
+ >
21
+ Cancel
22
+ </button>
23
+ </div>
24
+ </DocumentFragment>
25
+ `;
@@ -1,48 +1,48 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import StringDatePickerGroup from './StringDatePickerGroup';
4
- import FormTestBase from '../__Tests__/FormTestBase';
5
-
6
- //hack so the datepicker internals don't complain about this not existing in the context of jest
7
- HTMLCanvasElement.prototype.getContext = () => null;
8
-
9
- describe('IconInputGroup', () => {
10
- it('renders without crashing', () => {
11
- render(
12
- <FormTestBase>
13
- {({ Field }) => (
14
- <Field
15
- name="favoriteDate"
16
- Component={StringDatePickerGroup}
17
- label="Favorite Date"
18
- monthPlaceholder="mm"
19
- dayPlaceholder="dd"
20
- yearPlaceholder="yyyy"
21
- maxDate={new Date('9/23/2023')}
22
- minDate={new Date('6/22/2022')}
23
- />
24
- )}
25
- </FormTestBase>
26
- );
27
- });
28
-
29
- it('has matching snapshot', () => {
30
- const renderResult = render(
31
- <FormTestBase>
32
- {({ Field }) => (
33
- <Field
34
- name="favoriteDate"
35
- Component={StringDatePickerGroup}
36
- label="Favorite Date"
37
- monthPlaceholder="mm"
38
- dayPlaceholder="dd"
39
- yearPlaceholder="yyyy"
40
- maxDate={new Date('9/23/2023')}
41
- minDate={new Date('6/22/2022')}
42
- />
43
- )}
44
- </FormTestBase>
45
- );
46
- expect(renderResult.asFragment()).toMatchSnapshot();
47
- });
48
- });
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import StringDatePickerGroup from './StringDatePickerGroup';
4
+ import FormTestBase from '../__Tests__/FormTestBase';
5
+
6
+ //hack so the datepicker internals don't complain about this not existing in the context of jest
7
+ HTMLCanvasElement.prototype.getContext = () => null;
8
+
9
+ describe('IconInputGroup', () => {
10
+ it('renders without crashing', () => {
11
+ render(
12
+ <FormTestBase>
13
+ {({ Field }) => (
14
+ <Field
15
+ name="favoriteDate"
16
+ Component={StringDatePickerGroup}
17
+ label="Favorite Date"
18
+ monthPlaceholder="mm"
19
+ dayPlaceholder="dd"
20
+ yearPlaceholder="yyyy"
21
+ maxDate={new Date('9/23/2023')}
22
+ minDate={new Date('6/22/2022')}
23
+ />
24
+ )}
25
+ </FormTestBase>
26
+ );
27
+ });
28
+
29
+ it('has matching snapshot', () => {
30
+ const renderResult = render(
31
+ <FormTestBase>
32
+ {({ Field }) => (
33
+ <Field
34
+ name="favoriteDate"
35
+ Component={StringDatePickerGroup}
36
+ label="Favorite Date"
37
+ monthPlaceholder="mm"
38
+ dayPlaceholder="dd"
39
+ yearPlaceholder="yyyy"
40
+ maxDate={new Date('9/23/2023')}
41
+ minDate={new Date('6/22/2022')}
42
+ />
43
+ )}
44
+ </FormTestBase>
45
+ );
46
+ expect(renderResult.asFragment()).toMatchSnapshot();
47
+ });
48
+ });
@@ -1,115 +1,109 @@
1
- import { useEffect, useState } from 'react';
2
- import DatePicker, {
3
- DatePickerProps,
4
- } from 'react-date-picker/dist/entry.nostyle';
5
- import classnames from 'classnames';
6
- import parseISO from 'date-fns/parseISO';
7
- import { InjectedFieldProps } from '../Field/InjectedFieldProps';
8
- import { FormDefaults } from '../FormDefaults';
9
- import Group, { GroupProps } from '../Group';
10
-
11
- export interface DatePickerGroupProps<T>
12
- extends InjectedFieldProps<T | undefined | null>,
13
- Omit<
14
- DatePickerProps,
15
- keyof InjectedFieldProps<T> | 'name' | 'value' | 'className'
16
- >,
17
- Omit<GroupProps, keyof InjectedFieldProps<T> | 'children'> {
18
- convert: (arg: Date) => T;
19
- }
20
-
21
- /**
22
- * Field for inputting dates. Uses `<Group/>` and `<DatePicker/>`.
23
- *
24
- * Uses [react-date-picker](https://www.npmjs.com/package/react-date-picker)
25
- */
26
- export default function DatePickerGroup<T>({
27
- input,
28
- meta,
29
- label,
30
- helpText,
31
- className,
32
- required,
33
- disabled,
34
- convert,
35
- ...rest
36
- }: DatePickerGroupProps<T>) {
37
- const [displayDate, setDisplayDate] = useState<Date | null>(null);
38
-
39
- useEffect(() => {
40
- if (!input.value) {
41
- setDisplayDate(null);
42
- } else if (typeof input.value === 'string') {
43
- if (input.value.indexOf('T') === -1) {
44
- setDisplayDate(new Date(`${input.value}T00:00:00.000`));
45
- } else {
46
- setDisplayDate(new Date(input.value));
47
- }
48
- }
49
- }, [setDisplayDate, input.value]);
50
-
51
- return (
52
- <Group
53
- input={input}
54
- meta={meta}
55
- label={label}
56
- helpText={helpText}
57
- className={classnames(
58
- className,
59
- FormDefaults.cssClassPrefix + 'date-picker'
60
- )}
61
- required={required}
62
- disabled={disabled}>
63
- <DatePicker
64
- {...rest}
65
- className={classnames(
66
- FormDefaults.cssClassPrefix + 'date-picker',
67
- className
68
- )}
69
- value={displayDate}
70
- onChange={handleChange}
71
- />
72
- </Group>
73
- );
74
-
75
- function handleChange(e: Date | Date[] | string | undefined) {
76
- const { onChange } = input;
77
- if (onChange === null) {
78
- return;
79
- }
80
-
81
- if (!e) {
82
- onChange(undefined);
83
- setDisplayDate(null);
84
- return;
85
- } else if (typeof e === 'string') {
86
- const parsedValue = parseISO(e.toString().split('T')[0]);
87
- setDisplayDate(parsedValue);
88
- onChange(convert(parsedValue));
89
- } else if (e instanceof Date) {
90
- const parsedValue = parseISO(
91
- convertToTimeZoneInsensitiveISOString(e).split('T')[0]
92
- );
93
- setDisplayDate(parsedValue);
94
- onChange(convert(parsedValue));
95
- } else {
96
- // e is instanceof Date[]
97
- const parsedValue = parseISO(
98
- convertToTimeZoneInsensitiveISOString(e[0]).split('T')[0]
99
- );
100
- setDisplayDate(parsedValue);
101
- onChange(convert(parsedValue));
102
- }
103
- }
104
- }
105
-
106
- export function convertToTimeZoneInsensitiveISOString(date: Date): string {
107
- const year = new Intl.DateTimeFormat('en', { year: 'numeric' })
108
- .format(date)
109
- .padStart(4, '0');
110
- const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(
111
- date
112
- );
113
- const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
114
- return `${year}-${month}-${day}T00:00:00.000Z`;
115
- }
1
+ import { useEffect, useState } from 'react';
2
+ import DatePicker, {
3
+ DatePickerProps,
4
+ } from 'react-date-picker/dist/entry.nostyle';
5
+ import { clsx } from 'clsx';
6
+ import parseISO from 'date-fns/parseISO';
7
+ import { InjectedFieldProps } from '../Field/InjectedFieldProps';
8
+ import { FormDefaults } from '../FormDefaults';
9
+ import Group, { GroupProps } from '../Group';
10
+
11
+ export interface DatePickerGroupProps<T>
12
+ extends InjectedFieldProps<T | undefined | null>,
13
+ Omit<
14
+ DatePickerProps,
15
+ keyof InjectedFieldProps<T> | 'name' | 'value' | 'className'
16
+ >,
17
+ Omit<GroupProps, keyof InjectedFieldProps<T> | 'children'> {
18
+ convert: (arg: Date) => T;
19
+ }
20
+
21
+ /**
22
+ * Field for inputting dates. Uses `<Group/>` and `<DatePicker/>`.
23
+ *
24
+ * Uses [react-date-picker](https://www.npmjs.com/package/react-date-picker)
25
+ */
26
+ export default function DatePickerGroup<T>({
27
+ input,
28
+ meta,
29
+ label,
30
+ helpText,
31
+ className,
32
+ required,
33
+ disabled,
34
+ convert,
35
+ ...rest
36
+ }: DatePickerGroupProps<T>) {
37
+ const [displayDate, setDisplayDate] = useState<Date | null>(null);
38
+
39
+ useEffect(() => {
40
+ if (!input.value) {
41
+ setDisplayDate(null);
42
+ } else if (typeof input.value === 'string') {
43
+ if (input.value.indexOf('T') === -1) {
44
+ setDisplayDate(new Date(`${input.value}T00:00:00.000`));
45
+ } else {
46
+ setDisplayDate(new Date(input.value));
47
+ }
48
+ }
49
+ }, [setDisplayDate, input.value]);
50
+
51
+ return (
52
+ <Group
53
+ input={input}
54
+ meta={meta}
55
+ label={label}
56
+ helpText={helpText}
57
+ className={clsx(className, FormDefaults.cssClassPrefix + 'date-picker')}
58
+ required={required}
59
+ disabled={disabled}>
60
+ <DatePicker
61
+ {...rest}
62
+ className={clsx(FormDefaults.cssClassPrefix + 'date-picker', className)}
63
+ value={displayDate}
64
+ onChange={handleChange}
65
+ />
66
+ </Group>
67
+ );
68
+
69
+ function handleChange(e: Date | Date[] | string | undefined) {
70
+ const { onChange } = input;
71
+ if (onChange === null) {
72
+ return;
73
+ }
74
+
75
+ if (!e) {
76
+ onChange(undefined);
77
+ setDisplayDate(null);
78
+ return;
79
+ } else if (typeof e === 'string') {
80
+ const parsedValue = parseISO(e.toString().split('T')[0]);
81
+ setDisplayDate(parsedValue);
82
+ onChange(convert(parsedValue));
83
+ } else if (e instanceof Date) {
84
+ const parsedValue = parseISO(
85
+ convertToTimeZoneInsensitiveISOString(e).split('T')[0]
86
+ );
87
+ setDisplayDate(parsedValue);
88
+ onChange(convert(parsedValue));
89
+ } else {
90
+ // e is instanceof Date[]
91
+ const parsedValue = parseISO(
92
+ convertToTimeZoneInsensitiveISOString(e[0]).split('T')[0]
93
+ );
94
+ setDisplayDate(parsedValue);
95
+ onChange(convert(parsedValue));
96
+ }
97
+ }
98
+ }
99
+
100
+ export function convertToTimeZoneInsensitiveISOString(date: Date): string {
101
+ const year = new Intl.DateTimeFormat('en', { year: 'numeric' })
102
+ .format(date)
103
+ .padStart(4, '0');
104
+ const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(
105
+ date
106
+ );
107
+ const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
108
+ return `${year}-${month}-${day}T00:00:00.000Z`;
109
+ }
@@ -1,4 +1,4 @@
1
- import { DatePickerGroupProps } from './DatePickerGroup';
2
-
3
- export interface DatePickerHelper<T>
4
- extends Omit<DatePickerGroupProps<T>, 'convert'> {}
1
+ import { DatePickerGroupProps } from './DatePickerGroup';
2
+
3
+ export interface DatePickerHelper<T>
4
+ extends Omit<DatePickerGroupProps<T>, 'convert'> {}
@@ -1,28 +1,28 @@
1
- import DatePickerGroup from './DatePickerGroup';
2
- import { DatePickerHelper } from './DatePickerHelper';
3
-
4
- export interface StringDateOnlyPickerGroupProps
5
- extends DatePickerHelper<string | undefined | null> {}
6
-
7
- /**
8
- * Date picker input that consumes and outputs as a date only string in ISO format `YYYY-MM-DD`.
9
- *
10
- * Default display to the user is in `MM/DD/YYYY` format.
11
- */
12
- export default function StringDatePickerGroup(
13
- props: StringDateOnlyPickerGroupProps
14
- ) {
15
- return <DatePickerGroup {...props} convert={convertToDateOnly} />;
16
- }
17
-
18
- function convertToDateOnly(arg: Date) {
19
- const year = new Intl.DateTimeFormat('en', { year: 'numeric' })
20
- .format(arg)
21
- .padStart(4, '0');
22
-
23
- const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(arg);
24
-
25
- const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(arg);
26
-
27
- return `${year}-${month}-${day}`;
28
- }
1
+ import DatePickerGroup from './DatePickerGroup';
2
+ import { DatePickerHelper } from './DatePickerHelper';
3
+
4
+ export interface StringDateOnlyPickerGroupProps
5
+ extends DatePickerHelper<string | undefined | null> {}
6
+
7
+ /**
8
+ * Date picker input that consumes and outputs as a date only string in ISO format `YYYY-MM-DD`.
9
+ *
10
+ * Default display to the user is in `MM/DD/YYYY` format.
11
+ */
12
+ export default function StringDatePickerGroup(
13
+ props: StringDateOnlyPickerGroupProps
14
+ ) {
15
+ return <DatePickerGroup {...props} convert={convertToDateOnly} />;
16
+ }
17
+
18
+ function convertToDateOnly(arg: Date) {
19
+ const year = new Intl.DateTimeFormat('en', { year: 'numeric' })
20
+ .format(arg)
21
+ .padStart(4, '0');
22
+
23
+ const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(arg);
24
+
25
+ const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(arg);
26
+
27
+ return `${year}-${month}-${day}`;
28
+ }
@@ -1,20 +1,20 @@
1
- import DatePickerGroup from './DatePickerGroup';
2
- import { DatePickerHelper } from './DatePickerHelper';
3
-
4
- export interface StringDatePickerGroupProps
5
- extends DatePickerHelper<string | undefined | null> {}
6
-
7
- /**
8
- * Date picker input that consumes and outputs as a date only string in ISO format `YYYY-MM-DDTHH:mm:ss.sssZ` or `±YYYYYY-MM-DDTHH:mm:ss.sssZ`
9
- *
10
- * If you need `YYYY-MM-DD` format use `<StringDateOnlyPickerGroup/>`.
11
- */
12
- export default function StringDatePickerGroup(
13
- props: StringDatePickerGroupProps
14
- ) {
15
- return <DatePickerGroup {...props} convert={convertToDateOnly} />;
16
- }
17
-
18
- function convertToDateOnly(arg: Date) {
19
- return arg.toISOString();
20
- }
1
+ import DatePickerGroup from './DatePickerGroup';
2
+ import { DatePickerHelper } from './DatePickerHelper';
3
+
4
+ export interface StringDatePickerGroupProps
5
+ extends DatePickerHelper<string | undefined | null> {}
6
+
7
+ /**
8
+ * Date picker input that consumes and outputs as a date only string in ISO format `YYYY-MM-DDTHH:mm:ss.sssZ` or `±YYYYYY-MM-DDTHH:mm:ss.sssZ`
9
+ *
10
+ * If you need `YYYY-MM-DD` format use `<StringDateOnlyPickerGroup/>`.
11
+ */
12
+ export default function StringDatePickerGroup(
13
+ props: StringDatePickerGroupProps
14
+ ) {
15
+ return <DatePickerGroup {...props} convert={convertToDateOnly} />;
16
+ }
17
+
18
+ function convertToDateOnly(arg: Date) {
19
+ return arg.toISOString();
20
+ }