@abgov/jsonforms-components 0.0.1

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 (75) hide show
  1. package/.babelrc +12 -0
  2. package/.eslintrc.json +36 -0
  3. package/.releaserc.json +25 -0
  4. package/README.md +251 -0
  5. package/jest.config.ts +11 -0
  6. package/package.json +17 -0
  7. package/project.json +55 -0
  8. package/rollup.config.js +14 -0
  9. package/src/index.ts +166 -0
  10. package/src/lib/Additional/HelpContent.tsx +95 -0
  11. package/src/lib/Additional/index.ts +1 -0
  12. package/src/lib/Additional/styled-components.ts +27 -0
  13. package/src/lib/Cells/DateCell.tsx +10 -0
  14. package/src/lib/Cells/IntegerCell.tsx +10 -0
  15. package/src/lib/Cells/NumberCell.tsx +10 -0
  16. package/src/lib/Cells/TextCell.tsx +10 -0
  17. package/src/lib/Cells/TimeCell.tsx +10 -0
  18. package/src/lib/Cells/index.tsx +14 -0
  19. package/src/lib/Context/index.tsx +172 -0
  20. package/src/lib/Controls/FileUploader/ContextMenu.tsx +50 -0
  21. package/src/lib/Controls/FileUploader/FileUploaderControl.tsx +131 -0
  22. package/src/lib/Controls/FileUploader/FileUploaderTester.tsx +3 -0
  23. package/src/lib/Controls/FileUploader/index.tsx +2 -0
  24. package/src/lib/Controls/FileUploader/styled-components.tsx +10 -0
  25. package/src/lib/Controls/FormStepper/FormStepperControl.tsx +269 -0
  26. package/src/lib/Controls/FormStepper/FormStepperTester.tsx +22 -0
  27. package/src/lib/Controls/FormStepper/index.tsx +2 -0
  28. package/src/lib/Controls/FormStepper/styled-components.tsx +17 -0
  29. package/src/lib/Controls/Inputs/InputBaseControl.tsx +52 -0
  30. package/src/lib/Controls/Inputs/InputBooleanControl.tsx +67 -0
  31. package/src/lib/Controls/Inputs/InputBooleanRadioControl.tsx +74 -0
  32. package/src/lib/Controls/Inputs/InputDateControl.tsx +90 -0
  33. package/src/lib/Controls/Inputs/InputDateTimeControl.tsx +46 -0
  34. package/src/lib/Controls/Inputs/InputEnum.tsx +74 -0
  35. package/src/lib/Controls/Inputs/InputEnumAutoComplete.tsx +73 -0
  36. package/src/lib/Controls/Inputs/InputEnumRadios.tsx +43 -0
  37. package/src/lib/Controls/Inputs/InputIntegerControl.tsx +63 -0
  38. package/src/lib/Controls/Inputs/InputMultiLineTextControl.tsx +63 -0
  39. package/src/lib/Controls/Inputs/InputNumberControl.tsx +63 -0
  40. package/src/lib/Controls/Inputs/InputTextControl.tsx +62 -0
  41. package/src/lib/Controls/Inputs/InputTimeControl.tsx +46 -0
  42. package/src/lib/Controls/Inputs/index.tsx +13 -0
  43. package/src/lib/Controls/Inputs/inputControl.spec.ts +84 -0
  44. package/src/lib/Controls/Inputs/type.ts +3 -0
  45. package/src/lib/Controls/ObjectArray/DeleteDialog.tsx +49 -0
  46. package/src/lib/Controls/ObjectArray/ObjectArray.tsx +59 -0
  47. package/src/lib/Controls/ObjectArray/ObjectArrayToolBar.tsx +51 -0
  48. package/src/lib/Controls/ObjectArray/ObjectListControl.tsx +368 -0
  49. package/src/lib/Controls/ObjectArray/index.tsx +1 -0
  50. package/src/lib/Controls/ObjectArray/styled-components.tsx +13 -0
  51. package/src/lib/Controls/index.tsx +4 -0
  52. package/src/lib/ErrorHandling/GoAErrorControl.tsx +53 -0
  53. package/src/lib/ErrorHandling/MessageControl.tsx +19 -0
  54. package/src/lib/ErrorHandling/categorizationValidation.spec.ts +98 -0
  55. package/src/lib/ErrorHandling/controlValildation.spec.ts +57 -0
  56. package/src/lib/ErrorHandling/errorCheck.spec.ts +185 -0
  57. package/src/lib/ErrorHandling/errorCheck.tsx +86 -0
  58. package/src/lib/ErrorHandling/layoutValildation.spec.ts +47 -0
  59. package/src/lib/ErrorHandling/otherValildation.spec.ts +74 -0
  60. package/src/lib/ErrorHandling/schemaValidation.ts +115 -0
  61. package/src/lib/common/Grid.tsx +55 -0
  62. package/src/lib/jsonforms-components.module.scss +7 -0
  63. package/src/lib/jsonforms-components.spec.tsx +10 -0
  64. package/src/lib/jsonforms-components.tsx +14 -0
  65. package/src/lib/layouts/GroupControl.tsx +25 -0
  66. package/src/lib/layouts/HorizontalLayoutControl.tsx +30 -0
  67. package/src/lib/layouts/VerticalLayoutControl.tsx +28 -0
  68. package/src/lib/layouts/index.ts +3 -0
  69. package/src/lib/util/layout.tsx +68 -0
  70. package/src/lib/util/schemaUtils.ts +9 -0
  71. package/src/lib/util/stringUtils.ts +98 -0
  72. package/src/lib/util/style-component.ts +8 -0
  73. package/tsconfig.json +20 -0
  74. package/tsconfig.lib.json +19 -0
  75. package/tsconfig.spec.json +20 -0
