@evoke-platform/ui-components 1.5.1-testing.0 → 1.6.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
- import { ErrorRounded, Help, HighlightOffOutlined } from '../../../../icons';
2
+ import { ErrorRounded, Help } from '../../../../icons';
3
3
  import { IconButton, Tooltip, Typography } from '../../../core';
4
4
  import { Box } from '../../../layout';
5
5
  const underFieldStyles = {
@@ -13,11 +13,6 @@ 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
- };
21
16
  const PrefixSuffix = (props) => {
22
17
  const { prefix, suffix, height } = props;
23
18
  const text = prefix || suffix;
@@ -51,7 +46,7 @@ const PrefixSuffix = (props) => {
51
46
  * description, tooltip, prefix, suffix and word/char counts
52
47
  */
53
48
  export const FormComponentWrapper = (props) => {
54
- const { inputId, label, description, tooltip, prefix, suffix, value, validate, errorMessage, showCharCount, type, viewOnly, children, displayOption, onChange, property, readOnly, } = props;
49
+ const { inputId, label, description, tooltip, prefix, suffix, value, validate, errorMessage, showCharCount, type, viewOnly, children, displayOption, property, } = props;
55
50
  const [fieldHeight, setFieldHeight] = useState(40);
56
51
  const { maxLength } = validate;
57
52
  const fieldRef = useRef(null);
@@ -76,12 +71,6 @@ export const FormComponentWrapper = (props) => {
76
71
  tooltip && (React.createElement(Tooltip, { placement: "right", title: tooltip },
77
72
  React.createElement(IconButton, null,
78
73
  React.createElement(Help, { sx: { fontSize: '14px' } }))))),
79
- displayOption === 'radioButton' && onChange && !viewOnly && !readOnly && value && (React.createElement(Tooltip, { title: `Clear` },
80
- React.createElement("span", null,
81
- React.createElement(IconButton, { "aria-label": `Clear`, sx: { padding: '0px' }, onClick: () => {
82
- property && onChange(property.id, '');
83
- } },
84
- React.createElement(HighlightOffOutlined, { sx: clearBtnStyles }))))),
85
74
  React.createElement(Box, { sx: { ...(displayOption === 'radioButton' && { display: 'flex' }) } },
86
75
  React.createElement(Typography, { variant: "caption", sx: descriptionStyles }, description)))),
87
76
  React.createElement(Box, { sx: { display: 'flex', flexDirection: 'row' } },
@@ -12,6 +12,7 @@ type FormFieldComponentProps = Omit<BaseFormComponentProps, 'property'> & {
12
12
  initialValue?: string;
13
13
  displayOption?: 'radioButton' | 'dropdown';
14
14
  strictlyTrue?: boolean;
15
+ nonStrictEnum?: boolean;
15
16
  };
16
17
  export declare class FormFieldComponent extends ReactComponent {
17
18
  [x: string]: any;
@@ -485,6 +485,6 @@ export class FormFieldComponent extends ReactComponent {
485
485
  falsePositiveMaskError &&
486
486
  isEmpty(this.errorDetails) &&
487
487
  this.emit('changed-' + this.component.key, e.target.value);
488
- }, ...this.component, id: inputId, defaultValue: this.dataValue, mask: this.component.inputMask, error: this.hasErrors(), size: this.component.fieldHeight ?? 'medium', required: this.component.property.type === 'boolean' ? this.component.strictlyTrue : undefined }))), root);
488
+ }, ...this.component, id: inputId, defaultValue: this.dataValue, mask: this.component.inputMask, error: this.hasErrors(), size: this.component.fieldHeight ?? 'medium', required: this.component.property.type === 'boolean' ? this.component.strictlyTrue : undefined, isCombobox: this.component.nonStrictEnum }))), root);
489
489
  }
490
490
  }
