@evoke-platform/ui-components 1.6.0-testing.12 → 1.6.0-testing.14
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/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +2 -3
- package/dist/published/components/custom/FormField/AddressFieldComponent/AddressFieldComponent.test.js +1 -1
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/FormField.d.ts +3 -2
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
- package/dist/published/components/custom/FormField/Select/Select.test.js +16 -41
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +19 -0
- package/dist/published/components/custom/FormV2/FormRenderer.js +183 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +131 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +88 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +73 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +129 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
- package/dist/published/components/custom/FormV2/components/FormSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormSections.js +104 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +209 -0
- package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
- package/dist/published/components/custom/FormV2/components/types.d.ts +131 -0
- package/dist/published/components/custom/FormV2/components/types.js +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +47 -0
- package/dist/published/components/custom/FormV2/components/utils.js +434 -0
- package/dist/published/components/custom/FormV2/index.d.ts +1 -0
- package/dist/published/components/custom/FormV2/index.js +1 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +2 -2
- package/dist/published/index.js +2 -2
- package/dist/published/stories/FormField.stories.js +2 -1
- package/dist/published/theme/hooks.d.ts +7 -0
- package/dist/published/theme/hooks.js +9 -0
- package/package.json +4 -2
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { Property } from '@evoke-platform/context';
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import 'react-querybuilder/dist/query-builder.css';
|
|
4
3
|
import { EvokeObject } from '../../../types';
|
|
5
|
-
import { Operator, PresetValue, TreeViewObject } from './types';
|
|
4
|
+
import { ObjectProperty, Operator, PresetValue, TreeViewObject } from './types';
|
|
6
5
|
import { ValueEditorProps } from './ValueEditor';
|
|
7
6
|
export type CriteriaInputProps = {
|
|
8
|
-
properties:
|
|
7
|
+
properties: ObjectProperty[];
|
|
9
8
|
setCriteria: (criteria?: Record<string, unknown> | undefined) => void;
|
|
10
9
|
criteria?: Record<string, unknown>;
|
|
11
10
|
originalCriteria?: Record<string, unknown>;
|
package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js
CHANGED
|
@@ -45,7 +45,7 @@ const AddressFieldComponent = (props) => {
|
|
|
45
45
|
setAnchorEl(null);
|
|
46
46
|
};
|
|
47
47
|
return (React.createElement(Box, null,
|
|
48
|
-
!mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: placeholder, InputProps: {
|
|
48
|
+
!mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: readOnly ? undefined : placeholder, InputProps: {
|
|
49
49
|
type: 'search',
|
|
50
50
|
autoComplete: 'off',
|
|
51
51
|
}, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
|
|
@@ -56,12 +56,12 @@ const BooleanSelect = (props) => {
|
|
|
56
56
|
return readOnlyComponent();
|
|
57
57
|
}
|
|
58
58
|
return displayOption === 'dropdown' ? (React.createElement(Autocomplete, { renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, placeholder: placeholder, size: size ?? 'medium' })), value: booleanOptions.find((opt) => opt.value === value) ?? '', onChange: (e, selectedValue) => handleChange(selectedValue.value), isOptionEqualToValue: (option, val) => option?.value === val?.value, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}), sortBy: "NONE", required: strictlyTrue })) : (React.createElement(FormControl, { required: strictlyTrue, error: error, fullWidth: true },
|
|
59
|
-
React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
|
|
59
|
+
React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), sx: { marginLeft: '-8px' }, control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
|
|
60
60
|
alignSelf: 'start',
|
|
61
|
-
} })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx: {
|
|
61
|
+
}, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx: {
|
|
62
62
|
alignSelf: 'start',
|
|
63
63
|
padding: '4px 9px 9px 9px',
|
|
64
|
-
} })) }),
|
|
64
|
+
}, ...(additionalProps ?? {}) })) }),
|
|
65
65
|
error && errorMessage?.length && React.createElement(FormHelperText, { sx: { marginX: 0 } }, errorMessage),
|
|
66
66
|
descriptionComponent()));
|
|
67
67
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DateTimeFormatter } from '@js-joda/core';
|
|
2
|
+
import { omit } from 'lodash';
|
|
2
3
|
import React, { useEffect, useState } from 'react';
|
|
3
4
|
import { InvalidDate, LocalDate, nativeJs } from '../../../../util';
|
|
4
5
|
import { DatePicker, LocalizationProvider, TextField } from '../../../core';
|
|
@@ -36,6 +37,11 @@ const DatePickerSelect = (props) => {
|
|
|
36
37
|
onChange && onChange(property.id, date, property);
|
|
37
38
|
};
|
|
38
39
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: asMonthDayYearFormat(value) } })) : (React.createElement(LocalizationProvider, null,
|
|
39
|
-
React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
40
|
+
React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
41
|
+
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
42
|
+
inputProps: {
|
|
43
|
+
...params.inputProps,
|
|
44
|
+
...(additionalProps?.inputProps ?? {}),
|
|
45
|
+
}, ...omit(additionalProps, ['inputProps']) })) })));
|
|
40
46
|
};
|
|
41
47
|
export default DatePickerSelect;
|
package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
|
|
2
|
+
import { omit } from 'lodash';
|
|
2
3
|
import React, { useEffect, useState } from 'react';
|
|
3
4
|
import { InvalidDate } from '../../../../util';
|
|
4
5
|
import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
|
|
@@ -43,6 +44,11 @@ const DateTimePickerSelect = (props) => {
|
|
|
43
44
|
props.onChange && props.onChange(property.id, date, property);
|
|
44
45
|
};
|
|
45
46
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: formatDateTime(value) } })) : (React.createElement(LocalizationProvider, null,
|
|
46
|
-
React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
47
|
+
React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
48
|
+
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
49
|
+
inputProps: {
|
|
50
|
+
...params.inputProps,
|
|
51
|
+
...(additionalProps?.inputProps ?? {}),
|
|
52
|
+
}, ...omit(additionalProps, ['inputProps']) })) })));
|
|
47
53
|
};
|
|
48
54
|
export default DateTimePickerSelect;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SelectOption } from '@evoke-platform/context';
|
|
2
2
|
import React, { FocusEventHandler, ReactNode } from 'react';
|
|
3
|
+
import { ObjectProperty } from '../../../types';
|
|
3
4
|
import { AutocompleteOption } from '../../core';
|
|
4
5
|
import { Address } from './AddressFieldComponent/addressFieldComponent';
|
|
5
6
|
export type FormFieldProps = {
|
|
6
7
|
id?: string;
|
|
7
|
-
property:
|
|
8
|
+
property: ObjectProperty;
|
|
8
9
|
onChange?: Function;
|
|
9
10
|
onBlur?: FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
|
10
11
|
defaultValue?: unknown;
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js
CHANGED
|
@@ -36,9 +36,12 @@ const InputFieldComponent = (props) => {
|
|
|
36
36
|
setInputValue(selectValue);
|
|
37
37
|
};
|
|
38
38
|
const InputProps = property.type === 'number'
|
|
39
|
-
? {
|
|
39
|
+
? {
|
|
40
|
+
inputComponent: NumericFormat,
|
|
41
|
+
inputProps: { min, max, readOnly, ...(additionalProps?.inputProps ?? {}) },
|
|
42
|
+
}
|
|
40
43
|
: property.type === 'integer'
|
|
41
|
-
? { inputProps: { min, max } }
|
|
44
|
+
? { inputProps: { min, max, ...(additionalProps?.inputProps ?? {}) } }
|
|
42
45
|
: null;
|
|
43
46
|
return property.enum && !readOnly ? (React.createElement(Autocomplete, { id: id,
|
|
44
47
|
// note: this is different between widgets and builder
|
|
@@ -62,7 +65,7 @@ const InputFieldComponent = (props) => {
|
|
|
62
65
|
backgroundColor: '#f4f6f8',
|
|
63
66
|
},
|
|
64
67
|
}),
|
|
65
|
-
}, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
|
|
68
|
+
}, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: readOnly ? undefined : placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
|
|
66
69
|
? {
|
|
67
70
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
68
71
|
border: 'none',
|
|
@@ -11,28 +11,23 @@ describe('Single select', () => {
|
|
|
11
11
|
const choiceProperty = {
|
|
12
12
|
id: 'selectOptions',
|
|
13
13
|
name: 'Select Options',
|
|
14
|
-
type: '
|
|
14
|
+
type: 'choices',
|
|
15
15
|
};
|
|
16
16
|
it('returns selected option', async () => {
|
|
17
17
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
18
18
|
const user = userEvent.setup();
|
|
19
19
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
20
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
20
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: onChangeMock }));
|
|
21
21
|
const input = screen.getByRole('combobox');
|
|
22
22
|
await user.click(input);
|
|
23
23
|
const option2 = await screen.findByRole('option', { name: 'option 2' });
|
|
24
24
|
await user.click(option2);
|
|
25
|
-
expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'option 2', value: 'option 2' }),
|
|
26
|
-
id: 'selectOptions',
|
|
27
|
-
name: 'Select Options',
|
|
28
|
-
type: 'string',
|
|
29
|
-
enum: ['option 1', 'option 2', 'option 3'],
|
|
30
|
-
});
|
|
25
|
+
expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'option 2', value: 'option 2' }), choiceProperty);
|
|
31
26
|
});
|
|
32
27
|
it('displays matching options', async () => {
|
|
33
28
|
const user = userEvent.setup();
|
|
34
29
|
const options = ['option 1', 'option 2', 'something different'];
|
|
35
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
30
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: () => { } }));
|
|
36
31
|
const input = screen.getByRole('combobox');
|
|
37
32
|
await user.type(input, 'option');
|
|
38
33
|
await screen.findByRole('option', { name: 'option 1' });
|
|
@@ -45,7 +40,7 @@ describe('Single select', () => {
|
|
|
45
40
|
{ sortBy: 'DESC', expectedValues: ['option 3', 'option 2', 'option 1'] },
|
|
46
41
|
])('shows options in $sortBy order as dropdown display', async ({ sortBy, expectedValues }) => {
|
|
47
42
|
const options = ['option 2', 'option 1', 'option 3'];
|
|
48
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
43
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'dropdown', sortBy: sortBy, onChange: vi.fn() }));
|
|
49
44
|
const user = userEvent.setup();
|
|
50
45
|
const input = screen.getByRole('combobox');
|
|
51
46
|
await user.click(input);
|
|
@@ -57,7 +52,7 @@ describe('Single select', () => {
|
|
|
57
52
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
58
53
|
const user = userEvent.setup();
|
|
59
54
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
60
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
55
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
|
|
61
56
|
const input = screen.getByRole('combobox');
|
|
62
57
|
await user.click(input);
|
|
63
58
|
// Verify the instruction text for a combobox displays as the sub-header of the combobox.
|
|
@@ -66,12 +61,7 @@ describe('Single select', () => {
|
|
|
66
61
|
// Verify the option to add the custom value is displayed as an available option in the dropdown.
|
|
67
62
|
const customOption = await screen.findByRole('option', { name: 'Add "custom option"' });
|
|
68
63
|
await user.click(customOption);
|
|
69
|
-
expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'Add "custom option"', value: 'custom option' }),
|
|
70
|
-
id: 'selectOptions',
|
|
71
|
-
name: 'Select Options',
|
|
72
|
-
type: 'string',
|
|
73
|
-
enum: ['option 1', 'option 2', 'option 3'],
|
|
74
|
-
});
|
|
64
|
+
expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'Add "custom option"', value: 'custom option' }), choiceProperty);
|
|
75
65
|
});
|
|
76
66
|
});
|
|
77
67
|
describe('Multi select', () => {
|
|
@@ -86,7 +76,7 @@ describe('Multi select', () => {
|
|
|
86
76
|
const user = userEvent.setup();
|
|
87
77
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
88
78
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
89
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
79
|
+
render(React.createElement(Select, { id: "testSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock }));
|
|
90
80
|
const input = screen.getByRole('combobox');
|
|
91
81
|
await user.click(input);
|
|
92
82
|
const option2 = await screen.findByRole('option', { name: 'option 2' });
|
|
@@ -96,18 +86,13 @@ describe('Multi select', () => {
|
|
|
96
86
|
const option3 = await screen.findByRole('option', { name: 'option 3' });
|
|
97
87
|
await user.click(option3);
|
|
98
88
|
expect(onChangeMock).toBeCalledTimes(2);
|
|
99
|
-
expect(onChangeMock).lastCalledWith('multiSelect', ['option 2', 'option 3'],
|
|
100
|
-
id: 'multiSelect',
|
|
101
|
-
name: 'Select Multiple',
|
|
102
|
-
type: 'array',
|
|
103
|
-
enum: ['option 1', 'option 2', 'option 3'],
|
|
104
|
-
});
|
|
89
|
+
expect(onChangeMock).lastCalledWith('multiSelect', ['option 2', 'option 3'], multiChoiceProperty);
|
|
105
90
|
});
|
|
106
91
|
it('allows the user to enter custom values if it is combobox component', async () => {
|
|
107
92
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
108
93
|
const user = userEvent.setup();
|
|
109
94
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
110
|
-
render(React.createElement(Select, { id: "multiSelect", property:
|
|
95
|
+
render(React.createElement(Select, { id: "multiSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
|
|
111
96
|
const input = screen.getByRole('combobox');
|
|
112
97
|
await user.click(input);
|
|
113
98
|
// Verify the instruction text for a combobox displays as the sub-header of the combobox.
|
|
@@ -119,18 +104,13 @@ describe('Multi select', () => {
|
|
|
119
104
|
const customOption2 = await screen.findByRole('option', { name: 'Add "custom option 2"' });
|
|
120
105
|
await user.click(customOption2);
|
|
121
106
|
expect(onChangeMock).toBeCalledTimes(2);
|
|
122
|
-
expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'custom option 2'],
|
|
123
|
-
id: 'multiSelect',
|
|
124
|
-
name: 'Select Multiple',
|
|
125
|
-
type: 'array',
|
|
126
|
-
enum: ['option 1', 'option 2', 'option 3'],
|
|
127
|
-
});
|
|
107
|
+
expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'custom option 2'], multiChoiceProperty);
|
|
128
108
|
});
|
|
129
109
|
it('allows the user to enter custom values in conjunction with the predefined options if it is combobox component', async () => {
|
|
130
110
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
131
111
|
const user = userEvent.setup();
|
|
132
112
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
133
|
-
render(React.createElement(Select, { id: "multiSelect", property:
|
|
113
|
+
render(React.createElement(Select, { id: "multiSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
|
|
134
114
|
const input = screen.getByRole('combobox');
|
|
135
115
|
await user.click(input);
|
|
136
116
|
// Verify the instruction text for a combobox displays as the sub-header of the combobox.
|
|
@@ -142,19 +122,14 @@ describe('Multi select', () => {
|
|
|
142
122
|
const option1 = await screen.findByRole('option', { name: 'option 1' });
|
|
143
123
|
await user.click(option1);
|
|
144
124
|
expect(onChangeMock).toBeCalledTimes(2);
|
|
145
|
-
expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'option 1'],
|
|
146
|
-
id: 'multiSelect',
|
|
147
|
-
name: 'Select Multiple',
|
|
148
|
-
type: 'array',
|
|
149
|
-
enum: ['option 1', 'option 2', 'option 3'],
|
|
150
|
-
});
|
|
125
|
+
expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'option 1'], multiChoiceProperty);
|
|
151
126
|
});
|
|
152
127
|
});
|
|
153
128
|
describe('Radio Single select', () => {
|
|
154
129
|
const choiceProperty = {
|
|
155
130
|
id: 'selectOptions',
|
|
156
131
|
name: 'Select Options',
|
|
157
|
-
type: '
|
|
132
|
+
type: 'choices',
|
|
158
133
|
};
|
|
159
134
|
it('returns selected radio option', async () => {
|
|
160
135
|
const user = userEvent.setup();
|
|
@@ -171,7 +146,7 @@ describe('Radio Single select', () => {
|
|
|
171
146
|
{ sortBy: 'DESC', expectedValues: ['option 3', 'option 2', 'option 1'] },
|
|
172
147
|
])('shows options in $sortBy order as radio display', async ({ sortBy, expectedValues }) => {
|
|
173
148
|
const options = ['option 2', 'option 1', 'option 3'];
|
|
174
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
149
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: sortBy, onChange: vi.fn() }));
|
|
175
150
|
const radioButtons = screen.getAllByRole('radio');
|
|
176
151
|
const radioValues = radioButtons.map((radioButton) => radioButton.value);
|
|
177
152
|
expect(radioValues).toEqual(expectedValues);
|
|
@@ -179,7 +154,7 @@ describe('Radio Single select', () => {
|
|
|
179
154
|
it('renders an "Other" option in the radio group if the component is configured to support a custom value', async () => {
|
|
180
155
|
const onChangeMock = vi.fn((name, value, property) => { });
|
|
181
156
|
const options = ['option 1', 'option 2', 'option 3'];
|
|
182
|
-
render(React.createElement(Select, { id: "testSelect", property:
|
|
157
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock, isCombobox: true }));
|
|
183
158
|
await screen.findByRole('radio', { name: 'Other' });
|
|
184
159
|
});
|
|
185
160
|
it('renders a text field for a custom option if the "Other" option is selected', async () => {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { LocalDateTime } from '@js-joda/core';
|
|
2
2
|
import { TimePicker } from '@mui/x-date-pickers';
|
|
3
|
-
import { isUndefined, padStart } from 'lodash';
|
|
3
|
+
import { isUndefined, omit, padStart } from 'lodash';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
4
5
|
import React, { useEffect, useState } from 'react';
|
|
5
6
|
import { InvalidDate } from '../../../../util';
|
|
6
7
|
import { LocalizationProvider, TextField } from '../../../core';
|
|
8
|
+
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
7
9
|
const TimePickerSelect = (props) => {
|
|
8
10
|
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, placeholder, additionalProps, } = props;
|
|
9
11
|
const values = defaultValue ? defaultValue.split(':') : undefined;
|
|
@@ -39,7 +41,15 @@ const TimePickerSelect = (props) => {
|
|
|
39
41
|
props.onChange && props.onChange(property.id, date, property);
|
|
40
42
|
}
|
|
41
43
|
};
|
|
42
|
-
return (React.createElement(
|
|
43
|
-
|
|
44
|
+
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
45
|
+
...props,
|
|
46
|
+
defaultValue: value instanceof LocalDateTime ? DateTime.fromISO(value.toString()).toFormat('hh:mm a') : '',
|
|
47
|
+
} })) : (React.createElement(LocalizationProvider, null,
|
|
48
|
+
React.createElement(TimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
|
|
49
|
+
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
50
|
+
inputProps: {
|
|
51
|
+
...params.inputProps,
|
|
52
|
+
...(additionalProps?.inputProps ?? {}),
|
|
53
|
+
}, ...omit(additionalProps, ['inputProps']) })), readOnly: readOnly })));
|
|
44
54
|
};
|
|
45
55
|
export default TimePickerSelect;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
|
|
2
|
+
import React, { ComponentType } from 'react';
|
|
3
|
+
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
4
|
+
import { BaseProps, DocumentInstance, SimpleEditorProps } from './components/types';
|
|
5
|
+
export type FormProps = BaseProps & {
|
|
6
|
+
richTextEditor?: ComponentType<SimpleEditorProps>;
|
|
7
|
+
hideButtons?: boolean;
|
|
8
|
+
value?: FieldValues;
|
|
9
|
+
onSubmit?: (data: FieldValues) => void;
|
|
10
|
+
fieldHeight?: 'small' | 'medium';
|
|
11
|
+
stickyFooter?: boolean;
|
|
12
|
+
onCancel?: () => void;
|
|
13
|
+
form: EvokeForm;
|
|
14
|
+
instance?: ObjectInstance | DocumentInstance;
|
|
15
|
+
onChange: (id: string, value: unknown) => void;
|
|
16
|
+
onValidationChange?: (errors: FieldErrors) => void;
|
|
17
|
+
};
|
|
18
|
+
declare function FormRenderer(props: FormProps): React.JSX.Element;
|
|
19
|
+
export default FormRenderer;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { useObject } from '@evoke-platform/context';
|
|
2
|
+
import { isEqual } from 'lodash';
|
|
3
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { useResponsive } from '../../../theme';
|
|
6
|
+
import { Button, Skeleton, Typography } from '../../core';
|
|
7
|
+
import { Box } from '../../layout';
|
|
8
|
+
import ActionButtons from './components/ActionButtons';
|
|
9
|
+
import { FormContext } from './components/FormContext';
|
|
10
|
+
import { RecursiveEntryRenderer } from './components/RecursiveEntryRenderer';
|
|
11
|
+
import { convertDocToParameters, convertPropertiesToParams } from './components/utils';
|
|
12
|
+
import { handleValidation } from './components/ValidationFiles/Validation';
|
|
13
|
+
import ValidationErrorDisplay from './components/ValidationFiles/ValidationErrorDisplay';
|
|
14
|
+
function FormRenderer(props) {
|
|
15
|
+
const { onSubmit, value, fieldHeight, richTextEditor, hideButtons, stickyFooter, onCancel, form, instance, onChange, onValidationChange, } = props;
|
|
16
|
+
const { entries, name: title, objectId, actionId, display } = form;
|
|
17
|
+
const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
|
|
18
|
+
defaultValues: value,
|
|
19
|
+
});
|
|
20
|
+
const hasSections = entries.some((entry) => entry.type === 'sections');
|
|
21
|
+
const isModal = !!document.querySelector('.MuiDialog-container');
|
|
22
|
+
const { isSm, isXs, smallerThan } = useResponsive();
|
|
23
|
+
const isSmallerThanMd = smallerThan('md');
|
|
24
|
+
const objectStore = useObject(objectId);
|
|
25
|
+
const [expandedSections, setExpandedSections] = useState([]);
|
|
26
|
+
const [fetchedOptions, setFetchedOptions] = useState({});
|
|
27
|
+
const [expandAll, setExpandAll] = useState();
|
|
28
|
+
const [action, setAction] = useState();
|
|
29
|
+
const [object, setObject] = useState();
|
|
30
|
+
const [triggerFieldReset, setTriggerFieldReset] = useState(false);
|
|
31
|
+
const updateFetchedOptions = (newData) => {
|
|
32
|
+
setFetchedOptions((prev) => ({
|
|
33
|
+
...prev,
|
|
34
|
+
...newData,
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
function handleExpandAll() {
|
|
38
|
+
setExpandAll(true);
|
|
39
|
+
}
|
|
40
|
+
function handleCollapseAll() {
|
|
41
|
+
setExpandAll(false);
|
|
42
|
+
}
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const object = await objectStore.get({ sanitized: true });
|
|
47
|
+
setObject(object);
|
|
48
|
+
if (actionId) {
|
|
49
|
+
const action = object?.actions?.find((a) => a.id === actionId);
|
|
50
|
+
setAction(action);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('Failed to fetch object or action:', error);
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
}, [objectStore, actionId]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const currentValues = getValues();
|
|
60
|
+
if (value) {
|
|
61
|
+
for (const key of Object.keys(currentValues)) {
|
|
62
|
+
if (!isEqual(currentValues[key], value[key])) {
|
|
63
|
+
setValue(key, value[key], { shouldValidate: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (triggerFieldReset === true) {
|
|
67
|
+
setTriggerFieldReset(false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, [value]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (onValidationChange) {
|
|
73
|
+
onValidationChange(errors);
|
|
74
|
+
}
|
|
75
|
+
}, [errors, onValidationChange]);
|
|
76
|
+
const handleReset = () => {
|
|
77
|
+
if (onCancel) {
|
|
78
|
+
onCancel();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
reset(instance); // clears react-hook-form state back to default values
|
|
82
|
+
}
|
|
83
|
+
setTriggerFieldReset(true);
|
|
84
|
+
};
|
|
85
|
+
const parameters = useMemo(() => {
|
|
86
|
+
if (form.id === 'documentForm') {
|
|
87
|
+
return convertDocToParameters(instance);
|
|
88
|
+
}
|
|
89
|
+
else if (action?.parameters) {
|
|
90
|
+
return action.parameters;
|
|
91
|
+
}
|
|
92
|
+
else if (object) {
|
|
93
|
+
// if forms actionId is synced with object properties
|
|
94
|
+
return convertPropertiesToParams(object);
|
|
95
|
+
}
|
|
96
|
+
}, [form.id, action?.parameters, object, instance]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
handleValidation(entries, register, getValues(), action?.parameters, instance);
|
|
99
|
+
}, []);
|
|
100
|
+
if (entries && parameters && (!actionId || action)) {
|
|
101
|
+
return (React.createElement(React.Fragment, null,
|
|
102
|
+
React.createElement(Box, { sx: {
|
|
103
|
+
paddingX: isSmallerThanMd ? 2 : 3,
|
|
104
|
+
paddingTop: '0px',
|
|
105
|
+
borderBottom: '2px solid #F4F6F8',
|
|
106
|
+
} },
|
|
107
|
+
React.createElement(Box, { sx: {
|
|
108
|
+
display: 'flex',
|
|
109
|
+
justifyContent: 'space-between',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
flexWrap: 'wrap',
|
|
112
|
+
paddingY: isSm || isXs ? 2 : 3,
|
|
113
|
+
} },
|
|
114
|
+
React.createElement(Typography, { sx: {
|
|
115
|
+
fontSize: '20px',
|
|
116
|
+
lineHeight: '30px',
|
|
117
|
+
fontWeight: 700,
|
|
118
|
+
flexGrow: '1',
|
|
119
|
+
} }, title),
|
|
120
|
+
isSmallerThanMd && hasSections && (React.createElement(Box, { sx: {
|
|
121
|
+
display: 'flex',
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
maxHeight: '22px',
|
|
124
|
+
} },
|
|
125
|
+
React.createElement(Button, { variant: "text", size: "small", disableRipple: true, disabled: expandedSections.every((section) => section.expanded === true), sx: {
|
|
126
|
+
color: '#212B36',
|
|
127
|
+
borderRight: '1px solid #e5e8eb',
|
|
128
|
+
borderRadius: '0px',
|
|
129
|
+
'&:hover': {
|
|
130
|
+
backgroundColor: 'transparent',
|
|
131
|
+
},
|
|
132
|
+
fontWeight: 400,
|
|
133
|
+
fontSize: '14px',
|
|
134
|
+
}, onClick: handleExpandAll }, "Expand all"),
|
|
135
|
+
React.createElement(Button, { variant: "text", size: "small", disableRipple: true, disabled: expandedSections.every((section) => section.expanded === false), sx: {
|
|
136
|
+
color: '#212B36',
|
|
137
|
+
'&:hover': {
|
|
138
|
+
backgroundColor: 'transparent',
|
|
139
|
+
},
|
|
140
|
+
fontWeight: 400,
|
|
141
|
+
fontSize: '14px',
|
|
142
|
+
}, onClick: handleCollapseAll }, "Collapse all")))),
|
|
143
|
+
React.createElement(ValidationErrorDisplay, { errors: errors, show: !!(isSubmitted && errors), formId: form.id, title: title })),
|
|
144
|
+
React.createElement(FormContext.Provider, { value: {
|
|
145
|
+
fetchedOptions,
|
|
146
|
+
setFetchedOptions: updateFetchedOptions,
|
|
147
|
+
getValues,
|
|
148
|
+
stickyFooter,
|
|
149
|
+
object,
|
|
150
|
+
} },
|
|
151
|
+
React.createElement(Box, { sx: {
|
|
152
|
+
padding: isModal ? '0px' : isSm || isXs ? 2 : 3,
|
|
153
|
+
paddingBottom: '0px',
|
|
154
|
+
paddingTop: !hasSections ? undefined : '0px',
|
|
155
|
+
} },
|
|
156
|
+
entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { fieldHeight: fieldHeight, key: index, entry: entry, handleChange: onChange, errors: errors, showSubmitError: !!(isSubmitted && errors), instance: instance, richTextEditor: richTextEditor, expandedSections: expandedSections, setExpandedSections: setExpandedSections, expandAll: expandAll, setExpandAll: setExpandAll, triggerFieldReset: triggerFieldReset, parameters: parameters }))),
|
|
157
|
+
!hideButtons && (actionId || form.id === 'documentForm') && onSubmit && (React.createElement(Box, { sx: {
|
|
158
|
+
...(stickyFooter === false ? { position: 'static' } : { position: 'sticky' }),
|
|
159
|
+
bottom: isModal ? -5 : isSmallerThanMd ? 0 : 24,
|
|
160
|
+
zIndex: 1000,
|
|
161
|
+
borderTop: action?.type !== 'delete' ? '1px solid #f4f6f8' : 'none',
|
|
162
|
+
backgroundColor: '#fff',
|
|
163
|
+
paddingY: isSmallerThanMd ? '16px' : '20px',
|
|
164
|
+
paddingX: isSmallerThanMd ? '16px' : '20px',
|
|
165
|
+
display: 'flex',
|
|
166
|
+
justifyContent: isXs ? 'center' : 'flex-end',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
marginX: isSmallerThanMd ? -2 : -3,
|
|
169
|
+
marginBottom: '1px',
|
|
170
|
+
borderRadius: '0px 0px 6px 6px',
|
|
171
|
+
} },
|
|
172
|
+
React.createElement(ActionButtons, { onSubmit: onSubmit, handleSubmit: handleSubmit, isModal: isModal, actionType: action?.type, submitButtonLabel: display?.submitLabel, onReset: handleReset, errors: errors, unregister: unregister, entries: entries, setValue: setValue, formId: form.id, instance: instance })))))));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
return (React.createElement(Box, { p: 2 },
|
|
176
|
+
React.createElement(Skeleton, null),
|
|
177
|
+
React.createElement(Skeleton, null),
|
|
178
|
+
React.createElement(Skeleton, null),
|
|
179
|
+
React.createElement(Skeleton, null),
|
|
180
|
+
React.createElement(Skeleton, null)));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export default FormRenderer;
|