@@ -0,0 +1,269 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useState, useEffect } from 'react';
3
+ import { GoAFormStepper, GoAFormStep, GoAPages, GoAButton } from '@abgov/react-components-new';
4
+ import {
5
+ Categorization,
6
+ UISchemaElement,
7
+ deriveLabelForUISchemaElement,
8
+ Category,
9
+ StatePropsOfLayout,
10
+ isVisible,
11
+ isEnabled,
12
+ } from '@jsonforms/core';
13
+
14
+ import { TranslateProps, withJsonFormsLayoutProps, withTranslateProps, useJsonForms } from '@jsonforms/react';
15
+ import { AjvProps, withAjvProps } from '@jsonforms/material-renderers';
16
+ import { JsonFormsDispatch } from '@jsonforms/react';
17
+ import { Hidden } from '@mui/material';
18
+ import { Grid, GridItem } from '../../common/Grid';
19
+ import { ReviewItem, ReviewListItem, ReviewListWrapper } from './styled-components';
20
+
21
+ export interface CategorizationStepperLayoutRendererProps extends StatePropsOfLayout, AjvProps, TranslateProps {
22
+ // eslint-disable-next-line
23
+ data: any;
24
+ }
25
+
26
+ export const FormStepper = ({
27
+ uischema,
28
+ data,
29
+ schema,
30
+ // eslint-disable-next-line
31
+ ajv,
32
+ path,
33
+ cells,
34
+ renderers,
35
+ config,
36
+ visible,
37
+ enabled,
38
+ t,
39
+ }: CategorizationStepperLayoutRendererProps) => {
40
+ const categorization = uischema as Categorization;
41
+ const [step, setStep] = useState(0);
42
+ const [isFormValid, setIsFormValid] = useState(false);
43
+ const [showNextBtn, setShowNextBtn] = useState(true);
44
+ const categories = useMemo(
45
+ () => categorization.elements.filter((category) => isVisible(category, data, '', ajv)),
46
+ [categorization, data, ajv]
47
+ );
48
+ const disabledCategoryMap: boolean[] = categories.map((c) => !isEnabled(c, data, '', ajv));
49
+ const handleSubmit = () => {
50
+ console.log('submitted', data);
51
+ };
52
+
53
+ const CategoryLabels = useMemo(() => {
54
+ return categories.map((e: Category | Categorization) => deriveLabelForUISchemaElement(e, t));
55
+ }, [categories, t]);
56
+
57
+ useEffect(() => {}, [categories.length]);
58
+
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ const vslidateFormData = (formData: Array<UISchemaElement>) => {
61
+ const validate = ajv.compile(schema);
62
+ return validate(formData);
63
+ };
64
+
65
+ useEffect(() => {
66
+ const valid = vslidateFormData(data);
67
+ setIsFormValid(valid);
68
+ }, [data, vslidateFormData]);
69
+
70
+ if (categories?.length < 1) {
71
+ // eslint-disable-next-line
72
+ return <></>;
73
+ }
74
+
75
+ function nextPage(page: number, disabled: boolean[]) {
76
+ page++;
77
+ while (page <= disabled.length && disabled[page - 1]) {
78
+ page++;
79
+ }
80
+ setPage(page);
81
+ }
82
+
83
+ function prevPage(page: number, disabled: boolean[]) {
84
+ page--;
85
+ while (page >= 0 && disabled[page - 1]) {
86
+ page--;
87
+ }
88
+ setPage(page);
89
+ }
90
+
91
+ function setPage(page: number) {
92
+ setStep(page);
93
+ if (page < 1 || page > categories.length + 1) return;
94
+ if (categories.length + 1 === page) {
95
+ setShowNextBtn(false);
96
+ } else {
97
+ setShowNextBtn(true);
98
+ }
99
+ }
100
+ // eslint-disable-next-line react-hooks/rules-of-hooks
101
+ useEffect(() => {
102
+ setStep(0);
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ }, []);
105
+ const renderStepElements = (category: Category | Categorization, indexOfCategory: number) => {
106
+ return (
107
+ /*
108
+ [Mar-04-2024][Paul Li] the GoAPages internal state cannot handle the hidden/display well. We need extra hide/display control to it appropriately.
109
+ */
110
+ <Hidden xsUp={indexOfCategory !== step - 1}>
111
+ {category.elements.map((elementUiSchema, index) => {
112
+ return (
113
+ <JsonFormsDispatch
114
+ key={index}
115
+ schema={schema}
116
+ uischema={elementUiSchema}
117
+ renderers={renderers}
118
+ cells={cells}
119
+ path={path}
120
+ visible={visible}
121
+ enabled={enabled && !disabledCategoryMap[indexOfCategory]}
122
+ />
123
+ );
124
+ })}
125
+ </Hidden>
126
+ );
127
+ };
128
+
129
+ return (
130
+ <Hidden xsUp={!visible}>
131
+ <div id={`${path || `goa`}-form-stepper`} className="formStepper">
132
+ <GoAFormStepper
133
+ testId="form-stepper-test"
134
+ step={step}
135
+ onChange={(step) => {
136
+ setPage(step);
137
+ }}
138
+ >
139
+ {categories?.map((category, index) => {
140
+ return (
141
+ <GoAFormStep
142
+ key={`${CategoryLabels[index]}-tab`}
143
+ text={`${CategoryLabels[index]}${disabledCategoryMap[index] ? ' (disabled)' : ''}`}
144
+ status={'incomplete'}
145
+ />
146
+ );
147
+ })}
148
+ <GoAFormStep text="Review" status="incomplete" />
149
+ </GoAFormStepper>
150
+ <GoAPages current={step} mb="xl">
151
+ {categories?.map((category, index) => {
152
+ return (
153
+ <div data-testid={`step_${index}-content`} key={`${CategoryLabels[index]}`}>
154
+ {renderStepElements(category, index)}
155
+ </div>
156
+ );
157
+ })}
158
+ <div>
159
+ <h3 style={{ flex: 1 }}>Summary</h3>
160
+
161
+ <ReviewItem>
162
+ <div style={{ width: '100%' }}>
163
+ {data && Object.keys(data)?.length > 0 && (
164
+ <Grid>
165
+ {Object.keys(flattenObject(data)).map((key, ix) => {
166
+ return (
167
+ <GridItem key={ix} md={6} vSpacing={1} hSpacing={0.5}>
168
+ <b>{key}</b> : <PreventControlElement key={ix} value={flattenObject(data)[key]} />
169
+ </GridItem>
170
+ );
171
+ })}
172
+ </Grid>
173
+ )}
174
+ </div>
175
+ </ReviewItem>
176
+ </div>
177
+ </GoAPages>
178
+ {step && step !== 0 && (
179
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
180
+ {step !== 1 ? (
181
+ <GoAButton
182
+ type="secondary"
183
+ disabled={disabledCategoryMap[step - 1] || !enabled}
184
+ onClick={() => prevPage(step, disabledCategoryMap)}
185
+ >
186
+ Previous
187
+ </GoAButton>
188
+ ) : (
189
+ <div></div>
190
+ )}
191
+ {step !== null && showNextBtn && (
192
+ <GoAButton
193
+ type="primary"
194
+ disabled={disabledCategoryMap[step - 1] || !enabled}
195
+ onClick={() => nextPage(step, disabledCategoryMap)}
196
+ >
197
+ Next
198
+ </GoAButton>
199
+ )}
200
+ {!showNextBtn && (
201
+ <div>
202
+ <GoAButton type="primary" onClick={handleSubmit} disabled={!isFormValid || !enabled}>
203
+ Submit
204
+ </GoAButton>
205
+ </div>
206
+ )}
207
+ </div>
208
+ )}
209
+ </div>
210
+ </Hidden>
211
+ );
212
+ };
213
+
214
+ interface PreventControlElement {
215
+ value: unknown;
216
+ }
217
+
218
+ const PreventControlElement = (props: PreventControlElement): JSX.Element => {
219
+ if (typeof props?.value === 'string') return <span>{props.value}</span>;
220
+
221
+ if (Array.isArray(props?.value)) {
222
+ return (
223
+ <div>
224
+ {props.value.map((item, index) => {
225
+ return (
226
+ <ReviewListWrapper key={index}>
227
+ {item &&
228
+ Object.keys(item).map((key, innerIndex) => {
229
+ if (typeof item[key] === 'string') {
230
+ return (
231
+ <ReviewListItem key={innerIndex}>
232
+ {key}: {item[key]}
233
+ </ReviewListItem>
234
+ );
235
+ }
236
+ return (
237
+ <ReviewListItem key={innerIndex}>
238
+ {key}: {String(item[key])}
239
+ </ReviewListItem>
240
+ );
241
+ })}
242
+ </ReviewListWrapper>
243
+ );
244
+ })}
245
+ </div>
246
+ );
247
+ }
248
+
249
+ // eslint-disable-next-line
250
+ return <></>;
251
+ };
252
+
253
+ export const flattenObject = (obj: Record<string, string>): Record<string, string> => {
254
+ const flattened = {} as Record<string, string>;
255
+
256
+ Object.keys(obj || {}).forEach((key) => {
257
+ const value = obj[key];
258
+
259
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
260
+ Object.assign(flattened, flattenObject(value));
261
+ } else {
262
+ flattened[key] = value;
263
+ }
264
+ });
265
+
266
+ return flattened;
267
+ };
268
+
269
+ export const FormStepperControl = withAjvProps(withTranslateProps(withJsonFormsLayoutProps(FormStepper)));
@@ -0,0 +1,22 @@
1
+ import { rankWith, RankedTester, uiTypeIs, and, optionIs, UISchemaElement, isCategorization } from '@jsonforms/core';
2
+
3
+ // Ensure that all children (Category) have valid elements or things tend
4
+ // to blow up. If not, the the error control will report the problem.
5
+ const categoriesAreValid = (uischema: UISchemaElement): boolean => {
6
+ let isValid = true;
7
+ if ('type' in uischema && uischema.type === 'Categorization' && 'elements' in uischema) {
8
+ (uischema.elements as UISchemaElement[]).forEach((e) => {
9
+ if (e.type !== 'Category' || !('elements' in e)) {
10
+ isValid = false;
11
+ }
12
+ });
13
+ } else {
14
+ return false;
15
+ }
16
+ return isValid;
17
+ };
18
+
19
+ export const CategorizationRendererTester: RankedTester = rankWith(
20
+ 2,
21
+ and(uiTypeIs('Categorization'), categoriesAreValid, optionIs('variant', 'stepper'))
22
+ );
@@ -0,0 +1,2 @@
1
+ export * from './FormStepperControl';
2
+ export * from './FormStepperTester';
@@ -0,0 +1,17 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const ReviewItem = styled.div`
4
+ display: flex;
5
+ border: 1px solid grey;
6
+ border-radius: 5px;
7
+ margin: 5px;
8
+ padding: 10px;
9
+ `;
10
+
11
+ export const ReviewListItem = styled.div`
12
+ margin-left: 1rem;
13
+ `;
14
+
15
+ export const ReviewListWrapper = styled.div`
16
+ margin-bottom: 1rem;
17
+ `;
@@ -0,0 +1,52 @@
1
+ import React, { useContext } from 'react';
2
+ import { GoAFormItem } from '@abgov/react-components-new';
3
+ import { ControlProps } from '@jsonforms/core';
4
+ import { Hidden } from '@mui/material';
5
+ import { checkFieldValidity, getLabelText } from '../../util/stringUtils';
6
+ export type GoAInputType =
7
+ | 'text'
8
+ | 'password'
9
+ | 'email'
10
+ | 'number'
11
+ | 'date'
12
+ | 'datetime-local'
13
+ | 'month'
14
+ | 'range'
15
+ | 'search'
16
+ | 'tel'
17
+ | 'time'
18
+ | 'url'
19
+ | 'week';
20
+
21
+ export interface WithInput {
22
+ //eslint-disable-next-line
23
+ input: any;
24
+ noLabel?: boolean;
25
+ }
26
+
27
+ export const GoAInputBaseControl = (props: ControlProps & WithInput): JSX.Element => {
28
+ // eslint-disable-next-line
29
+ const { id, description, errors, uischema, visible, config, label, input, required } = props;
30
+ const isValid = errors.length === 0;
31
+ const InnerComponent = input;
32
+ const labelToUpdate: string = getLabelText(uischema.scope, label || '');
33
+
34
+ let modifiedErrors = checkFieldValidity(props as ControlProps);
35
+
36
+ if (modifiedErrors === 'should be equal to one of the allowed values' && uischema?.options?.enumContext) {
37
+ modifiedErrors = '';
38
+ }
39
+
40
+ return (
41
+ <Hidden xsUp={!visible}>
42
+ <GoAFormItem
43
+ requirement={required ? 'required' : undefined}
44
+ error={modifiedErrors}
45
+ label={props?.noLabel === true ? '' : labelToUpdate}
46
+ helpText={typeof uischema?.options?.help === 'string' ? uischema?.options?.help : ''}
47
+ >
48
+ <InnerComponent {...props} />
49
+ </GoAFormItem>
50
+ </Hidden>
51
+ );
52
+ };
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import { isBooleanControl, RankedTester, rankWith, ControlProps, isDescriptionHidden } from '@jsonforms/core';
3
+ import { withJsonFormsControlProps } from '@jsonforms/react';
4
+ import { Hidden } from '@mui/material';
5
+ import { GoACheckbox } from '@abgov/react-components-new';
6
+ import { GoAInputBaseControl } from './InputBaseControl';
7
+ import { checkFieldValidity } from '../../util/stringUtils';
8
+
9
+ export const BooleanComponent = ({
10
+ data,
11
+ visible,
12
+ enabled,
13
+ uischema,
14
+ handleChange,
15
+ path,
16
+ config,
17
+ label,
18
+ required,
19
+ errors,
20
+ description,
21
+ }: ControlProps) => {
22
+ const appliedUiSchemaOptions = { ...config, ...uischema.options };
23
+
24
+ const showDescription = !isDescriptionHidden(
25
+ visible,
26
+ description,
27
+ false,
28
+ appliedUiSchemaOptions.showUnfocusedDescription
29
+ );
30
+ const errorsFormInput = checkFieldValidity({
31
+ data,
32
+ uischema,
33
+ label,
34
+ required,
35
+ errors,
36
+ } as ControlProps);
37
+
38
+ let text = label + (required ? ' (required)' : '');
39
+
40
+ if (label && description) {
41
+ text = description;
42
+ if (required) {
43
+ text = `${description} ` + (required ? ' (required)' : '');
44
+ }
45
+ }
46
+
47
+ return (
48
+ <GoACheckbox
49
+ error={errorsFormInput.length > 0}
50
+ testId={`${path}-checkbox-test-id`}
51
+ disabled={!enabled}
52
+ text={text}
53
+ name={`${path}`}
54
+ checked={data}
55
+ onChange={(name: string, checked: boolean, value: string) => {
56
+ handleChange(path, checked);
57
+ }}
58
+ {...uischema?.options?.componentProps}
59
+ ></GoACheckbox>
60
+ );
61
+ };
62
+ export const BooleanControl = (props: ControlProps) => (
63
+ <GoAInputBaseControl {...{ ...props, noLabel: true }} input={BooleanComponent} />
64
+ );
65
+
66
+ export const GoABooleanControlTester: RankedTester = rankWith(2, isBooleanControl);
67
+ export const GoABooleanControl = withJsonFormsControlProps(BooleanControl);
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import { isBooleanControl, RankedTester, rankWith, ControlProps, optionIs, and } from '@jsonforms/core';
3
+ import { withJsonFormsControlProps } from '@jsonforms/react';
4
+ import { Hidden } from '@mui/material';
5
+ import { GoARadioGroup, GoARadioItem, GoAFormItem } from '@abgov/react-components-new';
6
+ import { GoAInputBaseControl } from './InputBaseControl';
7
+ import { checkFieldValidity, getLabelText } from '../../util/stringUtils';
8
+
9
+ export const BooleanRadioComponent = ({
10
+ data,
11
+ visible,
12
+ enabled,
13
+ uischema,
14
+ handleChange,
15
+ path,
16
+ config,
17
+ label,
18
+ required,
19
+ errors,
20
+ description,
21
+ }: ControlProps) => {
22
+ const appliedUiSchemaOptions = { ...config, ...uischema?.options };
23
+ const TrueValue = appliedUiSchemaOptions?.textForTrue || 'Yes';
24
+ const FalseValue = appliedUiSchemaOptions?.textForFalse || 'No';
25
+ const EnableDescription = appliedUiSchemaOptions?.enableDescription === true;
26
+ const TrueDescription = description || appliedUiSchemaOptions?.descriptionForTrue;
27
+ const FalseDescription = description || appliedUiSchemaOptions?.descriptionForFalse;
28
+ const BaseTestId = appliedUiSchemaOptions?.testId || `${path}-boolean-radio-jsonform`;
29
+ const errorsFormInput = checkFieldValidity({
30
+ data,
31
+ uischema,
32
+ label,
33
+ required,
34
+ errors,
35
+ } as ControlProps);
36
+
37
+ return (
38
+ <Hidden xsUp={!visible}>
39
+ <GoARadioGroup
40
+ error={errorsFormInput.length > 0}
41
+ name={`${label}`}
42
+ value={data === true ? TrueValue : data === false ? FalseValue : null}
43
+ disabled={!enabled}
44
+ testId={BaseTestId}
45
+ onChange={(_name, value) => {
46
+ if (value === TrueValue) {
47
+ handleChange(path, true);
48
+ }
49
+ if (value === FalseValue) {
50
+ handleChange(path, false);
51
+ }
52
+ }}
53
+ {...uischema?.options?.componentProps}
54
+ >
55
+ <GoARadioItem
56
+ value={TrueValue}
57
+ testId={`${BaseTestId}-yes-option`}
58
+ description={EnableDescription ? TrueDescription : null}
59
+ />
60
+ <GoARadioItem
61
+ value={FalseValue}
62
+ testId={`${BaseTestId}-no-option`}
63
+ description={EnableDescription ? FalseDescription : null}
64
+ />
65
+ </GoARadioGroup>
66
+ </Hidden>
67
+ );
68
+ };
69
+ export const BooleanRadioControl = (props: ControlProps) => (
70
+ <GoAInputBaseControl {...{ ...props }} input={BooleanRadioComponent} />
71
+ );
72
+
73
+ export const GoABooleanRadioControlTester: RankedTester = rankWith(3, and(isBooleanControl, optionIs('radio', true)));
74
+ export const GoABooleanRadioControl = withJsonFormsControlProps(BooleanRadioControl);
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { CellProps, WithClassname, ControlProps, isDateControl, RankedTester, rankWith } from '@jsonforms/core';
3
+ import { GoAInputDate } from '@abgov/react-components-new';
4
+ import { WithInputProps } from './type';
5
+ import { withJsonFormsControlProps } from '@jsonforms/react';
6
+ import { GoAInputBaseControl } from './InputBaseControl';
7
+ import { checkFieldValidity, getLabelText, isValidDate } from '../../util/stringUtils';
8
+ import { MessageControl } from '../../ErrorHandling/MessageControl';
9
+
10
+ export type GoAInputDateProps = CellProps & WithClassname & WithInputProps;
11
+ export const errMalformedDate = (scope: string, type: string): string => {
12
+ return `${type}-date for variable '${scope}' has an incorrect format.`;
13
+ };
14
+
15
+ const standardizeDate = (date: Date | string): string | undefined => {
16
+ try {
17
+ const stdDate = new Date(date).toISOString().substring(0, 10);
18
+ return stdDate;
19
+ } catch (e) {
20
+ const err = e as Error;
21
+ return undefined;
22
+ }
23
+ };
24
+
25
+ const isValidDateFormat = (date: string): boolean => {
26
+ const standardized = standardizeDate(date);
27
+ return standardized !== undefined;
28
+ };
29
+
30
+ const invalidDateFormat = (scope: string, type: string): JSX.Element => {
31
+ return MessageControl(errMalformedDate(scope, type));
32
+ };
33
+
34
+ const reformatDateProps = (props: object): object => {
35
+ if (props) {
36
+ if ('min' in props && typeof props.min === 'string') {
37
+ props['min'] = standardizeDate(props.min);
38
+ }
39
+ if ('max' in props && typeof props.max === 'string') {
40
+ props['max'] = standardizeDate(props.max as string);
41
+ }
42
+ }
43
+ return props;
44
+ };
45
+
46
+ export const GoADateInput = (props: GoAInputDateProps): JSX.Element => {
47
+ const { data, config, id, enabled, uischema, path, handleChange, label } = props;
48
+ const appliedUiSchemaOptions = { ...config, ...uischema?.options };
49
+
50
+ const minDate = uischema?.options?.componentProps?.min;
51
+ if (minDate && !isValidDateFormat(minDate)) {
52
+ return invalidDateFormat(uischema.scope, 'Min');
53
+ }
54
+
55
+ const maxDate = uischema?.options?.componentProps?.max;
56
+ if (maxDate && !isValidDateFormat(maxDate)) {
57
+ return invalidDateFormat(uischema.scope, 'Max');
58
+ }
59
+
60
+ return (
61
+ <GoAInputDate
62
+ error={checkFieldValidity(props as ControlProps).length > 0}
63
+ width="100%"
64
+ name={appliedUiSchemaOptions?.name || `${id || label}-input`}
65
+ value={standardizeDate(data) || ''}
66
+ testId={appliedUiSchemaOptions?.testId || `${id}-input`}
67
+ disabled={!enabled}
68
+ // Don't use handleChange in the onChange event, use the keyPress or onBlur.
69
+ // If you use it onChange along with keyPress event it will cause a
70
+ // side effect that causes the validation to render when it shouldn't.
71
+ onChange={(name, value) => {}}
72
+ onKeyPress={(name: string, value: Date | string, key: string) => {
73
+ if (!(key === 'Tab' || key === 'Shift')) {
74
+ value = standardizeDate(value) || '';
75
+ handleChange(path, value);
76
+ }
77
+ }}
78
+ onBlur={(name: string, value: Date | string) => {
79
+ value = standardizeDate(value) || '';
80
+ handleChange(path, value);
81
+ }}
82
+ {...reformatDateProps(uischema?.options?.componentProps)}
83
+ />
84
+ );
85
+ };
86
+
87
+ export const GoADateControl = (props: ControlProps) => <GoAInputBaseControl {...props} input={GoADateInput} />;
88
+
89
+ export const GoADateControlTester: RankedTester = rankWith(4, isDateControl);
90
+ export const GoAInputDateControl = withJsonFormsControlProps(GoADateControl);
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { CellProps, WithClassname, ControlProps, isDateTimeControl, RankedTester, rankWith } from '@jsonforms/core';
3
+ import { GoAInputDateTime } from '@abgov/react-components-new';
4
+ import { WithInputProps } from './type';
5
+ import { withJsonFormsControlProps } from '@jsonforms/react';
6
+ import { GoAInputBaseControl } from './InputBaseControl';
7
+ import { checkFieldValidity, isValidDate } from '../../util/stringUtils';
8
+ type GoAInputDateTimeProps = CellProps & WithClassname & WithInputProps;
9
+
10
+ export const GoADateTimeInput = (props: GoAInputDateTimeProps): JSX.Element => {
11
+ // eslint-disable-next-line
12
+ const { data, config, id, enabled, uischema, isValid, path, errors, handleChange, schema, label } = props;
13
+
14
+ const appliedUiSchemaOptions = { ...config, ...uischema?.options };
15
+
16
+ return (
17
+ <GoAInputDateTime
18
+ error={checkFieldValidity(props as ControlProps).length > 0}
19
+ width="100%"
20
+ name={appliedUiSchemaOptions?.name || `${id || label}-input`}
21
+ value={data ? new Date(data).toISOString() : ''}
22
+ testId={appliedUiSchemaOptions?.testId || `${id}-input`}
23
+ disabled={!enabled}
24
+ // Dont use handleChange in the onChange event, use the keyPress or onBlur.
25
+ // If you use it onChange along with keyPress event it will cause a
26
+ // side effect that causes the validation to render when it shouldnt.
27
+ onChange={(name, value) => {}}
28
+ onKeyPress={(name: string, value: string, key: string) => {
29
+ if (!(key === 'Tab' || key === 'Shift')) {
30
+ value = isValidDate(value) ? new Date(value)?.toISOString() : '';
31
+ handleChange(path, value);
32
+ }
33
+ }}
34
+ onBlur={(name: string, value: string) => {
35
+ value = isValidDate(value) ? new Date(value).toISOString() : '';
36
+ handleChange(path, value);
37
+ }}
38
+ {...uischema?.options?.componentProps}
39
+ />
40
+ );
41
+ };
42
+
43
+ export const GoADateTimeControl = (props: ControlProps) => <GoAInputBaseControl {...props} input={GoADateTimeInput} />;
44
+
45
+ export const GoADateTimeControlTester: RankedTester = rankWith(2, isDateTimeControl);
46
+ export const GoAInputDateTimeControl = withJsonFormsControlProps(GoADateTimeControl);