@@ -165,6 +165,7 @@ export function convertFormToComponents(entries, parameters, object) {
165
165
  data: {
166
166
  values: entry.type === 'input' ? entry.enumWithLabels : undefined,
167
167
  },
168
+ nonStrictEnum: parameter.nonStrictEnum,
168
169
  property: property,
169
170
  defaultToCurrentTime: displayOptions?.defaultValue === 'currentTime' && parameter.type === 'time' ? true : false,
170
171
  defaultToCurrentDate: displayOptions?.defaultValue === 'currentDate' && parameter.type === 'date' ? true : false,
@@ -47,7 +47,7 @@ const BooleanSelect = (props) => {
47
47
  }
48
48
  else {
49
49
  const iconColor = 'rgb(160, 160, 160)';
50
- return (React.createElement(FormControl, { disabled: true, fullWidth: true },
50
+ return (React.createElement(FormControl, { disabled: true, required: strictlyTrue, fullWidth: true },
51
51
  React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), sx: { gap: 1, marginLeft: 0 }, control: value ? (displayOption === 'checkbox' ? (React.createElement(CheckBoxIcon, { fontSize: size ?? 'medium', "aria-label": "Checked", htmlColor: iconColor })) : (React.createElement(ToggleOn, { fontSize: size ?? 'medium', "aria-label": "Checked", htmlColor: iconColor }))) : displayOption === 'checkbox' ? (React.createElement(CheckBoxOutlineBlank, { fontSize: size ?? 'medium', "aria-label": "Unchecked", htmlColor: iconColor })) : (React.createElement(ToggleOff, { fontSize: size ?? 'medium', "aria-label": "Unchecked", htmlColor: iconColor })) }),
52
52
  descriptionComponent()));
53
53
  }
@@ -35,6 +35,7 @@ export type FormFieldProps = {
35
35
  label?: string;
36
36
  description?: string;
37
37
  tooltip?: string;
38
+ isCombobox?: boolean;
38
39
  };
39
40
  declare const FormField: (props: FormFieldProps) => React.JSX.Element;
40
41
  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, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, } = props;
11
+ const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, isCombobox, } = props;
12
12
  let control;
13
13
  const commonProps = {
14
14
  id: id ?? property.id,
@@ -36,6 +36,7 @@ const FormField = (props) => {
36
36
  label,
37
37
  description,
38
38
  tooltip,
39
+ isCombobox,
39
40
  };
40
41
  if (queryAddresses) {
41
42
  control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
@@ -1,23 +1,56 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Autocomplete, FormControl, FormControlLabel, Radio, RadioGroup, TextField, } from '../../../core';
1
+ import { createFilterOptions, List, ListSubheader } from '@mui/material';
2
+ import React, { forwardRef, useEffect, useRef, useState } from 'react';
3
+ import { Clear } from '../../../../icons';
4
+ import { Autocomplete, FormControl, FormControlLabel, IconButton, Radio, RadioGroup, TextField, Typography, } from '../../../core';
5
+ import { Box } from '../../../layout';
3
6
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
7
+ const filter = createFilterOptions();
4
8
  const Select = (props) => {
5
- const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, selectOptions, required, size, isOptionEqualToValue, renderOption, getOptionLabel, disableCloseOnSelect, additionalProps, displayOption, sortBy, } = props;
9
+ const { id, property, defaultValue, error, errorMessage, onBlur, onChange, readOnly, isCombobox, selectOptions, required, size, isOptionEqualToValue, renderOption, getOptionLabel, disableCloseOnSelect, additionalProps, displayOption, sortBy, } = props;
10
+ const otherInputRef = useRef(null);
11
+ const [isOther, setIsOther] = useState(!!isCombobox &&
12
+ !!defaultValue &&
13
+ !selectOptions?.some((option) => (typeof option === 'string' && option === defaultValue) ||
14
+ option.value === defaultValue));
15
+ const [isOtherFocused, setIsOtherFocused] = useState(false);
6
16
  const [value, setValue] = useState(defaultValue);
7
- const [inputValue, setInputValue] = useState('');
17
+ const [inputValue, setInputValue] = useState(!selectOptions?.some((option) => (typeof option === 'string' && option === defaultValue) ||
18
+ option.value === defaultValue)
19
+ ? defaultValue
20
+ : '');
21
+ const [errorState, setErrorState] = useState();
8
22
  useEffect(() => {
9
- setValue(defaultValue);
10
- }, [defaultValue]);
23
+ setErrorState(isCombobox ? error && !isOtherFocused : error);
24
+ }, [error, isOtherFocused]);
25
+ useEffect(() => {
26
+ if (isOther && otherInputRef.current && value === '') {
27
+ otherInputRef.current.focus();
28
+ }
29
+ }, [isOther, value]);
11
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
31
  const handleChange = (event, selected) => {
13
32
  if (Array.isArray(selected)) {
14
- setValue(selected.map((option) => option.value ?? option));
15
- props.onChange &&
16
- props.onChange(property.id, selected.map((option) => option.value ?? option), property);
33
+ const newValues = selected.map((option) => option.value ?? option);
34
+ setValue(newValues);
35
+ onChange && onChange(property.id, newValues, property);
17
36
  }
18
37
  else {
19
- setValue(selected);
20
- props.onChange && props.onChange(property.id, selected, property);
38
+ if (isCombobox && displayOption === 'radioButton') {
39
+ if (selected === '__other__') {
40
+ setIsOther(true);
41
+ setValue('');
42
+ }
43
+ else {
44
+ setIsOther(false);
45
+ setValue(selected);
46
+ setInputValue('');
47
+ onChange && onChange(property.id, selected, property);
48
+ }
49
+ }
50
+ else {
51
+ setValue(selected);
52
+ onChange && onChange(property.id, selected, property);
53
+ }
21
54
  }
22
55
  };
