Skip to content

Commit d59b083

Browse files
authored
Merge pull request #851 from skateman/carbon-select-searchclear
feat(carbon): Implement searchable/clearable non-multi select
2 parents 4dded57 + b236b93 commit d59b083

File tree

3 files changed

+113
-60
lines changed

3 files changed

+113
-60
lines changed

packages/carbon-component-mapper/src/files/select.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useFieldApi } from '@data-driven-forms/react-form-renderer';
55
import DataDrivenSelect from '@data-driven-forms/common/src/select';
66
import fnToString from '@data-driven-forms/common/src/utils/fn-to-string';
77

8-
import { Select as CarbonSelect, MultiSelect, SelectItem } from 'carbon-components-react';
8+
import { Select as CarbonSelect, MultiSelect, SelectItem, ComboBox } from 'carbon-components-react';
99
import prepareProps from '../common/prepare-props';
1010

1111
export const multiOnChange = (input, simpleValue) => ({ selectedItems }) => {
@@ -165,8 +165,63 @@ ClearedSelect.propTypes = {
165165
isClearable: PropTypes.bool
166166
};
167167

168+
const ClearedSelectSearchable = ({
169+
isSearchable,
170+
isClearable,
171+
isDisabled,
172+
isMulti,
173+
invalidText,
174+
hideSelectedOptions,
175+
noOptionsMessage,
176+
onInputChange,
177+
options,
178+
isFetching,
179+
invalid,
180+
classNamePrefix,
181+
closeMenuOnSelect,
182+
originalOnChange,
183+
placeholder,
184+
labelText,
185+
...rest
186+
}) => (
187+
<ComboBox
188+
disabled={isFetching}
189+
{...rest}
190+
id={rest.name}
191+
invalid={Boolean(invalidText)}
192+
invalidText={invalidText}
193+
initialSelectedItem={rest.value}
194+
items={options}
195+
placeholder={placeholder}
196+
titleText={labelText}
197+
onChange={originalOnChange}
198+
/>
199+
);
200+
201+
ClearedSelectSearchable.propTypes = {
202+
invalidText: PropTypes.node,
203+
hideSelectedOptions: PropTypes.any,
204+
noOptionsMessage: PropTypes.any,
205+
onInputChange: PropTypes.func,
206+
options: PropTypes.array,
207+
isFetching: PropTypes.bool,
208+
invalid: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
209+
isMulti: PropTypes.bool,
210+
classNamePrefix: PropTypes.any,
211+
closeMenuOnSelect: PropTypes.any,
212+
onChange: PropTypes.func,
213+
originalOnChange: PropTypes.func,
214+
carbonLabel: PropTypes.node,
215+
placeholder: PropTypes.node,
216+
isDisabled: PropTypes.bool,
217+
isRequired: PropTypes.bool,
218+
isSearchable: PropTypes.bool,
219+
isClearable: PropTypes.bool,
220+
labelText: PropTypes.string
221+
};
222+
168223
const Select = (props) => {
169-
const { isMulti, isSearchable, loadOptions, input, meta, validateOnMount, ...rest } = useFieldApi(prepareProps(props));
224+
const { isMulti, isSearchable, isClearable, loadOptions, input, meta, validateOnMount, ...rest } = useFieldApi(prepareProps(props));
170225

171226
const [loadOptionsChangeCounter, setCounter] = useState(0);
172227

@@ -176,8 +231,10 @@ const Select = (props) => {
176231
setCounter(loadOptionsChangeCounter + 1);
177232
// eslint-disable-next-line react-hooks/exhaustive-deps
178233
}, [loadOptionsStr]);
234+
const isSearchClear = isSearchable || isClearable;
179235

180-
const Component = isMulti && isSearchable ? ClearedMultiSelectFilterable : isMulti ? ClearedMultiSelect : ClearedSelect;
236+
const Component =
237+
isMulti && isSearchClear ? ClearedMultiSelectFilterable : isMulti ? ClearedMultiSelect : isSearchClear ? ClearedSelectSearchable : ClearedSelect;
181238

182239
const invalidText = ((meta.touched || validateOnMount) && meta.error) || '';
183240

packages/carbon-component-mapper/src/tests/select.test.js

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import FormRenderer, { componentTypes } from '@data-driven-forms/react-form-rend
55

66
import FormTemplate from '../files/form-template';
77
import componentMapper from '../files/component-mapper';
8-
import { Select, MultiSelect } from 'carbon-components-react';
8+
import { Select, MultiSelect, ComboBox } from 'carbon-components-react';
99
import { multiOnChange } from '../files/select';
1010

1111
describe('<Select />', () => {
@@ -31,6 +31,31 @@ describe('<Select />', () => {
3131
expect(wrapper.find(Select)).toHaveLength(1);
3232
});
3333

34+
['isSearchable', 'isClearable'].forEach((setting) => {
35+
it(`renders select ${setting}`, () => {
36+
const schema = {
37+
fields: [
38+
{
39+
component: componentTypes.SELECT,
40+
name: 'select',
41+
label: 'select',
42+
[setting]: true,
43+
options: [
44+
{ label: 'option 1', value: 1 },
45+
{ label: 'option 2', value: 2 }
46+
]
47+
}
48+
]
49+
};
50+
51+
const wrapper = mount(
52+
<FormRenderer onSubmit={jest.fn()} FormTemplate={(props) => <FormTemplate {...props} />} schema={schema} componentMapper={componentMapper} />
53+
);
54+
55+
expect(wrapper.find(ComboBox)).toHaveLength(1);
56+
});
57+
});
58+
3459
it('renders multi select', () => {
3560
const schema = {
3661
fields: [
@@ -55,29 +80,31 @@ describe('<Select />', () => {
5580
expect(wrapper.find(MultiSelect)).toHaveLength(1);
5681
});
5782

58-
it('renders multi select - searchable', () => {
59-
const schema = {
60-
fields: [
61-
{
62-
component: componentTypes.SELECT,
63-
name: 'select',
64-
label: 'select',
65-
initialValue: [1],
66-
isMulti: true,
67-
isSearchable: true,
68-
options: [
69-
{ label: 'option 1', value: 1 },
70-
{ label: 'option 2', value: 2 }
71-
]
72-
}
73-
]
74-
};
75-
76-
const wrapper = mount(
77-
<FormRenderer onSubmit={jest.fn()} FormTemplate={(props) => <FormTemplate {...props} />} schema={schema} componentMapper={componentMapper} />
78-
);
79-
80-
expect(wrapper.find(MultiSelect.Filterable)).toHaveLength(1);
83+
['isSearchable', 'isClearable'].forEach((setting) => {
84+
it(`renders multi select - ${setting}`, () => {
85+
const schema = {
86+
fields: [
87+
{
88+
component: componentTypes.SELECT,
89+
name: 'select',
90+
label: 'select',
91+
initialValue: [1],
92+
isMulti: true,
93+
[setting]: true,
94+
options: [
95+
{ label: 'option 1', value: 1 },
96+
{ label: 'option 2', value: 2 }
97+
]
98+
}
99+
]
100+
};
101+
102+
const wrapper = mount(
103+
<FormRenderer onSubmit={jest.fn()} FormTemplate={(props) => <FormTemplate {...props} />} schema={schema} componentMapper={componentMapper} />
104+
);
105+
106+
expect(wrapper.find(MultiSelect.Filterable)).toHaveLength(1);
107+
});
81108
});
82109

83110
describe('multichange', () => {

packages/react-renderer-demo/src/pages/mappers/carbon-component-mapper.md

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,41 +38,10 @@ This field will show the error immediately.
3838

3939
## Select
4040

41-
### No isClearable
41+
### isClearable and isSearchable turn on each other
4242

43-
Carbon select does not support `isClearable` option, instead of it use an option with null.
43+
Carbon doesn't provide a component that is searchable but not clearable and vice versa. Therefore, if one of these two options is turned on, it automatically triggers the other.
4444

45-
🛑
46-
47-
```jsx
48-
{
49-
component: 'select',
50-
label: 'select',
51-
isClearable: true,
52-
options: [
53-
{value: 'option 1', label: 'first option'},
54-
{value: 'option 2', label: 'second option'}
55-
]
56-
}
57-
```
58-
59-
🆗
60-
61-
```jsx
62-
{
63-
component: 'select',
64-
label: 'select',
65-
options: [
66-
{label: 'none', value: null},
67-
{value: 'option 1', label: 'first option'},
68-
{value: 'option 2', label: 'second option'}
69-
]
70-
}
71-
```
72-
73-
### Single select cannot be isSearchable
74-
75-
No known workaround.
7645

7746
### No async filtering options in multiselect
7847

0 commit comments

Comments
 (0)