@evoke-platform/ui-components 1.5.0-dev.1 → 1.5.0-dev.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/List/ListItemIcon/ListItemIcon.d.ts +4 -0
- package/dist/published/components/core/List/ListItemIcon/ListItemIcon.js +6 -0
- package/dist/published/components/core/List/ListItemIcon/index.d.ts +3 -0
- package/dist/published/components/core/List/ListItemIcon/index.js +3 -0
- package/dist/published/components/core/List/index.d.ts +2 -1
- package/dist/published/components/core/List/index.js +2 -1
- package/dist/published/components/core/index.d.ts +1 -1
- package/dist/published/components/core/index.js +1 -1
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +6 -6
- package/dist/published/components/custom/CriteriaBuilder/ValueEditor.d.ts +2 -3
- package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +87 -62
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +18 -17
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +1 -1
- package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +6 -9
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +3 -3
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/DocumentViewerCell.d.ts +13 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/DocumentViewerCell.js +115 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +25 -26
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +1 -1
- package/dist/published/components/custom/Form/utils.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +28 -14
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.test.js +50 -8
- package/dist/published/components/custom/FormField/FormField.d.ts +4 -1
- package/dist/published/components/custom/FormField/FormField.js +5 -2
- package/dist/published/components/custom/Menubar/Menubar.d.ts +14 -1
- package/dist/published/components/custom/Menubar/Menubar.js +9 -2
- package/dist/published/components/custom/Menubar/Menubar.test.js +5 -0
- package/dist/published/components/layout/Box/Box.d.ts +3 -3
- package/dist/published/components/layout/Box/Box.js +4 -4
- package/dist/published/icons/custom/NoNavigation.d.ts +3 -0
- package/dist/published/icons/custom/NoNavigation.js +10 -0
- package/dist/published/icons/custom/SideNavigation.d.ts +3 -0
- package/dist/published/icons/custom/SideNavigation.js +11 -0
- package/dist/published/icons/custom/TopNavigation.d.ts +3 -0
- package/dist/published/icons/custom/TopNavigation.js +11 -0
- package/dist/published/icons/custom/index.d.ts +3 -0
- package/dist/published/icons/custom/index.js +3 -0
- package/dist/published/stories/Box.stories.d.ts +3 -12
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/dist/published/stories/Form.stories.js +7 -2
- package/dist/published/stories/FormField.stories.js +2 -0
- package/dist/published/stories/MenuBar.stories.js +13 -18
- package/dist/published/stories/Palette.stories.d.ts +2 -12
- package/package.json +8 -6
@@ -0,0 +1,115 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { AutorenewRounded, FileWithExtension, LaunchRounded } from '../../../../../icons';
|
3
|
+
import { Button, Menu, MenuItem, Typography } from '../../../../core';
|
4
|
+
import { Grid } from '../../../../layout';
|
5
|
+
import { getPrefixedUrl } from '../../utils';
|
6
|
+
export const DocumentViewerCell = (props) => {
|
7
|
+
const { instance, propertyId, apiServices, setSnackbarError } = props;
|
8
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
10
|
+
const downloadDocument = async (doc, instance) => {
|
11
|
+
setIsLoading(true);
|
12
|
+
try {
|
13
|
+
const documentResponse = await apiServices.get(getPrefixedUrl(`/objects/${instance.objectId}/instances/${instance.id}/documents/${doc.id}/content`), { responseType: 'blob' });
|
14
|
+
const contentType = documentResponse.type;
|
15
|
+
const blob = new Blob([documentResponse], { type: contentType });
|
16
|
+
const url = URL.createObjectURL(blob);
|
17
|
+
// Let the browser handle whether to open the document to view in a new tab or download it.
|
18
|
+
window.open(url, '_blank');
|
19
|
+
setIsLoading(false);
|
20
|
+
URL.revokeObjectURL(url);
|
21
|
+
}
|
22
|
+
catch (error) {
|
23
|
+
const status = error.status;
|
24
|
+
let message = 'An error occurred while downloading the document.';
|
25
|
+
if (status === 403) {
|
26
|
+
message = 'You do not have permission to download this document.';
|
27
|
+
}
|
28
|
+
else if (status === 404) {
|
29
|
+
message = 'Document not found.';
|
30
|
+
}
|
31
|
+
setIsLoading(false);
|
32
|
+
setSnackbarError({
|
33
|
+
showAlert: true,
|
34
|
+
message,
|
35
|
+
isError: true,
|
36
|
+
});
|
37
|
+
}
|
38
|
+
};
|
39
|
+
return (React.createElement(React.Fragment, null, instance[propertyId]?.length ? (React.createElement(React.Fragment, null,
|
40
|
+
React.createElement(Button, { sx: {
|
41
|
+
display: 'flex',
|
42
|
+
alignItems: 'center',
|
43
|
+
justifyContent: 'flex-start',
|
44
|
+
padding: '6px 10px',
|
45
|
+
}, color: 'inherit', onClick: async (event) => {
|
46
|
+
event.stopPropagation();
|
47
|
+
const documents = instance[propertyId];
|
48
|
+
if (documents.length === 1) {
|
49
|
+
await downloadDocument(documents[0], instance);
|
50
|
+
}
|
51
|
+
else {
|
52
|
+
setAnchorEl(event.currentTarget);
|
53
|
+
}
|
54
|
+
}, variant: "text", "aria-haspopup": "menu", "aria-controls": `document-menu-${instance.id}-${propertyId}`, "aria-expanded": anchorEl ? 'true' : 'false' },
|
55
|
+
isLoading ? (React.createElement(AutorenewRounded, { sx: {
|
56
|
+
color: '#637381',
|
57
|
+
width: '20px',
|
58
|
+
height: '20px',
|
59
|
+
} })) : (React.createElement(LaunchRounded, { sx: {
|
60
|
+
color: '#637381',
|
61
|
+
width: '20px',
|
62
|
+
height: '20px',
|
63
|
+
} })),
|
64
|
+
React.createElement(Typography, { sx: {
|
65
|
+
marginLeft: '8px',
|
66
|
+
fontWeight: 400,
|
67
|
+
fontSize: '14px',
|
68
|
+
} }, isLoading ? 'Preparing document...' : 'View Document')),
|
69
|
+
React.createElement("section", { role: "region", "aria-label": "Document Menu" },
|
70
|
+
React.createElement(Menu, { id: `document-menu-${instance.id}-${propertyId}`, anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
|
71
|
+
setAnchorEl(null);
|
72
|
+
}, sx: {
|
73
|
+
'& .MuiPaper-root': {
|
74
|
+
borderRadius: '12px',
|
75
|
+
boxShadow: 'rgba(145, 158, 171, 0.2)',
|
76
|
+
},
|
77
|
+
}, variant: 'menu', slotProps: {
|
78
|
+
paper: {
|
79
|
+
tabIndex: 0,
|
80
|
+
style: {
|
81
|
+
maxHeight: 200,
|
82
|
+
maxWidth: 300,
|
83
|
+
minWidth: 300,
|
84
|
+
},
|
85
|
+
},
|
86
|
+
} }, instance[propertyId].map((document) => (React.createElement(MenuItem, { key: document.id, onClick: async (e) => {
|
87
|
+
setAnchorEl(null);
|
88
|
+
await downloadDocument(document, instance);
|
89
|
+
}, "aria-label": document.name },
|
90
|
+
React.createElement(Grid, { item: true, sx: {
|
91
|
+
display: 'flex',
|
92
|
+
justifyContent: 'center',
|
93
|
+
padding: '7px',
|
94
|
+
} },
|
95
|
+
React.createElement(FileWithExtension, { fontFamily: "Arial", fileExtension: document.name?.split('.')?.pop() ?? '', sx: {
|
96
|
+
height: '1rem',
|
97
|
+
width: '1rem',
|
98
|
+
} })),
|
99
|
+
React.createElement(Grid, { item: true, xs: 12, sx: {
|
100
|
+
width: '100%',
|
101
|
+
overflow: 'hidden',
|
102
|
+
textOverflow: 'ellipsis',
|
103
|
+
} },
|
104
|
+
React.createElement(Typography, { noWrap: true, sx: {
|
105
|
+
fontSize: '14px',
|
106
|
+
fontWeight: 700,
|
107
|
+
color: '#212B36',
|
108
|
+
lineHeight: '15px',
|
109
|
+
width: '100%',
|
110
|
+
} }, document.name))))))))) : (React.createElement(Typography, { sx: {
|
111
|
+
fontStyle: 'italic',
|
112
|
+
marginLeft: '12px',
|
113
|
+
fontSize: '14px',
|
114
|
+
} }, "No documents"))));
|
115
|
+
};
|
@@ -9,6 +9,7 @@ import { Button, IconButton, Skeleton, Snackbar, Table, TableBody, TableCell, Ta
|
|
9
9
|
import { Box } from '../../../../layout';
|
10
10
|
import { getPrefixedUrl, normalizeDateTime } from '../../utils';
|
11
11
|
import { ActionDialog } from './ActionDialog';
|
12
|
+
import { DocumentViewerCell } from './DocumentViewerCell';
|
12
13
|
const styles = {
|
13
14
|
addButton: {
|
14
15
|
backgroundColor: 'rgba(0, 117, 167, 0.08)',
|
@@ -250,13 +251,13 @@ const RepeatableField = (props) => {
|
|
250
251
|
isSuccessful = true;
|
251
252
|
}
|
252
253
|
catch (err) {
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
255
|
+
error = err.response?.data?.error;
|
253
256
|
setSnackbarError({
|
254
257
|
showAlert: true,
|
255
|
-
message: `An error occurred while creating an instance`,
|
258
|
+
message: error?.message ?? `An error occurred while creating an instance`,
|
256
259
|
isError: true,
|
257
260
|
});
|
258
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
259
|
-
error = err.response?.data?.error;
|
260
261
|
setSubmitting && setSubmitting(false);
|
261
262
|
}
|
262
263
|
}
|
@@ -284,13 +285,15 @@ const RepeatableField = (props) => {
|
|
284
285
|
isSuccessful = true;
|
285
286
|
}
|
286
287
|
catch (err) {
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
289
|
+
error = err.response?.data?.error;
|
287
290
|
setSnackbarError({
|
288
291
|
showAlert: true,
|
289
|
-
message:
|
292
|
+
message: error?.message ??
|
293
|
+
`An error occurred while ${actionType === 'delete' ? ' deleting' : ' updating'} an instance`,
|
290
294
|
isError: true,
|
291
295
|
});
|
292
|
-
|
293
|
-
error = err.response?.data?.error;
|
296
|
+
setSubmitting && setSubmitting(false);
|
294
297
|
}
|
295
298
|
}
|
296
299
|
return { isSuccessful, error };
|
@@ -334,9 +337,9 @@ const RepeatableField = (props) => {
|
|
334
337
|
};
|
335
338
|
const getValue = (relatedInstance, propertyId, propertyType) => {
|
336
339
|
const value = get(relatedInstance, propertyId);
|
337
|
-
// If the property is not date-like
|
340
|
+
// If the property is not date-like then just return the
|
338
341
|
// value found at the given path.
|
339
|
-
if (!['date', 'date-time', 'time'
|
342
|
+
if (!['date', 'date-time', 'time'].includes(propertyType)) {
|
340
343
|
return value;
|
341
344
|
}
|
342
345
|
// If the date-like value is empty then there is no need to format.
|
@@ -356,9 +359,6 @@ const RepeatableField = (props) => {
|
|
356
359
|
if (propertyType === 'time') {
|
357
360
|
return DateTime.fromISO(stringValue).toLocaleString(DateTime.TIME_SIMPLE);
|
358
361
|
}
|
359
|
-
if (property.type === 'document') {
|
360
|
-
return Array.isArray(value) ? value.map((v) => v.name).join(', ') : value;
|
361
|
-
}
|
362
362
|
};
|
363
363
|
const columns = retrieveViewLayout();
|
364
364
|
return loading ? (React.createElement(React.Fragment, null,
|
@@ -376,23 +376,22 @@ const RepeatableField = (props) => {
|
|
376
376
|
React.createElement(TableHead, { sx: { backgroundColor: '#F4F6F8' } },
|
377
377
|
React.createElement(TableRow, null,
|
378
378
|
columns?.map((prop) => React.createElement(TableCell, { sx: styles.tableCell }, prop.name)),
|
379
|
-
React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
|
379
|
+
canUpdateProperty && React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
|
380
380
|
React.createElement(TableBody, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
|
381
381
|
columns?.map((prop) => {
|
382
|
-
return (React.createElement(TableCell, { sx: { color: '#212B36', fontSize: '16px' } },
|
383
|
-
|
384
|
-
|
385
|
-
'
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
prop.
|
395
|
-
users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && React.createElement("span", null, ' (Inactive)'))));
|
382
|
+
return (React.createElement(TableCell, { sx: { color: '#212B36', fontSize: '16px' } }, prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, apiServices: apiServices, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
|
383
|
+
? {
|
384
|
+
'&:hover': {
|
385
|
+
textDecoration: 'underline',
|
386
|
+
cursor: 'pointer',
|
387
|
+
},
|
388
|
+
}
|
389
|
+
: {}, onClick: canUpdateProperty && prop.id === 'name'
|
390
|
+
? () => editRow(relatedInstance.id)
|
391
|
+
: undefined },
|
392
|
+
getValue(relatedInstance, prop.id, prop.type),
|
393
|
+
prop.type === 'user' &&
|
394
|
+
users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
|
396
395
|
}),
|
397
396
|
canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
|
398
397
|
React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
|
@@ -127,7 +127,7 @@ export function convertFormToComponents(entries, parameters, object) {
|
|
127
127
|
conditional: convertVisibilityToConditional(entry.visibility),
|
128
128
|
};
|
129
129
|
}
|
130
|
-
else {
|
130
|
+
else if (entry.type === 'input') {
|
131
131
|
const displayOptions = entry.display;
|
132
132
|
const parameter = parameters.find((parameter) => parameter.id === entry.parameterId);
|
133
133
|
if (!parameter) {
|
@@ -1,39 +1,53 @@
|
|
1
|
+
import parse from 'html-react-parser';
|
1
2
|
import React, { useEffect, useState } from 'react';
|
2
|
-
import {
|
3
|
+
import { Help } from '../../../../icons';
|
4
|
+
import { Autocomplete, Checkbox, FormControl, FormControlLabel, FormHelperText, IconButton, Switch, TextField, Tooltip, Typography, } from '../../../core';
|
3
5
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
6
|
+
const descriptionStyles = {
|
7
|
+
color: '#999 !important',
|
8
|
+
whiteSpace: 'normal',
|
9
|
+
paddingBottom: '4px',
|
10
|
+
marginX: 0,
|
11
|
+
};
|
4
12
|
const BooleanSelect = (props) => {
|
5
|
-
const { id, property, defaultValue, error, errorMessage, readOnly, size, placeholder, onBlur, additionalProps } = props;
|
13
|
+
const { id, property, defaultValue, error, errorMessage, readOnly, size, displayOption, label, required, tooltip, description, placeholder, onBlur, additionalProps, } = props;
|
6
14
|
const [value, setValue] = useState(defaultValue);
|
7
15
|
useEffect(() => {
|
8
16
|
setValue(defaultValue);
|
9
17
|
}, [defaultValue]);
|
10
|
-
const handleChange = (
|
11
|
-
setValue(
|
12
|
-
props.onChange(property.id,
|
18
|
+
const handleChange = (value) => {
|
19
|
+
setValue(value);
|
20
|
+
props.onChange(property.id, value, property);
|
13
21
|
};
|
14
22
|
const booleanOptions = [
|
15
23
|
{
|
16
|
-
label: '
|
24
|
+
label: 'True',
|
17
25
|
value: true,
|
18
26
|
},
|
19
27
|
{
|
20
|
-
label: '
|
28
|
+
label: 'False',
|
21
29
|
value: false,
|
22
30
|
},
|
23
31
|
];
|
24
|
-
return readOnly ? (React.createElement(InputFieldComponent, { ...props })) : (React.createElement(Autocomplete, {
|
32
|
+
return displayOption === 'dropdown' ? (readOnly ? (React.createElement(InputFieldComponent, { ...props })) : (React.createElement(Autocomplete, { renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, placeholder: placeholder })), value: value, onChange: (e, selectedValue) => handleChange(selectedValue.value), isOptionEqualToValue: (option, val) => {
|
25
33
|
if (typeof val === 'boolean') {
|
26
|
-
return option
|
34
|
+
return option?.value === val;
|
27
35
|
}
|
28
|
-
return option
|
36
|
+
return option?.value === val?.value;
|
29
37
|
}, getOptionLabel: (option) => {
|
30
38
|
if (typeof option === 'boolean') {
|
31
39
|
const opt = booleanOptions.find((o) => o.value === option);
|
32
|
-
return opt
|
40
|
+
return opt?.label ?? '';
|
33
41
|
}
|
34
|
-
if (typeof option === 'string')
|
35
|
-
return option;
|
36
42
|
return option.label;
|
37
|
-
}, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}) }))
|
43
|
+
}, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}) }))) : (React.createElement(FormControl, { error: error, fullWidth: true },
|
44
|
+
React.createElement(FormControlLabel, { labelPlacement: "end", label: React.createElement(Typography, { variant: "body2", sx: { wordWrap: 'break-word', fontFamily: 'Public Sans' } },
|
45
|
+
label,
|
46
|
+
tooltip && (React.createElement(Tooltip, { placement: "right", title: tooltip },
|
47
|
+
React.createElement(IconButton, null,
|
48
|
+
React.createElement(Help, { sx: { fontSize: '14px' } })))),
|
49
|
+
required && (React.createElement(Typography, { variant: "body2", component: "span", color: "error", sx: { marginLeft: '4px', fontSize: '18px' } }, "*"))), control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-checked": value, "aria-required": required, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), disabled: readOnly, sx: { alignSelf: 'start' } })) : (React.createElement(Checkbox, { id: id, "aria-checked": value, "aria-required": required, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), disabled: readOnly, sx: { alignSelf: 'start', padding: '4px 9px 9px 9px' } })) }),
|
50
|
+
error && React.createElement(FormHelperText, { sx: { marginX: 0 } }, errorMessage),
|
51
|
+
description && (React.createElement(FormHelperText, { sx: descriptionStyles, component: Typography }, parse(description)))));
|
38
52
|
};
|
39
53
|
export default BooleanSelect;
|
@@ -10,13 +10,55 @@ const booleanProperty = {
|
|
10
10
|
name: 'Question',
|
11
11
|
type: 'boolean',
|
12
12
|
};
|
13
|
-
|
13
|
+
describe('BooleanSelect', () => {
|
14
14
|
const user = userEvent.setup();
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
it('returns selected option', async () => {
|
16
|
+
const user = userEvent.setup();
|
17
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
18
|
+
render(React.createElement(BooleanSelect, { id: "chooseTrueOrFalse", property: booleanProperty, onChange: onChangeMock, displayOption: "dropdown" }));
|
19
|
+
const inputField = screen.getByRole('combobox');
|
20
|
+
await user.click(inputField);
|
21
|
+
const trueOption = await screen.findByRole('option', { name: 'True' });
|
22
|
+
await user.click(trueOption);
|
23
|
+
expect(onChangeMock).toBeCalledWith('theQuestion', true, booleanProperty);
|
24
|
+
});
|
25
|
+
it('renders checkbox', () => {
|
26
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
27
|
+
render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
|
28
|
+
const checkbox = screen.queryByRole('checkbox', { name: /Field label/i });
|
29
|
+
expect(checkbox).to.be.not.null;
|
30
|
+
});
|
31
|
+
it('renders switch', () => {
|
32
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
33
|
+
render(React.createElement(BooleanSelect, { id: "switch", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "switch" }));
|
34
|
+
const switchComp = screen.queryByRole('checkbox', { name: /Field label/i });
|
35
|
+
expect(switchComp).to.be.not.null;
|
36
|
+
});
|
37
|
+
it('renders label', () => {
|
38
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
39
|
+
render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
|
40
|
+
const label = screen.queryByText(/Field label/i);
|
41
|
+
expect(label).to.be.not.null;
|
42
|
+
});
|
43
|
+
it('allows defaulting', () => {
|
44
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
45
|
+
render(React.createElement(BooleanSelect, { id: "checkbox", defaultValue: true, property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
|
46
|
+
const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
|
47
|
+
expect(checkbox.checked).to.be.true;
|
48
|
+
});
|
49
|
+
it('sets checkbox', async () => {
|
50
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
51
|
+
render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
|
52
|
+
const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
|
53
|
+
await user.click(checkbox);
|
54
|
+
expect(checkbox.checked).to.be.true;
|
55
|
+
expect(onChangeMock).toBeCalledWith('theQuestion', true, booleanProperty);
|
56
|
+
});
|
57
|
+
it('unsets checkbox', async () => {
|
58
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
59
|
+
render(React.createElement(BooleanSelect, { id: "checkbox", defaultValue: true, property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
|
60
|
+
const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
|
61
|
+
await user.click(checkbox);
|
62
|
+
expect(checkbox.checked).to.be.false;
|
63
|
+
});
|
22
64
|
});
|
@@ -29,8 +29,11 @@ export type FormFieldProps = {
|
|
29
29
|
getOptionLabel?: (option: AutocompleteOption) => string;
|
30
30
|
disableCloseOnSelect?: boolean;
|
31
31
|
additionalProps?: Record<string, unknown>;
|
32
|
-
displayOption?: 'dropdown' | 'radioButton';
|
32
|
+
displayOption?: 'dropdown' | 'radioButton' | 'switch' | 'checkbox';
|
33
33
|
sortBy?: 'ASC' | 'DESC' | 'NONE';
|
34
|
+
label?: string;
|
35
|
+
description?: string;
|
36
|
+
tooltip?: string;
|
34
37
|
};
|
35
38
|
declare const FormField: (props: FormFieldProps) => React.JSX.Element;
|
36
39
|
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, displayOption, sortBy, } = 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, label, description, tooltip, } = props;
|
12
12
|
let control;
|
13
13
|
const commonProps = {
|
14
14
|
id: id ?? property.id,
|
@@ -32,6 +32,9 @@ const FormField = (props) => {
|
|
32
32
|
additionalProps,
|
33
33
|
displayOption,
|
34
34
|
sortBy,
|
35
|
+
label,
|
36
|
+
description,
|
37
|
+
tooltip,
|
35
38
|
};
|
36
39
|
if (queryAddresses) {
|
37
40
|
control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
|
@@ -39,7 +42,7 @@ const FormField = (props) => {
|
|
39
42
|
}
|
40
43
|
switch (property.type) {
|
41
44
|
case 'boolean':
|
42
|
-
control = React.createElement(BooleanSelect, { ...commonProps
|
45
|
+
control = React.createElement(BooleanSelect, { ...commonProps });
|
43
46
|
break;
|
44
47
|
case 'date':
|
45
48
|
control = React.createElement(DatePickerSelect, { ...commonProps });
|
@@ -1,9 +1,22 @@
|
|
1
1
|
import React, { ReactNode } from 'react';
|
2
2
|
export type MenuBarProps = {
|
3
|
-
|
3
|
+
/** The URL of the logo image to display. */
|
4
4
|
logo: string;
|
5
|
+
/** Alternative text for the logo image. */
|
5
6
|
logoAltText?: string;
|
7
|
+
/** Navigation items to display on the right side of the menu bar. */
|
6
8
|
navItems?: ReactNode;
|
9
|
+
/** The name of the environment to display instead of the logo. */
|
7
10
|
envName?: string;
|
11
|
+
/** Navigation item to display before the logo. */
|
12
|
+
navBeforeLogo?: ReactNode;
|
13
|
+
/** Indicates whether to show notifications. Currently not supported. */
|
14
|
+
showNotifications?: boolean;
|
8
15
|
};
|
16
|
+
/**
|
17
|
+
* Renders a customizable menu bar with navigation items, logo, etc.
|
18
|
+
*
|
19
|
+
* @param {MenuBarProps} props - Configuration props for the MenuBar component.
|
20
|
+
* @returns {JSX.Element} A menu bar component.
|
21
|
+
*/
|
9
22
|
export default function MenuBar(props: MenuBarProps): React.JSX.Element;
|
@@ -1,6 +1,12 @@
|
|
1
1
|
import { AppBar, Box, CardMedia, Toolbar, Typography } from '@mui/material';
|
2
2
|
import React from 'react';
|
3
3
|
import UIThemeProvider, { useResponsive } from '../../../theme';
|
4
|
+
/**
|
5
|
+
* Renders a customizable menu bar with navigation items, logo, etc.
|
6
|
+
*
|
7
|
+
* @param {MenuBarProps} props - Configuration props for the MenuBar component.
|
8
|
+
* @returns {JSX.Element} A menu bar component.
|
9
|
+
*/
|
4
10
|
export default function MenuBar(props) {
|
5
11
|
const { isXs: isMobileView } = useResponsive();
|
6
12
|
const classes = {
|
@@ -9,7 +15,7 @@ export default function MenuBar(props) {
|
|
9
15
|
flexGrow: 1,
|
10
16
|
},
|
11
17
|
logo: {
|
12
|
-
|
18
|
+
padding: '0 16px',
|
13
19
|
height: '70px',
|
14
20
|
maxWidth: isMobileView ? '220px' : null,
|
15
21
|
objectFit: 'contain',
|
@@ -18,7 +24,8 @@ export default function MenuBar(props) {
|
|
18
24
|
};
|
19
25
|
return (React.createElement(UIThemeProvider, null,
|
20
26
|
React.createElement(AppBar, { color: "inherit", position: "fixed", elevation: 0, sx: { zIndex: (theme) => theme.zIndex.drawer + 1, borderBottom: '1px solid #919EAB3D' } },
|
21
|
-
React.createElement(Toolbar, { sx: { justifyContent: 'space-between' } },
|
27
|
+
React.createElement(Toolbar, { disableGutters: true, sx: { justifyContent: 'space-between', overflow: 'hidden', paddingRight: '14px' } },
|
28
|
+
props.navBeforeLogo && (React.createElement(Box, { sx: { height: '70px', borderRight: '1px solid #919EAB3D' } }, props.navBeforeLogo)),
|
22
29
|
React.createElement(Box, { sx: classes.title }, !props.envName ? (React.createElement(CardMedia, { component: 'img', src: props.logo, alt: props.logoAltText, sx: classes.logo })) : (React.createElement(Box, { mt: 2 },
|
23
30
|
React.createElement(Typography, { variant: "h5" },
|
24
31
|
props?.envName,
|
@@ -11,3 +11,8 @@ it('render Menubar component without crashing', () => {
|
|
11
11
|
render(React.createElement(Menubar, { showNotifications: true, navItems: navItems, logo: "" }));
|
12
12
|
expect(screen.getAllByRole('link')).toHaveLength(3);
|
13
13
|
});
|
14
|
+
it('renders navBeforeLogo correctly', () => {
|
15
|
+
const navBeforeLogo = React.createElement(Link, { href: "#" }, "Before");
|
16
|
+
render(React.createElement(Menubar, { showNotifications: true, navItems: null, navBeforeLogo: navBeforeLogo, logo: "" }));
|
17
|
+
expect(screen.getAllByRole('link')).toHaveLength(1);
|
18
|
+
});
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import { BoxProps } from '@mui/material';
|
1
|
+
import { BoxProps as MUIBoxProps } from '@mui/material';
|
2
2
|
import React, { ElementType } from 'react';
|
3
|
-
type
|
3
|
+
export type BoxProps = MUIBoxProps & {
|
4
4
|
sx?: object;
|
5
5
|
component?: ElementType;
|
6
6
|
};
|
7
|
-
declare const Box:
|
7
|
+
declare const Box: React.ForwardRefExoticComponent<Omit<BoxProps, "ref"> & React.RefAttributes<unknown>>;
|
8
8
|
export default Box;
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import { Box as MUIBox } from '@mui/material';
|
2
|
-
import React from 'react';
|
2
|
+
import React, { forwardRef } from 'react';
|
3
3
|
import UIThemeProvider from '../../../theme';
|
4
|
-
const Box = (props) => {
|
4
|
+
const Box = forwardRef((props, ref) => {
|
5
5
|
return (React.createElement(UIThemeProvider, null,
|
6
|
-
React.createElement(MUIBox, { ...props })));
|
7
|
-
};
|
6
|
+
React.createElement(MUIBox, { ref: ref, ...props })));
|
7
|
+
});
|
8
8
|
export default Box;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { SvgIcon } from '@mui/material';
|
2
|
+
import React from 'react';
|
3
|
+
export const NoNavigation = (props) => (React.createElement(SvgIcon, { ...props, viewBox: '0 0 24 24', sx: {
|
4
|
+
verticalAlign: 'top',
|
5
|
+
maxWidth: props.width ?? '24',
|
6
|
+
maxHeight: props.height ?? '24',
|
7
|
+
...(props.sx ?? {}),
|
8
|
+
} },
|
9
|
+
React.createElement("g", { id: "icon/no_navigation" },
|
10
|
+
React.createElement("rect", { x: "2.75", y: "4.75", width: "18.5", height: "14.5", rx: "1.25", stroke: props?.htmlColor ?? '#212B36', strokeWidth: "1.5", fill: "transparent" }))));
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { SvgIcon } from '@mui/material';
|
2
|
+
import React from 'react';
|
3
|
+
export const SideNavigation = (props) => (React.createElement(SvgIcon, { ...props, viewBox: '0 0 24 24', sx: {
|
4
|
+
verticalAlign: 'top',
|
5
|
+
maxWidth: props.width ?? '24',
|
6
|
+
maxHeight: props.height ?? '24',
|
7
|
+
...(props.sx ?? {}),
|
8
|
+
} },
|
9
|
+
React.createElement("g", { id: "icon/side_navigation" },
|
10
|
+
React.createElement("rect", { x: "2.75", y: "4.75", width: "18.5", height: "14.5", rx: "1.25", stroke: props?.htmlColor ?? '#212B36', strokeWidth: "1.5", fill: "transparent" }),
|
11
|
+
React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M4 4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H11V4H4ZM4.5 6C4.22386 6 4 6.22386 4 6.5C4 6.77614 4.22386 7 4.5 7H8.5C8.77614 7 9 6.77614 9 6.5C9 6.22386 8.77614 6 8.5 6H4.5ZM4 8.5C4 8.22386 4.22386 8 4.5 8H7.5C7.77614 8 8 8.22386 8 8.5C8 8.77614 7.77614 9 7.5 9H4.5C4.22386 9 4 8.77614 4 8.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H8.5C8.77614 11 9 10.7761 9 10.5C9 10.2239 8.77614 10 8.5 10H4.5ZM4 14.5C4 14.2239 4.22386 14 4.5 14H8.5C8.77614 14 9 14.2239 9 14.5C9 14.7761 8.77614 15 8.5 15H4.5C4.22386 15 4 14.7761 4 14.5ZM4.5 12C4.22386 12 4 12.2239 4 12.5C4 12.7761 4.22386 13 4.5 13H7.5C7.77614 13 8 12.7761 8 12.5C8 12.2239 7.77614 12 7.5 12H4.5Z", fill: props?.htmlColor ?? '#212B36' }))));
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { SvgIcon } from '@mui/material';
|
2
|
+
import React from 'react';
|
3
|
+
export const TopNavigation = (props) => (React.createElement(SvgIcon, { ...props, viewBox: '0 0 24 24', sx: {
|
4
|
+
verticalAlign: 'top',
|
5
|
+
maxWidth: props.width ?? '24',
|
6
|
+
maxHeight: props.height ?? '24',
|
7
|
+
...(props.sx ?? {}),
|
8
|
+
} },
|
9
|
+
React.createElement("g", { id: "icon/top_navigation" },
|
10
|
+
React.createElement("rect", { x: "2.75", y: "4.75", width: "18.5", height: "14.5", rx: "1.25", stroke: props?.htmlColor ?? '#212B36', "stroke-width": "1.5", "stroke-linejoin": "round", fill: "transparent" }),
|
11
|
+
React.createElement("path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M22 5.66667C22 4.74619 21.2538 4 20.3333 4H3.66667C2.74619 4 2 4.74619 2 5.66667V9L22 9V5.66667ZM4.5 6C4.22386 6 4 6.22386 4 6.5C4 6.77614 4.22386 7 4.5 7H7.5C7.77614 7 8 6.77614 8 6.5C8 6.22386 7.77614 6 7.5 6H4.5ZM9 6.5C9 6.22386 9.22386 6 9.5 6H11.5C11.7761 6 12 6.22386 12 6.5C12 6.77614 11.7761 7 11.5 7H9.5C9.22386 7 9 6.77614 9 6.5ZM13.5 6C13.2239 6 13 6.22386 13 6.5C13 6.77614 13.2239 7 13.5 7H16.5C16.7761 7 17 6.77614 17 6.5C17 6.22386 16.7761 6 16.5 6H13.5Z", fill: props?.htmlColor ?? '#212B36' }))));
|
@@ -1,5 +1,8 @@
|
|
1
1
|
export * from './FileWithExtension';
|
2
2
|
export * from './Inherited';
|
3
|
+
export * from './NoNavigation';
|
3
4
|
export * from './Overrides';
|
5
|
+
export * from './SideNavigation';
|
6
|
+
export * from './TopNavigation';
|
4
7
|
export * from './TrashCan';
|
5
8
|
export * from './UploadCloud';
|
@@ -1,5 +1,8 @@
|
|
1
1
|
export * from './FileWithExtension';
|
2
2
|
export * from './Inherited';
|
3
|
+
export * from './NoNavigation';
|
3
4
|
export * from './Overrides';
|
5
|
+
export * from './SideNavigation';
|
6
|
+
export * from './TopNavigation';
|
4
7
|
export * from './TrashCan';
|
5
8
|
export * from './UploadCloud';
|
@@ -1,15 +1,6 @@
|
|
1
1
|
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
2
2
|
import React from 'react';
|
3
|
-
|
4
|
-
|
5
|
-
}, keyof import("@mui/material/OverridableComponent").CommonProps | keyof import("@mui/system").BoxOwnProps<import("@mui/material").Theme>> & {
|
6
|
-
sx?: object | undefined;
|
7
|
-
component?: React.ElementType<any, keyof React.JSX.IntrinsicElements> | undefined;
|
8
|
-
}) => React.JSX.Element>;
|
3
|
+
import { BoxProps } from '../components/layout/Box/Box';
|
4
|
+
declare const _default: ComponentMeta<React.ForwardRefExoticComponent<Omit<BoxProps, "ref"> & React.RefAttributes<unknown>>>;
|
9
5
|
export default _default;
|
10
|
-
export declare const Box: ComponentStory<
|
11
|
-
ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
|
12
|
-
}, keyof import("@mui/material/OverridableComponent").CommonProps | keyof import("@mui/system").BoxOwnProps<import("@mui/material").Theme>> & {
|
13
|
-
sx?: object | undefined;
|
14
|
-
component?: React.ElementType<any, keyof React.JSX.IntrinsicElements> | undefined;
|
15
|
-
}) => React.JSX.Element>;
|
6
|
+
export declare const Box: ComponentStory<React.ForwardRefExoticComponent<Omit<BoxProps, "ref"> & React.RefAttributes<unknown>>>;
|
@@ -95,6 +95,12 @@ const defaultProperties = [
|
|
95
95
|
type: 'integer',
|
96
96
|
required: false,
|
97
97
|
},
|
98
|
+
{
|
99
|
+
id: 'canApply',
|
100
|
+
name: 'Can Apply',
|
101
|
+
type: 'boolean',
|
102
|
+
required: true,
|
103
|
+
},
|
98
104
|
];
|
99
105
|
const CriteriaBuilderTemplate = (args) => {
|
100
106
|
const [criteria, setCriteria] = React.useState(args.criteria);
|