23
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -29,6 +62,10 @@ const Select = (props) => {
29
62
  setInputValue(selectValue);
30
63
  }
31
64
  };
65
+ const ListboxComponent = forwardRef(function ListboxComponent(props, ref) {
66
+ const { children, ...other } = props;
67
+ return (React.createElement(List, { component: "ul", ...other, ref: ref, subheader: isCombobox ? (React.createElement(ListSubheader, { component: 'div', id: `${id}-instructions`, role: "note", sx: { lineHeight: '36px', borderBottom: '1px solid #919EAB3D' } }, "Select below or type and click Enter to add a new item.")) : undefined }, children));
68
+ });
32
69
  const sortedOptions = (() => {
33
70
  if (!selectOptions)
34
71
  return [];
@@ -42,21 +79,84 @@ const Select = (props) => {
42
79
  return options.sort((a, b) => typeof a === 'string' ? a.localeCompare(b) : a.label.localeCompare(b.label));
43
80
  }
44
81
  })();
45
- return readOnly ? (React.createElement(InputFieldComponent, { ...props })) : displayOption === 'radioButton' ? (React.createElement(FormControl, { error: error, required: required },
46
- 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: {
47
- ...(size === 'small' && { paddingTop: '2px', paddingBottom: '2px' }),
48
- ...(error && {
49
- color: '#FF4842',
50
- '&.Mui-checked': { color: '#FF4842' },
51
- }),
52
- } }), 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
82
+ return readOnly ? (React.createElement(InputFieldComponent, { ...props })) : displayOption === 'radioButton' ? (React.createElement(React.Fragment, null,
83
+ React.createElement(FormControl, { error: error, required: required },
84
+ React.createElement(RadioGroup, { name: `radioGroup-${id}`, value: isOther ? '__other__' : value, onChange: handleChange },
85
+ sortedOptions.map((option, index) => (React.createElement(FormControlLabel, { key: index, sx: { margin: '3px 0' }, value: typeof option === 'string' ? option : option.value, control: React.createElement(Radio, { size: "small", sx: {
86
+ padding: '3px',
87
+ ...(index === 0 && {
88
+ paddingTop: 0,
89
+ }),
90
+ ...(!!errorState && {
91
+ color: '#FF4842',
92
+ '&.Mui-checked': { color: '#FF4842' },
93
+ }),
94
+ } }), label: typeof option === 'string' ? option : option.label }))),
95
+ isCombobox && (React.createElement(React.Fragment, null,
96
+ React.createElement(FormControlLabel, { value: '__other__', sx: { margin: '3px 0' }, control: React.createElement(Radio, { size: "small", sx: {
97
+ padding: '3px',
98
+ ...(!!errorState && {
99
+ color: '#FF4842',
100
+ '&.Mui-checked': { color: '#FF4842' },
101
+ }),
102
+ } }), label: React.createElement("span", { id: `other-radio-button-${id}` }, "Other") }),
103
+ isOther && (React.createElement(TextField, { id: `other-${id}`, inputProps: {
104
+ 'aria-labelledby': `other-radio-button-${id}`,
105
+ }, value: inputValue, inputRef: otherInputRef, error: !!errorState, sx: { marginLeft: '4px' }, onChange: (e) => {
106
+ setValue(e.target.value);
107
+ setInputValue(e.target.value);
108
+ onChange && onChange(property.id, e.target.value, property);
109
+ }, size: 'small', onFocus: () => setIsOtherFocused(true), onBlur: (e) => {
110
+ // If another radio button is focused, change the value to that radio button's value.
111
+ const targetElement = e.nativeEvent.relatedTarget;
112
+ if (targetElement?.defaultValue && targetElement.name === `radioGroup-${id}`) {
113
+ onChange && onChange(property.id, targetElement, property);
114
+ }
115
+ else {
116
+ onChange && onChange(property.id, e.target.value, property);
117
+ }
118
+ setIsOtherFocused(false);
119
+ } })))))),
120
+ displayOption === 'radioButton' && onChange && !readOnly && value && (React.createElement(Box, { sx: {
121
+ ':hover': { cursor: 'pointer' },
122
+ marginTop: '4px',
123
+ display: 'flex',
124
+ alignItems: 'center',
125
+ } },
126
+ React.createElement(IconButton, { "aria-label": `Clear`, onClick: () => {
127
+ setValue('');
128
+ property && onChange(property.id, '');
129
+ setIsOther(false);
130
+ }, sx: { padding: '3px', marginRight: '6px' } },
131
+ React.createElement(Clear, { sx: {
132
+ color: '#637381',
133
+ fontSize: '1.2rem',
134
+ } })),
135
+ React.createElement(Typography, { variant: "caption" }, "Clear Selection"))))) : (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, inputProps: {
136
+ ...params.inputProps,
137
+ 'aria-describedby': isCombobox ? `${id}-instructions` : undefined,
138
+ } })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
139
+ const filtered = filter(options, params);
140
+ const { inputValue } = params;
141
+ // Suggest to the user to add a new value.
142
+ const found = options.some((option) => inputValue === option.label);
143
+ if (inputValue !== '' && !found && isCombobox) {
144
+ filtered.push({
145
+ value: inputValue,
146
+ label: `Add "${inputValue}"`,
147
+ });
148
+ }
149
+ return filtered;
150
+ }, isOptionEqualToValue: isOptionEqualToValue
53
151
  ? (option, value) => isOptionEqualToValue(option, value)
