@evoke-platform/ui-components 1.1.0-testing.1 → 1.1.0-testing.10
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/core/DateTimePicker/DateTimePicker.js +7 -1
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +4 -5
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +37 -29
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +4 -10
- package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +39 -14
- package/dist/published/components/custom/Form/Common/Form.d.ts +7 -1
- package/dist/published/components/custom/Form/Common/Form.js +112 -94
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.d.ts +5 -0
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +15 -4
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +1 -0
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +7 -3
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableField.js +6 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ManyToMany/DropdownRepeatableFieldInput.js +1 -0
- package/dist/published/components/custom/Form/index.d.ts +2 -1
- package/dist/published/components/custom/Form/utils.js +21 -4
- package/dist/published/components/custom/FormField/FormField.d.ts +2 -0
- package/dist/published/components/custom/FormField/FormField.js +3 -1
- package/dist/published/components/custom/FormField/Select/Select.js +28 -4
- package/dist/published/components/custom/FormField/Select/Select.test.js +41 -0
- package/dist/published/components/custom/OverflowTextField/OverflowTextField.d.ts +4 -0
- package/dist/published/components/custom/OverflowTextField/OverflowTextField.js +13 -0
- package/dist/published/components/custom/OverflowTextField/index.d.ts +2 -0
- package/dist/published/components/custom/OverflowTextField/index.js +2 -0
- package/dist/published/components/custom/index.d.ts +3 -2
- package/dist/published/components/custom/index.js +2 -2
- package/dist/published/index.d.ts +6 -3
- package/dist/published/index.js +4 -2
- package/dist/published/stories/FormField.stories.js +2 -0
- package/dist/published/stories/OverflowTextField.stories.d.ts +5 -0
- package/dist/published/stories/OverflowTextField.stories.js +28 -0
- package/package.json +2 -8
@@ -24,6 +24,12 @@ const DateTimePicker = (props) => {
|
|
24
24
|
handleChange(newValue, keyboardInputValue);
|
25
25
|
};
|
26
26
|
return (React.createElement(UIThemeProvider, null,
|
27
|
-
React.createElement(MUIDateTimePicker, { value: value, onChange: onChange, renderInput: (params) => React.createElement(TextField, { ...params }),
|
27
|
+
React.createElement(MUIDateTimePicker, { value: value, onChange: onChange, renderInput: (params) => React.createElement(TextField, { ...params }), PaperProps: {
|
28
|
+
sx: {
|
29
|
+
'&.MuiPickersPopper-paper': {
|
30
|
+
borderRadius: '12px',
|
31
|
+
},
|
32
|
+
},
|
33
|
+
}, ...rest })));
|
28
34
|
};
|
29
35
|
export default DateTimePicker;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
/// <reference types="react" />
|
2
2
|
import 'react-querybuilder/dist/query-builder.css';
|
3
3
|
import { EvokeObject } from '../../../types';
|
4
4
|
import { ObjectProperty, Operator, PresetValue, TreeViewObject } from './types';
|
@@ -10,10 +10,9 @@ export type CriteriaInputProps = {
|
|
10
10
|
originalCriteria?: Record<string, unknown>;
|
11
11
|
enablePresetValues?: boolean;
|
12
12
|
presetValues?: PresetValue[];
|
13
|
-
|
14
|
-
component:
|
15
|
-
|
16
|
-
trigger?: Record<string, unknown>;
|
13
|
+
customValueEditor?: {
|
14
|
+
component: (props: ValueEditorProps) => JSX.Element;
|
15
|
+
props?: Record<string, unknown>;
|
17
16
|
};
|
18
17
|
operators?: Operator[];
|
19
18
|
disabledCriteria?: {
|
@@ -7,8 +7,9 @@ import { QueryBuilder, RuleGroupBodyComponents, RuleGroupHeaderComponents, TestI
|
|
7
7
|
import 'react-querybuilder/dist/query-builder.css';
|
8
8
|
import escape from 'string-escape-regex';
|
9
9
|
import { TrashCan } from '../../../icons/custom';
|
10
|
-
import { Autocomplete, Button, IconButton
|
10
|
+
import { Autocomplete, Button, IconButton } from '../../core';
|
11
11
|
import { Box } from '../../layout';
|
12
|
+
import { OverflowTextField } from '../OverflowTextField';
|
12
13
|
import { difference } from '../util';
|
13
14
|
import PropertyTree from './PropertyTree';
|
14
15
|
import { parseMongoDB, traversePropertyPath } from './utils';
|
@@ -32,7 +33,6 @@ const styles = {
|
|
32
33
|
buttons: {
|
33
34
|
padding: '6px 16px',
|
34
35
|
fontSize: '0.875rem',
|
35
|
-
marginRight: '0px',
|
36
36
|
boxShadow: 'none',
|
37
37
|
},
|
38
38
|
};
|
@@ -198,7 +198,7 @@ const customSelector = (props) => {
|
|
198
198
|
};
|
199
199
|
return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, handleTreePropertySelect: handleTreePropertySelect })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
|
200
200
|
if (typeof option === 'string') {
|
201
|
-
return opts.find((o) => option === o.name)?.label ||
|
201
|
+
return opts.find((o) => option === o.name)?.label || option;
|
202
202
|
}
|
203
203
|
return option.label;
|
204
204
|
}, isOptionEqualToValue: (option, value) => {
|
@@ -212,7 +212,10 @@ const customSelector = (props) => {
|
|
212
212
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
213
213
|
onChange: (event, newValue) => {
|
214
214
|
handleOnChange(newValue?.value.name);
|
215
|
-
}, renderInput: (params) => React.createElement(
|
215
|
+
}, renderInput: (params) => (React.createElement(OverflowTextField, { value: opts.find((o) => value === o.name)?.label || '', ...params, placeholder: placeholder, size: "small", inputProps: {
|
216
|
+
...params.inputProps,
|
217
|
+
'aria-label': placeholder,
|
218
|
+
} })), sx: { width: width, background: readOnly ? '#f4f6f8' : '#fff' }, disableClearable: true, readOnly: readOnly }))));
|
216
219
|
};
|
217
220
|
const customCombinator = (props) => {
|
218
221
|
const { value, handleOnChange, context, level, path } = props;
|
@@ -277,7 +280,7 @@ export const valueEditor = (props) => {
|
|
277
280
|
return ValueEditor(props);
|
278
281
|
};
|
279
282
|
const CriteriaBuilder = (props) => {
|
280
|
-
const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators,
|
283
|
+
const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, disabled, disabledCriteria, hideBorder, presetGroupLabel, customValueEditor, treeViewOpts, disableRegexEscapeChars, } = props;
|
281
284
|
const [query, setQuery] = useState(undefined);
|
282
285
|
const [propertyTreeMap, setPropertyTreeMap] = useState();
|
283
286
|
useEffect(() => {
|
@@ -287,33 +290,41 @@ const CriteriaBuilder = (props) => {
|
|
287
290
|
!isEmpty(treeViewOpts) && updatePropertyTreeMap(updatedQuery);
|
288
291
|
setQuery({
|
289
292
|
...updatedQuery,
|
290
|
-
rules: processRules(updatedQuery.rules),
|
293
|
+
rules: processRules(updatedQuery.rules, true),
|
291
294
|
});
|
292
295
|
}
|
293
296
|
else {
|
294
297
|
setQuery({ combinator: 'and', rules: [] });
|
295
298
|
}
|
296
299
|
}, [originalCriteria]);
|
297
|
-
|
300
|
+
const processRules = (rules, isSavedValue) => {
|
298
301
|
return rules.map((rule) => {
|
299
302
|
if ('rules' in rule) {
|
300
303
|
return {
|
301
304
|
...rule,
|
302
|
-
rules: processRules(rule.rules),
|
305
|
+
rules: processRules(rule.rules, isSavedValue),
|
303
306
|
};
|
304
307
|
}
|
305
308
|
else {
|
306
309
|
const propertyType = properties.find((property) => property.id === rule.field)?.type;
|
310
|
+
let adjustedValue = rule.value;
|
311
|
+
if ((propertyType === 'array' ||
|
312
|
+
((propertyType === 'string' || propertyType === 'richText') &&
|
313
|
+
(rule.operator === 'in' || rule.operator === 'notIn'))) &&
|
314
|
+
isSavedValue) {
|
315
|
+
adjustedValue = rule.value?.split(',');
|
316
|
+
}
|
317
|
+
else if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
|
318
|
+
adjustedValue = null;
|
319
|
+
}
|
307
320
|
return {
|
308
321
|
...rule,
|
309
|
-
|
310
|
-
|
311
|
-
? rule.value?.split(',')
|
312
|
-
: rule.value,
|
322
|
+
operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
|
323
|
+
value: adjustedValue,
|
313
324
|
};
|
314
325
|
}
|
315
326
|
});
|
316
|
-
}
|
327
|
+
};
|
317
328
|
// this retrieves the properties from a treeview for each property in the query
|
318
329
|
// they are then used in the custom query builder components to determine the input type etc
|
319
330
|
const updatePropertyTreeMap = (q) => {
|
@@ -358,8 +369,12 @@ const CriteriaBuilder = (props) => {
|
|
358
369
|
}
|
359
370
|
};
|
360
371
|
const handleQueryChange = (q) => {
|
361
|
-
|
362
|
-
|
372
|
+
const processedQuery = {
|
373
|
+
...q,
|
374
|
+
rules: processRules(q.rules, false),
|
375
|
+
};
|
376
|
+
setQuery(processedQuery);
|
377
|
+
const newCriteria = JSON.parse(formatQuery(processedQuery, {
|
363
378
|
format: 'mongodb',
|
364
379
|
ruleProcessor: (rule, options) => {
|
365
380
|
let newRule = rule;
|
@@ -375,16 +390,12 @@ const CriteriaBuilder = (props) => {
|
|
375
390
|
return defaultRuleProcessorMongoDB(newRule, options);
|
376
391
|
},
|
377
392
|
}));
|
378
|
-
|
379
|
-
|
380
|
-
// since the Add Condition / Add Group buttons add rules with all the fields empty,
|
381
|
-
// we need to check if the first rule was added because q will still parse to { $and: [{ $expr: true }] }
|
382
|
-
const firstRuleAdded = isEmpty(criteria) && q.rules.length > 0;
|
383
|
-
if (allRulesDeleted && !firstRuleAdded) {
|
384
|
-
setCriteria(undefined);
|
393
|
+
if (!isEmpty(difference(newCriteria, { $and: [{ $expr: true }] }))) {
|
394
|
+
setCriteria(newCriteria);
|
385
395
|
}
|
386
396
|
else {
|
387
|
-
|
397
|
+
if (q.rules.length === 0)
|
398
|
+
setCriteria(undefined);
|
388
399
|
}
|
389
400
|
};
|
390
401
|
const fields = useMemo(() => {
|
@@ -428,7 +439,6 @@ const CriteriaBuilder = (props) => {
|
|
428
439
|
borderStyle: 'hidden',
|
429
440
|
background: '#fff',
|
430
441
|
maxWidth: '70vw',
|
431
|
-
margin: 'auto',
|
432
442
|
},
|
433
443
|
'.ruleGroup-header': {
|
434
444
|
display: 'block',
|
@@ -487,9 +497,9 @@ const CriteriaBuilder = (props) => {
|
|
487
497
|
ruleGroup: CustomRuleGroup,
|
488
498
|
removeGroupAction: customDelete,
|
489
499
|
removeRuleAction: customDelete,
|
490
|
-
valueEditor: valueEditor,
|
500
|
+
valueEditor: customValueEditor ? customValueEditor.component : valueEditor,
|
491
501
|
}, context: {
|
492
|
-
|
502
|
+
...(customValueEditor?.props ?? {}),
|
493
503
|
presetValues,
|
494
504
|
enablePresetValues,
|
495
505
|
presetGroupLabel,
|
@@ -516,8 +526,6 @@ const CriteriaBuilder = (props) => {
|
|
516
526
|
alignItems: 'center',
|
517
527
|
marginBottom: '10px',
|
518
528
|
maxWidth: '71vw',
|
519
|
-
marginLeft: 'auto',
|
520
|
-
marginRight: 'auto',
|
521
529
|
} },
|
522
530
|
React.createElement(Box, null,
|
523
531
|
React.createElement(Button, { sx: {
|
@@ -547,7 +555,7 @@ const CriteriaBuilder = (props) => {
|
|
547
555
|
backgroundColor: 'transparent',
|
548
556
|
},
|
549
557
|
...styles.buttons,
|
550
|
-
}, onClick: handleClearAll, title: "Clear all conditions", disabled: isEmpty(query.rules) }, "Clear
|
558
|
+
}, onClick: handleClearAll, title: "Clear all conditions", disabled: isEmpty(query.rules) }, "Clear all"))));
|
551
559
|
}
|
552
560
|
return React.createElement(React.Fragment, null);
|
553
561
|
};
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { ChevronRight, ExpandMore } from '@mui/icons-material';
|
2
2
|
import React, { useEffect, useState } from 'react';
|
3
|
-
import { Autocomplete, MenuItem,
|
3
|
+
import { Autocomplete, MenuItem, TreeView } from '../../core';
|
4
|
+
import { OverflowTextField } from '../OverflowTextField';
|
4
5
|
import PropertyTreeItem from './PropertyTreeItem';
|
5
6
|
import { fetchDisplayNamePath, findPropertyById, setIdPaths, truncateNamePath, updateTreeNode } from './utils';
|
6
7
|
const PropertyTree = ({ fetchObject, handleTreePropertySelect, rootObject, value }) => {
|
@@ -126,15 +127,8 @@ const PropertyTree = ({ fetchObject, handleTreePropertySelect, rootObject, value
|
|
126
127
|
: objectPropertyNamePathMap[option.value] ?? '';
|
127
128
|
return truncateNamePath(namePath, NAME_PATH_LIMIT);
|
128
129
|
}, renderInput: (params) => {
|
129
|
-
|
130
|
-
|
131
|
-
let displayValue = fullDisplayValue;
|
132
|
-
if (!!displayValue && displayValue.length > NAME_PATH_LIMIT) {
|
133
|
-
isTruncated = true;
|
134
|
-
displayValue = truncateNamePath(displayValue, NAME_PATH_LIMIT);
|
135
|
-
}
|
136
|
-
return (React.createElement(Tooltip, { title: isTruncated ? fullDisplayValue : '', arrow: true },
|
137
|
-
React.createElement(TextField, { ...params, "aria-label": fullDisplayValue, value: displayValue, size: "small", placeholder: "Select a property", variant: "outlined" })));
|
130
|
+
const fullDisplayName = value && objectPropertyNamePathMap[value];
|
131
|
+
return (React.createElement(OverflowTextField, { ...params, "aria-label": fullDisplayName, value: fullDisplayName, size: "small", placeholder: "Select a property", variant: "outlined" }));
|
138
132
|
}, isOptionEqualToValue: (option, val) => {
|
139
133
|
if (typeof val === 'string') {
|
140
134
|
return option.value === val;
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import { Instant, LocalDate, LocalDateTime, LocalTime, ZoneId } from '@js-joda/core';
|
2
2
|
import { ClearRounded } from '@mui/icons-material';
|
3
3
|
import { Box, darken, lighten, styled } from '@mui/material';
|
4
|
-
import {
|
5
|
-
import React, { useRef, useState } from 'react';
|
6
|
-
import { Autocomplete, Chip, DatePicker, LocalizationProvider, Menu, MenuItem, TextField, Typography, } from '../../core';
|
4
|
+
import { TimePicker } from '@mui/x-date-pickers';
|
5
|
+
import React, { useEffect, useRef, useState } from 'react';
|
6
|
+
import { Autocomplete, Chip, DatePicker, DateTimePicker, LocalizationProvider, Menu, MenuItem, TextField, Typography, } from '../../core';
|
7
7
|
import { NumericFormat } from '../FormField/InputFieldComponent';
|
8
8
|
const GroupHeader = styled('div')(({ theme }) => ({
|
9
9
|
position: 'sticky',
|
@@ -18,7 +18,7 @@ const GroupHeader = styled('div')(({ theme }) => ({
|
|
18
18
|
}));
|
19
19
|
const GroupItems = styled('ul')({ padding: 0 });
|
20
20
|
const ValueEditor = (props) => {
|
21
|
-
const { handleOnChange, value, operator, context, level, rule } = props;
|
21
|
+
const { handleOnChange, value, operator, context, level, rule, fieldData } = props;
|
22
22
|
let inputType = props.inputType;
|
23
23
|
let values = props.values;
|
24
24
|
const property = context.propertyTreeMap?.[rule.field];
|
@@ -47,6 +47,20 @@ const ValueEditor = (props) => {
|
|
47
47
|
readOnly =
|
48
48
|
Object.entries(context.disabledCriteria.criteria).some(([key, value]) => key === rule.field && value === rule.value && rule.operator === '=') && level === context.disabledCriteria.level;
|
49
49
|
}
|
50
|
+
const styles = {
|
51
|
+
input: {
|
52
|
+
width: '33%',
|
53
|
+
background: readOnly ? '#f4f6f8' : '#fff',
|
54
|
+
},
|
55
|
+
};
|
56
|
+
useEffect(() => {
|
57
|
+
if (!['in', 'notIn'].includes(operator) && Array.isArray(value)) {
|
58
|
+
handleOnChange('');
|
59
|
+
}
|
60
|
+
else if (['in', 'notIn'].includes(operator) && !Array.isArray(value)) {
|
61
|
+
handleOnChange([]);
|
62
|
+
}
|
63
|
+
}, [operator]);
|
50
64
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
51
65
|
const onClick = (e) => {
|
52
66
|
// if property is date and date picker is open, don't open preset values
|
@@ -85,11 +99,11 @@ const ValueEditor = (props) => {
|
|
85
99
|
if (inputType === 'date') {
|
86
100
|
// date editor
|
87
101
|
return (React.createElement(LocalizationProvider, null,
|
88
|
-
React.createElement(DatePicker, { inputRef: inputRef, disabled: disabled, value: disabled ? null : value, onChange: handleOnChange, onClose: onClose, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx:
|
102
|
+
React.createElement(DatePicker, { inputRef: inputRef, disabled: disabled, value: disabled ? null : value, onChange: handleOnChange, onClose: onClose, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: styles.input })), readOnly: readOnly })));
|
89
103
|
}
|
90
104
|
else if (inputType === 'time') {
|
91
105
|
return (React.createElement(LocalizationProvider, null,
|
92
|
-
React.createElement(TimePicker, { inputRef: inputRef, disabled: disabled, value: disabled || !value ? null : value, onChange: handleOnChange, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx:
|
106
|
+
React.createElement(TimePicker, { inputRef: inputRef, disabled: disabled, value: disabled || !value ? null : value, onChange: handleOnChange, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: styles.input })), readOnly: readOnly })));
|
93
107
|
}
|
94
108
|
else if (inputType === 'date-time') {
|
95
109
|
const dateTimeValue = parseISOStringToLocalDateTime(value);
|
@@ -110,7 +124,7 @@ const ValueEditor = (props) => {
|
|
110
124
|
handleOnChange(new Date(localDateTime.toString()).toISOString());
|
111
125
|
}, onClose: onClose, PopperProps: {
|
112
126
|
anchorEl,
|
113
|
-
}, renderInput: (params) => (React.createElement(Box, { sx:
|
127
|
+
}, renderInput: (params) => (React.createElement(Box, { sx: styles.input, ref: setAnchorEl },
|
114
128
|
React.createElement(TextField, { ...params, disabled: disabled, onClick: onClick, placeholder: "Value", size: "small", inputRef: inputRef }))), readOnly: readOnly })));
|
115
129
|
}
|
116
130
|
else if (inputType === 'number' || inputType === 'integer') {
|
@@ -125,7 +139,7 @@ const ValueEditor = (props) => {
|
|
125
139
|
handleOnChange(uniqueSelections.length ? uniqueSelections : '');
|
126
140
|
},
|
127
141
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
128
|
-
isOptionEqualToValue: (option, value) => option === value, renderInput: (params) => (React.createElement(TextField, { label: params.label, ...params, size: "small"
|
142
|
+
isOptionEqualToValue: (option, value) => option === value, renderInput: (params) => (React.createElement(TextField, { label: params.label, ...params, size: "small" })), groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sx: styles.input, readOnly: readOnly }));
|
129
143
|
}
|
130
144
|
else {
|
131
145
|
return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => {
|
@@ -137,7 +151,7 @@ const ValueEditor = (props) => {
|
|
137
151
|
}
|
138
152
|
}, ...(inputType === 'number'
|
139
153
|
? { InputProps: { inputComponent: NumericFormat } }
|
140
|
-
: { type: 'number' }), placeholder: "Value", size: "small", onClick: onClick, sx:
|
154
|
+
: { type: 'number' }), placeholder: "Value", size: "small", onClick: onClick, sx: styles.input, readOnly: readOnly }));
|
141
155
|
}
|
142
156
|
}
|
143
157
|
else {
|
@@ -147,7 +161,7 @@ const ValueEditor = (props) => {
|
|
147
161
|
...(presetValues?.sort((a, b) => a.label.localeCompare(b.label)) ?? []),
|
148
162
|
];
|
149
163
|
if (isMultiple || values?.length) {
|
150
|
-
return (React.createElement(Autocomplete, { freeSolo: inputType !== 'array' &&
|
164
|
+
return (React.createElement(Autocomplete, { freeSolo: inputType !== 'array' && fieldData.valueEditorType !== 'select', multiple: isMultiple, options: options, value: isMultiple ? (Array.isArray(value) ? value : []) : Array.isArray(value) ? '' : value,
|
151
165
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
152
166
|
onChange: (event, newValue) => {
|
153
167
|
let value;
|
@@ -161,14 +175,25 @@ const ValueEditor = (props) => {
|
|
161
175
|
}
|
162
176
|
handleOnChange(value);
|
163
177
|
}, onBlur: () => {
|
164
|
-
if (inputValue &&
|
178
|
+
if (inputValue &&
|
179
|
+
(options.some((option) => option.name === inputValue) || !options.length) &&
|
180
|
+
(operator === 'in' || operator === 'notIn')) {
|
181
|
+
const newValues = Array.isArray(value) ? [...value, inputValue] : [inputValue];
|
182
|
+
handleOnChange(Array.from(new Set(newValues)));
|
183
|
+
setInputValue('');
|
184
|
+
}
|
185
|
+
}, onKeyDown: (event) => {
|
186
|
+
if (event.key === 'Enter' &&
|
187
|
+
inputValue &&
|
188
|
+
(options.some((option) => option.name === inputValue) || !options.length) &&
|
189
|
+
(operator === 'in' || operator === 'notIn')) {
|
165
190
|
const newValues = Array.isArray(value) ? [...value, inputValue] : [inputValue];
|
166
191
|
handleOnChange(Array.from(new Set(newValues)));
|
167
192
|
setInputValue('');
|
168
193
|
}
|
169
194
|
}, onInputChange: (event, newInputValue) => {
|
170
195
|
setInputValue(newInputValue);
|
171
|
-
}, inputValue: inputValue, renderInput: (params) => (React.createElement(TextField, { inputRef: inputRef, label: params?.label, ...params, size: "small"
|
196
|
+
}, inputValue: inputValue, renderInput: (params) => (React.createElement(TextField, { inputRef: inputRef, label: params?.label, ...params, size: "small" })),
|
172
197
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
173
198
|
getOptionLabel: (option) => {
|
174
199
|
if (typeof option === 'string') {
|
@@ -185,10 +210,10 @@ const ValueEditor = (props) => {
|
|
185
210
|
else {
|
186
211
|
return option?.label === value?.label;
|
187
212
|
}
|
188
|
-
}, groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sortBy: "NONE", sx:
|
213
|
+
}, groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sortBy: "NONE", sx: styles.input, readOnly: readOnly }));
|
189
214
|
}
|
190
215
|
else {
|
191
|
-
return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => handleOnChange(e.target.value), onClick: onClick, placeholder: "Value", size: "small", sx:
|
216
|
+
return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => handleOnChange(e.target.value), onClick: onClick, placeholder: "Value", size: "small", sx: styles.input, readOnly: readOnly }));
|
192
217
|
}
|
193
218
|
}
|
194
219
|
};
|
@@ -1,6 +1,6 @@
|
|
1
|
-
/// <reference types="react" />
|
2
1
|
import { ApiServices, Obj, ObjectInstance, UserAccount } from '@evoke-platform/context';
|
3
2
|
import { ReactComponent } from '@formio/react';
|
3
|
+
import React from 'react';
|
4
4
|
import '../../../../styles/form-component.css';
|
5
5
|
import { Document, ObjectPropertyInputProps } from '../types';
|
6
6
|
type OnSaveResponse = {
|
@@ -34,6 +34,12 @@ export type FormProps = {
|
|
34
34
|
queryAddresses?: unknown;
|
35
35
|
fieldHeight?: 'small' | 'medium';
|
36
36
|
richTextEditor?: typeof ReactComponent;
|
37
|
+
hideButtons?: boolean;
|
38
|
+
formRef?: React.MutableRefObject<FormRef | undefined>;
|
39
|
+
};
|
40
|
+
export type FormRef = {
|
41
|
+
submit: (submission: Record<string, unknown>, type: 'submit' | 'draft', setError: (error?: Record<string, unknown>) => void, setSubmitting?: (value: boolean) => void) => void;
|
42
|
+
data?: Record<string, unknown>;
|
37
43
|
};
|
38
44
|
export declare function Form(props: FormProps): JSX.Element;
|
39
45
|
export default Form;
|
@@ -17,8 +17,9 @@ const usePrevious = (value) => {
|
|
17
17
|
};
|
18
18
|
Utils.Evaluator.noeval = true;
|
19
19
|
export function Form(props) {
|
20
|
-
const { clearable, closeModal, onSave, submitButtonLabel, instance, object, objectInputCommonProps, actionId, actionType, associatedObject, onAutoSave, apiServices, navigateTo, document, queryAddresses, user, isReadOnly, fieldHeight, richTextEditor, } = props;
|
20
|
+
const { clearable, closeModal, onSave, submitButtonLabel, instance, object, objectInputCommonProps, actionId, actionType, associatedObject, onAutoSave, apiServices, navigateTo, document, queryAddresses, user, isReadOnly, fieldHeight, richTextEditor, hideButtons, formRef, } = props;
|
21
21
|
const [formKey, setFormKey] = useState();
|
22
|
+
const [formData, setFormData] = useState();
|
22
23
|
const [componentProps, setComponentProps] = useState([]);
|
23
24
|
const [snackbarError, setSnackbarError] = useState();
|
24
25
|
const prevFormKey = usePrevious(formKey);
|
@@ -70,6 +71,14 @@ export function Form(props) {
|
|
70
71
|
});
|
71
72
|
buildComponents();
|
72
73
|
};
|
74
|
+
useEffect(() => {
|
75
|
+
if (formRef) {
|
76
|
+
formRef.current = {
|
77
|
+
submit: saveHandler,
|
78
|
+
data: formData,
|
79
|
+
};
|
80
|
+
}
|
81
|
+
}, [formRef, formData]);
|
73
82
|
useEffect(() => {
|
74
83
|
// TODO: Check why formKey is not being set for create actions
|
75
84
|
if (((actionType === 'update' || actionType === 'delete') && !!instance) || !!document) {
|
@@ -130,112 +139,117 @@ export function Form(props) {
|
|
130
139
|
apiServices,
|
131
140
|
user: userAccount,
|
132
141
|
}, undefined, isReadOnly, allDefaultPages, navigateTo, queryAddresses, apiServices, !!closeModal, fieldHeight, richTextEditor);
|
133
|
-
|
134
|
-
|
142
|
+
if (!hideButtons && !isReadOnly) {
|
143
|
+
newComponentProps.push(BottomButtons);
|
144
|
+
}
|
145
|
+
setComponentProps(newComponentProps);
|
135
146
|
}
|
136
147
|
else {
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
148
|
+
const components = await addObjectPropertiesToComponentProps(visibleObjectProperties, [
|
149
|
+
{
|
150
|
+
html: `<p>${action?.type === 'delete' ? 'This action cannot be undone.' : 'Are you sure?'}</p>`,
|
151
|
+
label: 'Content',
|
152
|
+
customClass: '',
|
153
|
+
refreshOnChange: false,
|
154
|
+
hidden: false,
|
155
|
+
modalEdit: false,
|
156
|
+
conditional: {
|
157
|
+
show: null,
|
158
|
+
when: null,
|
159
|
+
eq: '',
|
160
|
+
},
|
161
|
+
type: 'Content',
|
162
|
+
key: 'content',
|
163
|
+
input: false,
|
164
|
+
placeholder: '',
|
165
|
+
prefix: '',
|
166
|
+
suffix: '',
|
167
|
+
multiple: false,
|
168
|
+
defaultValue: null,
|
169
|
+
protected: false,
|
170
|
+
unique: false,
|
171
|
+
persistent: true,
|
172
|
+
clearOnHide: true,
|
173
|
+
refreshOn: '',
|
174
|
+
redrawOn: '',
|
175
|
+
tableView: false,
|
176
|
+
dataGridLabel: false,
|
177
|
+
labelPosition: 'top',
|
178
|
+
description: '',
|
179
|
+
errorLabel: '',
|
180
|
+
tooltip: '',
|
181
|
+
hideLabel: false,
|
182
|
+
tabindex: '',
|
183
|
+
disabled: false,
|
184
|
+
autofocus: false,
|
185
|
+
dbIndex: false,
|
186
|
+
customDefaultValue: '',
|
187
|
+
calculateValue: '',
|
188
|
+
calculateServer: false,
|
189
|
+
widget: null,
|
190
|
+
attributes: {},
|
191
|
+
validateOn: 'change',
|
192
|
+
validate: {
|
193
|
+
required: false,
|
194
|
+
custom: '',
|
195
|
+
customPrivate: false,
|
196
|
+
strictDateValidation: false,
|
158
197
|
multiple: false,
|
159
|
-
defaultValue: null,
|
160
|
-
protected: false,
|
161
198
|
unique: false,
|
162
|
-
persistent: true,
|
163
|
-
clearOnHide: true,
|
164
|
-
refreshOn: '',
|
165
|
-
redrawOn: '',
|
166
|
-
tableView: false,
|
167
|
-
dataGridLabel: false,
|
168
|
-
labelPosition: 'top',
|
169
|
-
description: '',
|
170
|
-
errorLabel: '',
|
171
|
-
tooltip: '',
|
172
|
-
hideLabel: false,
|
173
|
-
tabindex: '',
|
174
|
-
disabled: false,
|
175
|
-
autofocus: false,
|
176
|
-
dbIndex: false,
|
177
|
-
customDefaultValue: '',
|
178
|
-
calculateValue: '',
|
179
|
-
calculateServer: false,
|
180
|
-
widget: null,
|
181
|
-
attributes: {},
|
182
|
-
validateOn: 'change',
|
183
|
-
validate: {
|
184
|
-
required: false,
|
185
|
-
custom: '',
|
186
|
-
customPrivate: false,
|
187
|
-
strictDateValidation: false,
|
188
|
-
multiple: false,
|
189
|
-
unique: false,
|
190
|
-
},
|
191
|
-
overlay: {
|
192
|
-
style: '',
|
193
|
-
left: '',
|
194
|
-
top: '',
|
195
|
-
width: '',
|
196
|
-
height: '',
|
197
|
-
},
|
198
|
-
allowCalculateOverride: false,
|
199
|
-
encrypted: false,
|
200
|
-
showCharCount: false,
|
201
|
-
showWordCount: false,
|
202
|
-
properties: {},
|
203
|
-
allowMultipleMasks: false,
|
204
|
-
addons: [],
|
205
|
-
id: 'eahbwo',
|
206
199
|
},
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
200
|
+
overlay: {
|
201
|
+
style: '',
|
202
|
+
left: '',
|
203
|
+
top: '',
|
204
|
+
width: '',
|
205
|
+
height: '',
|
206
|
+
},
|
207
|
+
allowCalculateOverride: false,
|
208
|
+
encrypted: false,
|
209
|
+
showCharCount: false,
|
210
|
+
showWordCount: false,
|
211
|
+
properties: {},
|
212
|
+
allowMultipleMasks: false,
|
213
|
+
addons: [],
|
214
|
+
id: 'eahbwo',
|
215
|
+
},
|
216
|
+
], instance, {
|
217
|
+
...objectInputCommonProps,
|
218
|
+
defaultPages: allDefaultPages,
|
219
|
+
navigateTo,
|
220
|
+
apiServices,
|
221
|
+
user: userAccount,
|
222
|
+
}, undefined, undefined, undefined, undefined, undefined, undefined, !!closeModal, fieldHeight, richTextEditor);
|
223
|
+
if (!hideButtons) {
|
224
|
+
components.push(BottomButtons);
|
225
|
+
}
|
226
|
+
// this condition is used for the delete action's modal form
|
227
|
+
setComponentProps(components);
|
216
228
|
}
|
217
229
|
}
|
218
230
|
else if (object?.properties) {
|
219
231
|
// If form.io form is not configured and no inputProperties are
|
220
232
|
// set, use object properties to build form.
|
221
233
|
const propertiesInForm = object.properties.filter((prop) => prop.id !== associatedObject?.propertyId && (action?.type !== 'create' || prop.type !== 'document'));
|
222
|
-
|
223
|
-
...
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
234
|
+
const components = buildComponentPropsFromObjectProperties(propertiesInForm, object.id, instance, {
|
235
|
+
...objectInputCommonProps,
|
236
|
+
defaultPages: allDefaultPages,
|
237
|
+
navigateTo,
|
238
|
+
apiServices,
|
239
|
+
user,
|
240
|
+
}, !!action, undefined, isReadOnly, queryAddresses, !!closeModal, fieldHeight, richTextEditor);
|
241
|
+
if (!isReadOnly && !hideButtons) {
|
242
|
+
components.push(BottomButtons);
|
243
|
+
}
|
244
|
+
setComponentProps(components);
|
232
245
|
}
|
233
246
|
else if (document) {
|
234
247
|
const documentProperties = toPairs(flatten(document ?? {}));
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
248
|
+
const components = buildComponentPropsFromDocumentProperties(documentProperties, isReadOnly, undefined, fieldHeight);
|
249
|
+
if (!isReadOnly && !hideButtons) {
|
250
|
+
components.push(BottomButtons);
|
251
|
+
}
|
252
|
+
setComponentProps(components);
|
239
253
|
}
|
240
254
|
};
|
241
255
|
const uploadDocuments = async (files, metadata) => {
|
@@ -427,7 +441,11 @@ export function Form(props) {
|
|
427
441
|
],
|
428
442
|
};
|
429
443
|
return (React.createElement(Box, null,
|
430
|
-
componentProps.length ? (React.createElement(FormIO
|
444
|
+
componentProps.length ? (React.createElement(FormIO
|
445
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
446
|
+
, {
|
447
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
448
|
+
onChange: (e) => setFormData(e.data), key: closeModal ? undefined : formKey, form: {
|
431
449
|
display: 'form',
|
432
450
|
components: componentProps,
|
433
451
|
}, formReady: handleFormReady })) : (React.createElement(Box, null,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import { ObjectProperty } from '../../../../types';
|
2
3
|
type FormComponentWrapperProps = {
|
3
4
|
inputId: string;
|
4
5
|
label: string;
|
@@ -17,6 +18,10 @@ type FormComponentWrapperProps = {
|
|
17
18
|
viewOnly: boolean;
|
18
19
|
children: React.ReactNode;
|
19
20
|
key: string;
|
21
|
+
displayOption?: 'radioButton' | 'dropdown' | 'dialogBox';
|
22
|
+
onChange?: (key: string, value: unknown) => void;
|
23
|
+
property?: ObjectProperty;
|
24
|
+
readOnly?: boolean;
|
20
25
|
};
|
21
26
|
/**
|
22
27
|
* A component that wraps a FormField and adds a label,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
2
|
-
import { ErrorRounded, Help } from '../../../../icons';
|
2
|
+
import { ErrorRounded, Help, HighlightOffOutlined } from '../../../../icons';
|
3
3
|
import { IconButton, Tooltip, Typography } from '../../../core';
|
4
4
|
import { Box } from '../../../layout';
|
5
5
|
const underFieldStyles = {
|
@@ -13,6 +13,11 @@ const descriptionStyles = {
|
|
13
13
|
whiteSpace: 'normal',
|
14
14
|
paddingBottom: '4px',
|
15
15
|
};
|
16
|
+
const clearBtnStyles = {
|
17
|
+
color: '#637381',
|
18
|
+
fontSize: '1.2rem',
|
19
|
+
marginBottom: '2px',
|
20
|
+
};
|
16
21
|
const PrefixSuffix = (props) => {
|
17
22
|
const { prefix, suffix, height } = props;
|
18
23
|
const text = prefix || suffix;
|
@@ -46,7 +51,7 @@ const PrefixSuffix = (props) => {
|
|
46
51
|
* description, tooltip, prefix, suffix and word/char counts
|
47
52
|
*/
|
48
53
|
export const FormComponentWrapper = (props) => {
|
49
|
-
const { inputId, label, description, tooltip, prefix, suffix, value, validate, errorMessage, showCharCount, type, viewOnly, children, } = props;
|
54
|
+
const { inputId, label, description, tooltip, prefix, suffix, value, validate, errorMessage, showCharCount, type, viewOnly, children, displayOption, onChange, property, readOnly, } = props;
|
50
55
|
const [fieldHeight, setFieldHeight] = useState(40);
|
51
56
|
const { maxLength } = validate;
|
52
57
|
const fieldRef = useRef(null);
|
@@ -62,7 +67,7 @@ export const FormComponentWrapper = (props) => {
|
|
62
67
|
charCount = maxLength - charCount;
|
63
68
|
return (React.createElement(Box, null,
|
64
69
|
React.createElement(Box, { sx: { padding: '10px 0' } },
|
65
|
-
React.createElement(Typography, { variant: "body2", color: viewOnly ? 'textSecondary' : 'textPrimary', component: "label", htmlFor: inputId },
|
70
|
+
React.createElement(Typography, { variant: "body2", color: viewOnly ? 'textSecondary' : 'textPrimary', component: "label", htmlFor: inputId, sx: { ...(displayOption === 'radioButton' && value && { marginRight: '8px' }) } },
|
66
71
|
label,
|
67
72
|
validate.required ? (React.createElement(Typography, { component: 'span', sx: { color: 'red', fontSize: '12px' } },
|
68
73
|
` *`,
|
@@ -70,7 +75,13 @@ export const FormComponentWrapper = (props) => {
|
|
70
75
|
tooltip && (React.createElement(Tooltip, { placement: "right", title: tooltip },
|
71
76
|
React.createElement(IconButton, null,
|
72
77
|
React.createElement(Help, { sx: { fontSize: '14px' } }))))),
|
73
|
-
React.createElement(
|
78
|
+
displayOption === 'radioButton' && onChange && !viewOnly && !readOnly && value && (React.createElement(Tooltip, { title: `Clear` },
|
79
|
+
React.createElement("span", null,
|
80
|
+
React.createElement(IconButton, { "aria-label": `Clear`, sx: { padding: '0px' }, onClick: () => {
|
81
|
+
property && onChange(property.id, '');
|
82
|
+
} },
|
83
|
+
React.createElement(HighlightOffOutlined, { sx: clearBtnStyles }))))),
|
84
|
+
React.createElement(Typography, { variant: "caption", sx: { ...descriptionStyles, ...(displayOption === 'radioButton' && { display: 'flex' }) } }, description),
|
74
85
|
React.createElement(Box, { sx: { display: 'flex', flexDirection: 'row' } },
|
75
86
|
React.createElement(PrefixSuffix, { prefix: prefix, height: fieldHeight }),
|
76
87
|
React.createElement(Box, { sx: { width: '100%', paddingTop: '6px' } }, children),
|
@@ -10,6 +10,7 @@ type FormFieldComponentProps = Omit<BaseFormComponentProps, 'property'> & {
|
|
10
10
|
addressPropertyId?: string;
|
11
11
|
isAddressLine1?: boolean;
|
12
12
|
initialValue?: string;
|
13
|
+
displayOption?: 'radioButton' | 'dropdown';
|
13
14
|
};
|
14
15
|
export declare class FormFieldComponent extends ReactComponent {
|
15
16
|
[x: string]: any;
|
@@ -448,7 +448,7 @@ export class FormFieldComponent extends ReactComponent {
|
|
448
448
|
* It'll cause issues with: field-level errors not showing up, conditional visibility not working, focus moving out of the form on keypress
|
449
449
|
* Will need to be revisited later. Possibly look into using this.ref */
|
450
450
|
return ReactDOM.render(React.createElement("div", null,
|
451
|
-
React.createElement(FormComponentWrapper, { ...this.component, inputId: inputId, errorMessage: this.errorMessages(), value: this.dataValue },
|
451
|
+
React.createElement(FormComponentWrapper, { ...this.component, inputId: inputId, errorMessage: this.errorMessages(), value: this.dataValue, onChange: this.handleChange },
|
452
452
|
React.createElement(FormField, { onChange: this.handleChange, onBlur: (e) => {
|
453
453
|
// no mask errors when field is empty and not required
|
454
454
|
const componentError = this.root.errors.find((error) => error.component.key === this.component.key);
|
package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js
CHANGED
@@ -78,7 +78,11 @@ export const ObjectPropertyInput = (props) => {
|
|
78
78
|
updatedFilter = {};
|
79
79
|
}
|
80
80
|
updatedFilter.limit = 100;
|
81
|
-
|
81
|
+
const { propertyId, direction } = layout?.sort ?? {
|
82
|
+
propertyId: 'name',
|
83
|
+
direction: 'asc',
|
84
|
+
};
|
85
|
+
updatedFilter.order = `${propertyId} ${direction}`;
|
82
86
|
const where = name
|
83
87
|
? transformToWhere({
|
84
88
|
name: {
|
@@ -103,7 +107,7 @@ export const ObjectPropertyInput = (props) => {
|
|
103
107
|
setLoadingOptions(false);
|
104
108
|
}
|
105
109
|
});
|
106
|
-
}, [relatedObject, setLoadingOptions, setOptions, property, filter]);
|
110
|
+
}, [relatedObject, setLoadingOptions, setOptions, property, filter, layout]);
|
107
111
|
useEffect(() => {
|
108
112
|
if (displayOption === 'dropdown') {
|
109
113
|
getDropdownOptions();
|
@@ -281,7 +285,7 @@ export const ObjectPropertyInput = (props) => {
|
|
281
285
|
caretColor: 'white',
|
282
286
|
}
|
283
287
|
: {}),
|
284
|
-
} })), readOnly: !loadingOptions && !canUpdateProperty, error: error }))) : (React.createElement(Box, { sx: {
|
288
|
+
} })), readOnly: !loadingOptions && !canUpdateProperty, error: error, sortBy: "NONE" }))) : (React.createElement(Box, { sx: {
|
285
289
|
padding: (instance?.[property.id]?.name ?? selectedInstance?.name)
|
286
290
|
? '16.5px 14px'
|
287
291
|
: '10.5px 0',
|
@@ -78,9 +78,13 @@ export const DropdownRepeatableField = (props) => {
|
|
78
78
|
setLoading(true);
|
79
79
|
const endObjectProperty = middleObject.properties?.find((currProperty) => property.manyToManyPropertyId === currProperty.id);
|
80
80
|
if (endObjectProperty?.objectId) {
|
81
|
+
const { propertyId, direction } = layout?.sort ?? {
|
82
|
+
propertyId: 'name',
|
83
|
+
direction: 'asc',
|
84
|
+
};
|
81
85
|
const filter = {
|
82
86
|
limit: 100,
|
83
|
-
order:
|
87
|
+
order: `${propertyId} ${direction}`,
|
84
88
|
};
|
85
89
|
let searchCriteria = criteria && !isEmpty(criteria) ? transformToWhere(criteria) : {};
|
86
90
|
if (searchedName?.length) {
|
@@ -106,7 +110,7 @@ export const DropdownRepeatableField = (props) => {
|
|
106
110
|
});
|
107
111
|
}
|
108
112
|
}
|
109
|
-
}, [property.objectId, property.manyToManyPropertyId, apiServices, middleObject]);
|
113
|
+
}, [property.objectId, property.manyToManyPropertyId, apiServices, middleObject, layout]);
|
110
114
|
const debouncedEndObjectSearch = useCallback(debounce(fetchEndObjectInstances, 500), [fetchEndObjectInstances]);
|
111
115
|
useEffect(() => {
|
112
116
|
debouncedEndObjectSearch(searchValue);
|
@@ -86,6 +86,7 @@ export const DropdownRepeatableFieldInput = (props) => {
|
|
86
86
|
setSearchValue(event.target.value);
|
87
87
|
} })),
|
88
88
|
loading: loading,
|
89
|
+
sortBy: 'NONE',
|
89
90
|
} }),
|
90
91
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })))) : (React.createElement(Typography, null, selectedOptions && selectedOptions.map((option) => option.label).join(', ')))));
|
91
92
|
};
|
@@ -195,7 +195,7 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
195
195
|
displayOptions?.defaultValue &&
|
196
196
|
typeof displayOptions.defaultValue !== 'string'
|
197
197
|
? displayOptions.defaultValue?.sortBy
|
198
|
-
:
|
198
|
+
: displayOptions?.choicesDisplay?.sortBy,
|
199
199
|
orderBy: parameter.type === 'object' &&
|
200
200
|
displayOptions?.defaultValue &&
|
201
201
|
typeof displayOptions.defaultValue !== 'string'
|
@@ -205,7 +205,11 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
205
205
|
inputMaskPlacholderChar: displayOptions?.placeholderChar,
|
206
206
|
placeholder: displayOptions?.placeholder,
|
207
207
|
tableView: false,
|
208
|
-
displayOption: parameter.type === 'object'
|
208
|
+
displayOption: parameter.type === 'object'
|
209
|
+
? displayOptions?.relatedObjectDisplay
|
210
|
+
: parameter.type === 'string' && parameter.enum
|
211
|
+
? displayOptions?.choicesDisplay?.type
|
212
|
+
: undefined,
|
209
213
|
labelPosition: 'top',
|
210
214
|
dataSrc: property.enum?.length ? 'values' : undefined,
|
211
215
|
showCharCount: displayOptions?.charCount,
|
@@ -367,7 +371,20 @@ export function convertComponentsToForm(components) {
|
|
367
371
|
...(component.readOnly ? { readOnly: component.readOnly } : {}),
|
368
372
|
...(component.mode ? { mode: component.mode } : {}),
|
369
373
|
...(component.validate?.required ? { required: component.validate?.required } : {}),
|
370
|
-
...(component.displayOption
|
374
|
+
...((component.displayOption || component.sortBy) &&
|
375
|
+
component.property?.enum &&
|
376
|
+
component.property?.type !== 'array' &&
|
377
|
+
component.property?.type !== 'object'
|
378
|
+
? {
|
379
|
+
choicesDisplay: {
|
380
|
+
...(component.displayOption ? { type: component.displayOption } : {}),
|
381
|
+
...(component.sortBy ? { sortBy: component.sortBy } : {}),
|
382
|
+
},
|
383
|
+
}
|
384
|
+
: {}),
|
385
|
+
...(component.displayOption && component?.property?.type === 'object'
|
386
|
+
? { relatedObjectDisplay: component.displayOption }
|
387
|
+
: {}),
|
371
388
|
...(component.defaultToCurrentDate ||
|
372
389
|
component.defaultToCurrentTime ||
|
373
390
|
component.initialValue ||
|
@@ -384,7 +401,7 @@ export function convertComponentsToForm(components) {
|
|
384
401
|
...(component.orderBy ? { sortBy: component.orderBy } : {}),
|
385
402
|
}
|
386
403
|
: isArray(component.initialValue)
|
387
|
-
? component.initialValue.map((c) => c.value)
|
404
|
+
? component.initialValue.map((c) => typeof c === 'string' ? c : c.value)
|
388
405
|
: component.initialValue?.value
|
389
406
|
? component.initialValue?.value
|
390
407
|
: component.initialValue,
|
@@ -28,6 +28,8 @@ export type FormFieldProps = {
|
|
28
28
|
getOptionLabel?: (option: AutocompleteOption) => string;
|
29
29
|
disableCloseOnSelect?: boolean;
|
30
30
|
additionalProps?: Record<string, unknown>;
|
31
|
+
displayOption?: 'dropdown' | 'radioButton';
|
32
|
+
sortBy?: 'ASC' | 'DESC' | 'NONE';
|
31
33
|
};
|
32
34
|
declare const FormField: (props: FormFieldProps) => JSX.Element;
|
33
35
|
export default FormField;
|
@@ -8,7 +8,7 @@ import InputFieldComponent from './InputFieldComponent/InputFieldComponent';
|
|
8
8
|
import Select from './Select/Select';
|
9
9
|
import TimePickerSelect from './TimePickerSelect/TimePickerSelect';
|
10
10
|
const FormField = (props) => {
|
11
|
-
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, } = props;
|
11
|
+
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, } = props;
|
12
12
|
let control;
|
13
13
|
const commonProps = {
|
14
14
|
id: id ?? property.id,
|
@@ -30,6 +30,8 @@ const FormField = (props) => {
|
|
30
30
|
getOptionLabel,
|
31
31
|
disableCloseOnSelect,
|
32
32
|
additionalProps,
|
33
|
+
displayOption,
|
34
|
+
sortBy,
|
33
35
|
};
|
34
36
|
if (queryAddresses) {
|
35
37
|
control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
2
|
-
import { Autocomplete, TextField } from '../../../core';
|
2
|
+
import { Autocomplete, FormControl, FormControlLabel, Radio, RadioGroup, TextField, } from '../../../core';
|
3
3
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
4
4
|
const Select = (props) => {
|
5
|
-
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, selectOptions, required, size, isOptionEqualToValue, renderOption, getOptionLabel, disableCloseOnSelect, additionalProps, } = props;
|
5
|
+
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, selectOptions, required, size, isOptionEqualToValue, renderOption, getOptionLabel, disableCloseOnSelect, additionalProps, displayOption, sortBy, } = props;
|
6
6
|
const [value, setValue] = useState(defaultValue);
|
7
7
|
const [inputValue, setInputValue] = useState('');
|
8
8
|
useEffect(() => {
|
@@ -28,10 +28,34 @@ const Select = (props) => {
|
|
28
28
|
setInputValue(selectValue);
|
29
29
|
}
|
30
30
|
};
|
31
|
-
|
31
|
+
const sortedOptions = (() => {
|
32
|
+
if (!selectOptions)
|
33
|
+
return [];
|
34
|
+
const options = [...selectOptions];
|
35
|
+
switch (sortBy) {
|
36
|
+
case 'NONE':
|
37
|
+
return options;
|
38
|
+
case 'DESC':
|
39
|
+
return options.sort((a, b) => typeof a === 'string' ? b.localeCompare(a) : b.label.localeCompare(a.label));
|
40
|
+
default:
|
41
|
+
return options.sort((a, b) => typeof a === 'string' ? a.localeCompare(b) : a.label.localeCompare(b.label));
|
42
|
+
}
|
43
|
+
})();
|
44
|
+
return readOnly ? (React.createElement(InputFieldComponent, { ...props })) : displayOption === 'radioButton' ? (React.createElement(FormControl, { error: error, required: required },
|
45
|
+
React.createElement(RadioGroup, { name: `radioGroup-${id}`, value: value, onChange: handleChange, sx: { paddingLeft: '12px' } }, sortedOptions.map((option, index) => (React.createElement(FormControlLabel, { key: index, value: typeof option === 'string' ? option : option.value, control: React.createElement(Radio, { size: "small", sx: {
|
46
|
+
...(size === 'small' && { paddingTop: '2px', paddingBottom: '2px' }),
|
47
|
+
...(error && {
|
48
|
+
color: '#FF4842',
|
49
|
+
'&.Mui-checked': { color: '#FF4842' },
|
50
|
+
}),
|
51
|
+
} }), label: typeof option === 'string' ? option : option.label })))))) : (React.createElement(Autocomplete, { multiple: property?.type === 'array' ? true : false, id: id, sortBy: sortBy, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, fullWidth: true, onBlur: onBlur })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, isOptionEqualToValue: isOptionEqualToValue
|
32
52
|
? (option, value) => isOptionEqualToValue(option, value)
|
33
53
|
: undefined, getOptionLabel: getOptionLabel ? (option) => getOptionLabel(option) : undefined, renderOption: renderOption
|
34
54
|
? (props, option, state) => renderOption(props, option, state)
|
35
|
-
: undefined, disableCloseOnSelect: disableCloseOnSelect,
|
55
|
+
: undefined, disableCloseOnSelect: disableCloseOnSelect, sx: {
|
56
|
+
'& button.MuiButtonBase-root': {
|
57
|
+
visibility: 'visible',
|
58
|
+
},
|
59
|
+
}, ...(additionalProps ?? {}) }));
|
36
60
|
};
|
37
61
|
export default Select;
|
@@ -32,6 +32,20 @@ describe('Single select', () => {
|
|
32
32
|
await screen.findByRole('option', { name: 'option 2' });
|
33
33
|
expect(screen.queryByRole('option', { name: 'something different' })).not.toBeInTheDocument();
|
34
34
|
});
|
35
|
+
test.each([
|
36
|
+
{ sortBy: 'ASC', expectedValues: ['option 1', 'option 2', 'option 3'] },
|
37
|
+
{ sortBy: 'NONE', expectedValues: ['option 2', 'option 1', 'option 3'] },
|
38
|
+
{ sortBy: 'DESC', expectedValues: ['option 3', 'option 2', 'option 1'] },
|
39
|
+
])('shows options in $sortBy order as dropdown display', async ({ sortBy, expectedValues }) => {
|
40
|
+
const options = ['option 2', 'option 1', 'option 3'];
|
41
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'dropdown', sortBy: sortBy, onChange: jest.fn() }));
|
42
|
+
const user = userEvent.setup();
|
43
|
+
const input = screen.getByRole('combobox');
|
44
|
+
await user.click(input);
|
45
|
+
const allOptions = screen.getAllByRole('option');
|
46
|
+
const optionLabels = allOptions.map((option) => option.textContent);
|
47
|
+
expect(optionLabels).toEqual(expectedValues);
|
48
|
+
});
|
35
49
|
});
|
36
50
|
describe('Multi select', () => {
|
37
51
|
// Right now an object property is required for this to function, but eventually this should go
|
@@ -57,3 +71,30 @@ describe('Multi select', () => {
|
|
57
71
|
expect(onChangeMock).lastCalledWith('multiSelect', ['option 2', 'option 3'], multiChoiceProperty);
|
58
72
|
});
|
59
73
|
});
|
74
|
+
describe('Radio Single select', () => {
|
75
|
+
const choiceProperty = {
|
76
|
+
id: 'selectOptions',
|
77
|
+
name: 'Select Options',
|
78
|
+
type: 'choices',
|
79
|
+
};
|
80
|
+
test('returns selected radio option', async () => {
|
81
|
+
const user = userEvent.setup();
|
82
|
+
const onChangeMock = jest.fn((name, value, property) => { });
|
83
|
+
const options = ['option 1', 'option 2', 'option 3'];
|
84
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock }));
|
85
|
+
const option2 = await screen.findByRole('radio', { name: 'option 2' });
|
86
|
+
await user.click(option2);
|
87
|
+
expect(onChangeMock).toBeCalledWith('selectOptions', expect.stringContaining('option 2'), choiceProperty);
|
88
|
+
});
|
89
|
+
test.each([
|
90
|
+
{ sortBy: 'ASC', expectedValues: ['option 1', 'option 2', 'option 3'] },
|
91
|
+
{ sortBy: 'NONE', expectedValues: ['option 2', 'option 1', 'option 3'] },
|
92
|
+
{ sortBy: 'DESC', expectedValues: ['option 3', 'option 2', 'option 1'] },
|
93
|
+
])('shows options in $sortBy order as radio display', async ({ sortBy, expectedValues }) => {
|
94
|
+
const options = ['option 2', 'option 1', 'option 3'];
|
95
|
+
render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: sortBy, onChange: jest.fn() }));
|
96
|
+
const radioButtons = screen.getAllByRole('radio');
|
97
|
+
const radioValues = radioButtons.map((radioButton) => radioButton.value);
|
98
|
+
expect(radioValues).toEqual(expectedValues);
|
99
|
+
});
|
100
|
+
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { TextField, Tooltip } from '@mui/material';
|
2
|
+
import React, { useRef, useState } from 'react';
|
3
|
+
const OverflowTextField = (props) => {
|
4
|
+
const { value, ...rest } = props;
|
5
|
+
const inputRef = useRef(null);
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
7
|
+
const textField = (React.createElement(TextField, { ...rest, value: value, InputProps: {
|
8
|
+
...rest.InputProps,
|
9
|
+
inputRef: inputRef,
|
10
|
+
} }));
|
11
|
+
return (React.createElement(Tooltip, { open: isOpen, onOpen: () => inputRef.current && inputRef.current.scrollWidth > inputRef.current.clientWidth && setIsOpen(true), onClose: () => setIsOpen(false), title: value }, textField));
|
12
|
+
};
|
13
|
+
export default OverflowTextField;
|
@@ -3,10 +3,11 @@ export { CriteriaBuilder } from './CriteriaBuilder';
|
|
3
3
|
export { DataGrid } from './DataGrid';
|
4
4
|
export { ErrorComponent } from './ErrorComponent';
|
5
5
|
export { Form } from './Form';
|
6
|
+
export type { FormRef } from './Form';
|
6
7
|
export { FormField } from './FormField';
|
8
|
+
export { HistoryLog } from './HistoryLog';
|
7
9
|
export { MenuBar } from './Menubar';
|
8
10
|
export { MultiSelect } from './MultiSelect';
|
9
11
|
export { RepeatableField } from './RepeatableField';
|
10
|
-
export { UserAvatar } from './UserAvatar';
|
11
|
-
export { HistoryLog } from './HistoryLog';
|
12
12
|
export { RichTextViewer } from './RichTextViewer';
|
13
|
+
export { UserAvatar } from './UserAvatar';
|
@@ -4,9 +4,9 @@ export { DataGrid } from './DataGrid';
|
|
4
4
|
export { ErrorComponent } from './ErrorComponent';
|
5
5
|
export { Form } from './Form';
|
6
6
|
export { FormField } from './FormField';
|
7
|
+
export { HistoryLog } from './HistoryLog';
|
7
8
|
export { MenuBar } from './Menubar';
|
8
9
|
export { MultiSelect } from './MultiSelect';
|
9
10
|
export { RepeatableField } from './RepeatableField';
|
10
|
-
export { UserAvatar } from './UserAvatar';
|
11
|
-
export { HistoryLog } from './HistoryLog';
|
12
11
|
export { RichTextViewer } from './RichTextViewer';
|
12
|
+
export { UserAvatar } from './UserAvatar';
|
@@ -1,11 +1,14 @@
|
|
1
|
-
export { ClickAwayListener,
|
1
|
+
export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
3
|
+
export * from './colors';
|
3
4
|
export * from './components/core';
|
4
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, MenuBar, MultiSelect, RepeatableField,
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
|
6
|
+
export type { FormRef } from './components/custom';
|
7
|
+
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
5
8
|
export { Box, Container, Grid, Stack } from './components/layout';
|
6
9
|
export * as EVOKE_TYPES from './types';
|
7
10
|
export * from './util';
|
8
|
-
export type { ButtonBaseActions, ButtonBaseClasses, FormControlProps, FormHelperTextProps, GridSize, MenuItemClasses, Theme, } from '@mui/material';
|
11
|
+
export type { AutocompleteRenderGroupParams, ButtonBaseActions, ButtonBaseClasses, FormControlProps, FormHelperTextProps, GridSize, MenuItemClasses, TextFieldProps, Theme, } from '@mui/material';
|
9
12
|
export type { TouchRippleActions, TouchRippleProps } from '@mui/material/ButtonBase/TouchRipple';
|
10
13
|
export type { CommonProps } from '@mui/material/OverridableComponent';
|
11
14
|
export type { SxProps } from '@mui/system';
|
package/dist/published/index.js
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
export { ClickAwayListener,
|
1
|
+
export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
3
|
+
export * from './colors';
|
3
4
|
export * from './components/core';
|
4
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, MenuBar, MultiSelect, RepeatableField,
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
|
6
|
+
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
5
7
|
export { Box, Container, Grid, Stack } from './components/layout';
|
6
8
|
export * as EVOKE_TYPES from './types';
|
7
9
|
export * from './util';
|
@@ -0,0 +1,5 @@
|
|
1
|
+
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
2
|
+
import React from 'react';
|
3
|
+
declare const _default: ComponentMeta<React.FC<import("@mui/material").FilledTextFieldProps | import("@mui/material").OutlinedTextFieldProps | import("@mui/material").StandardTextFieldProps>>;
|
4
|
+
export default _default;
|
5
|
+
export declare const OverflowTextField: ComponentStory<React.FC<import("@mui/material").FilledTextFieldProps | import("@mui/material").OutlinedTextFieldProps | import("@mui/material").StandardTextFieldProps>>;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { OverflowTextField as CustomOverflowTextField } from '../components/custom/OverflowTextField';
|
3
|
+
export default {
|
4
|
+
title: 'Input/OverflowTextField',
|
5
|
+
component: CustomOverflowTextField,
|
6
|
+
argTypes: {
|
7
|
+
value: {
|
8
|
+
control: 'text',
|
9
|
+
defaultValue: 'This is a sample text that is too long to be fully visible within the input field, triggering an ellipsis.',
|
10
|
+
description: 'The text to display in the field',
|
11
|
+
},
|
12
|
+
placeholder: {
|
13
|
+
control: 'text',
|
14
|
+
defaultValue: 'Enter text...',
|
15
|
+
},
|
16
|
+
size: {
|
17
|
+
control: 'text',
|
18
|
+
defaultValue: 'small',
|
19
|
+
},
|
20
|
+
},
|
21
|
+
};
|
22
|
+
const Template = (args) => React.createElement(CustomOverflowTextField, { ...args });
|
23
|
+
export const OverflowTextField = Template.bind({});
|
24
|
+
OverflowTextField.args = {
|
25
|
+
value: 'This is a sample text that is too long to be fully visible within the input field, triggering an ellipsis.',
|
26
|
+
placeholder: 'Enter text here...',
|
27
|
+
size: 'small',
|
28
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@evoke-platform/ui-components",
|
3
|
-
"version": "1.1.0-testing.
|
3
|
+
"version": "1.1.0-testing.10",
|
4
4
|
"description": "",
|
5
5
|
"main": "dist/published/index.js",
|
6
6
|
"module": "dist/published/index.js",
|
@@ -94,7 +94,7 @@
|
|
94
94
|
"@dnd-kit/sortable": "^7.0.1",
|
95
95
|
"@emotion/react": "^11.13.5",
|
96
96
|
"@emotion/styled": "^11.8.1",
|
97
|
-
"@evoke-platform/context": "^1.0.0-dev.
|
97
|
+
"@evoke-platform/context": "^1.0.0-dev.126",
|
98
98
|
"@formio/react": "^5.2.4-rc.1",
|
99
99
|
"@js-joda/core": "^3.2.0",
|
100
100
|
"@js-joda/locale_en-us": "^3.2.2",
|
@@ -135,12 +135,6 @@
|
|
135
135
|
"npm run lint:fix"
|
136
136
|
]
|
137
137
|
},
|
138
|
-
"husky": {
|
139
|
-
"hooks": {
|
140
|
-
"pre-commit": "npx lint-staged",
|
141
|
-
"commit-msg": "npx commitlint --edit $1"
|
142
|
-
}
|
143
|
-
},
|
144
138
|
"jest": {
|
145
139
|
"verbose": true,
|
146
140
|
"testEnvironment": "jsdom",
|