54
- : undefined, getOptionLabel: getOptionLabel ? (option) => getOptionLabel(option) : undefined, renderOption: renderOption
152
+ : undefined, getOptionLabel: getOptionLabel && !isCombobox
153
+ ? (option) => getOptionLabel(option)
154
+ : (option) => (typeof option === 'string' ? option : option.label), renderOption: renderOption
55
155
  ? (props, option, state) => renderOption(props, option, state)
56
- : undefined, disableCloseOnSelect: disableCloseOnSelect, sx: {
156
+ : undefined, ListboxComponent: ListboxComponent, disableCloseOnSelect: disableCloseOnSelect, sx: {
57
157
  '& button.MuiButtonBase-root': {
58
158
  visibility: 'visible',
59
159
  },
60
- }, ...(additionalProps ?? {}) }));
160
+ }, ...(isCombobox ? { selectOnFocus: true, handleHomeEndKeys: true, freeSolo: true } : {}), ...(additionalProps ?? {}) }));
61
161
  };
62
162
  export default Select;
@@ -14,8 +14,8 @@ describe('Single select', () => {
14
14
  type: 'choices',
15
15
  };
16
16
  it('returns selected option', async () => {
17
- const user = userEvent.setup();
18
17
  const onChangeMock = vi.fn((name, value, property) => { });
18
+ const user = userEvent.setup();
19
19
  const options = ['option 1', 'option 2', 'option 3'];
20
20
  render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: onChangeMock }));
21
21
  const input = screen.getByRole('combobox');
@@ -48,6 +48,21 @@ describe('Single select', () => {
48
48
  const optionLabels = allOptions.map((option) => option.textContent);
49
49
  expect(optionLabels).toEqual(expectedValues);
50
50
  });
51
+ it('allows the user to enter a custom value if it is combobox component', async () => {
52
+ const onChangeMock = vi.fn((name, value, property) => { });
53
+ const user = userEvent.setup();
54
+ const options = ['option 1', 'option 2', 'option 3'];
55
+ render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
56
+ const input = screen.getByRole('combobox');
57
+ await user.click(input);
58
+ // Verify the instruction text for a combobox displays as the sub-header of the combobox.
59
+ screen.getByText('Select below or type and click Enter to add a new item.');
60
+ await user.type(input, 'custom option');
61
+ // Verify the option to add the custom value is displayed as an available option in the dropdown.
62
+ const customOption = await screen.findByRole('option', { name: 'Add "custom option"' });
63
+ await user.click(customOption);
64
+ expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'Add "custom option"', value: 'custom option' }), choiceProperty);
65
+ });
51
66
  });
52
67
  describe('Multi select', () => {
53
68
  // Right now an object property is required for this to function, but eventually this should go
@@ -66,12 +81,49 @@ describe('Multi select', () => {
66
81
  await user.click(input);
67
82
  const option2 = await screen.findByRole('option', { name: 'option 2' });
68
83
  await user.click(option2);
69
- await user.click(input); // selecting option closes the dropdown, re-open the options
84
+ // selecting option closes the dropdown, re-open the options
85
+ await user.click(input);
70
86
  const option3 = await screen.findByRole('option', { name: 'option 3' });
71
87
  await user.click(option3);
72
88
  expect(onChangeMock).toBeCalledTimes(2);
73
89
  expect(onChangeMock).lastCalledWith('multiSelect', ['option 2', 'option 3'], multiChoiceProperty);
74
90
  });
91
+ it('allows the user to enter custom values if it is combobox component', async () => {
92
+ const onChangeMock = vi.fn((name, value, property) => { });
93
+ const user = userEvent.setup();
94
+ const options = ['option 1', 'option 2', 'option 3'];
95
+ render(React.createElement(Select, { id: "multiSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
96
+ const input = screen.getByRole('combobox');
97
+ await user.click(input);
98
+ // Verify the instruction text for a combobox displays as the sub-header of the combobox.
99
+ screen.getByText('Select below or type and click Enter to add a new item.');
100
+ await user.type(input, 'custom option 1');
101
+ const customOption1 = await screen.findByRole('option', { name: 'Add "custom option 1"' });
102
+ await user.click(customOption1);
103
+ await user.type(input, 'custom option 2');
104
+ const customOption2 = await screen.findByRole('option', { name: 'Add "custom option 2"' });
105
+ await user.click(customOption2);
106
+ expect(onChangeMock).toBeCalledTimes(2);
107
+ expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'custom option 2'], multiChoiceProperty);
108
+ });
109
+ it('allows the user to enter custom values in conjunction with the predefined options if it is combobox component', async () => {
110
+ const onChangeMock = vi.fn((name, value, property) => { });
111
+ const user = userEvent.setup();
112
+ const options = ['option 1', 'option 2', 'option 3'];
113
+ render(React.createElement(Select, { id: "multiSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true }));
114
+ const input = screen.getByRole('combobox');
115
+ await user.click(input);
116
+ // Verify the instruction text for a combobox displays as the sub-header of the combobox.
117
+ screen.getByText('Select below or type and click Enter to add a new item.');
118
+ await user.type(input, 'custom option 1');
119
+ const customOption1 = await screen.findByRole('option', { name: 'Add "custom option 1"' });
120
+ await user.click(customOption1);
121
+ await user.click(input);
122
+ const option1 = await screen.findByRole('option', { name: 'option 1' });
123
+ await user.click(option1);
124
+ expect(onChangeMock).toBeCalledTimes(2);
125
+ expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'option 1'], multiChoiceProperty);
126
+ });
75
127
  });
76
128
  describe('Radio Single select', () => {
77
129
  const choiceProperty = {
@@ -79,7 +131,7 @@ describe('Radio Single select', () => {
79
131
  name: 'Select Options',
80
132
  type: 'choices',
81
133
  };
82
- test('returns selected radio option', async () => {
134
+ it('returns selected radio option', async () => {
83
135
  const user = userEvent.setup();
84
136
  const onChangeMock = vi.fn((name, value, property) => { });
85
137
  const options = ['option 1', 'option 2', 'option 3'];
@@ -99,4 +151,21 @@ describe('Radio Single select', () => {
99
151
  const radioValues = radioButtons.map((radioButton) => radioButton.value);
100
152
  expect(radioValues).toEqual(expectedValues);
101
153
  });
154
+ it('renders an "Other" option in the radio group if the component is configured to support a custom value', async () => {
155
+ const onChangeMock = vi.fn((name, value, property) => { });
156
+ const options = ['option 1', 'option 2', 'option 3'];
157
+ render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock, isCombobox: true }));
158
+ await screen.findByRole('radio', { name: 'Other' });
159
+ });
160
+ it('renders a text field for a custom option if the "Other" option is selected', async () => {
161
+ const user = userEvent.setup();
162
+ const onChangeMock = vi.fn((name, value, property) => { });
163
+ const options = ['option 1', 'option 2', 'option 3'];
164
+ render(React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock, isCombobox: true }));
165
+ await screen.findByRole('radio', { name: 'Other' });
166
+ await user.click(screen.getByRole('radio', { name: 'Other' }));
167
+ const otherTextField = await screen.findByRole('textbox', { name: `Other` });
168
+ await user.type(otherTextField, 'custom option');
169
+ expect(onChangeMock).toBeCalledWith('selectOptions', expect.stringContaining('custom option'), choiceProperty);
170
+ });
102
171
  });
@@ -1,4 +1,4 @@
1
- export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
1
+ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMediaQuery, useTheme, } from '@mui/material';
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
3
  export * from './colors';
4
4
  export * from './components/core';
@@ -1,4 +1,4 @@
1
- export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
1
+ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMediaQuery, useTheme, } from '@mui/material';
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
3
  export * from './colors';
4
4
  export * from './components/core';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.5.1-testing.0",
3
+ "version": "1.6.0-dev.1",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",