Skip to content
This repository was archived by the owner on Oct 19, 2021. It is now read-only.

Commit 2753040

Browse files
emyarodasudoh
authored andcommitted
fix(ComboBox): match experimental spec (#2158)
* feat(ComboBox): add title and helper text support * feat(ComboBox): support invalid state * docs(ComboBox): add knob for light variant * refactor(ComboBox): match vanilla invalid state * fix(ListBox): pass aria label and role into menu * fix(ListBoxMenuIcon): add `role="button"` * fix(ListBox): require id for listbox menus * fix(ListBox): add aria attrs for listbox inputs * fix(ComboBox): pass `isOpen` to `<ListBox>` * fix(ListBoxField): set default negative tabindex * fix(ListBox): remove tabindex on wrapper div * test(ListBox): remove outdated tests * test(ListBoxField): test modifiable tabindex prop * chore: update snapshots * docs: add `ariaLabel` propType * test: pass in example IDs * docs(ListBox): remove unused propType * fix(Dropdown): remove unneeded ListBox prop * chore: update snapshots * fix: add IDs for Dropdown and Multiselect control * chore: update snapshots * fix(Dropdown): make listbox field tabbable * fix(MultiSelect): make listbox field tabbable * fix(FilterableMultiSelect): remove unused prop * chore: update snapshots * chore: bump carbon-components version
1 parent d869fef commit 2753040

22 files changed

Lines changed: 240 additions & 98 deletions

src/components/ComboBox/ComboBox-story.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,38 @@ import WithState from '../../tools/withState';
1414

1515
const items = [
1616
{
17-
id: 'option-1',
17+
id: 'option-0',
1818
text: 'Option 1',
1919
},
2020
{
21-
id: 'option-2',
21+
id: 'option-1',
2222
text: 'Option 2',
2323
},
2424
{
25-
id: 'option-3',
25+
id: 'option-2',
2626
text: 'Option 3',
27+
selected: true,
2728
},
2829
{
29-
id: 'option-4',
30+
id: 'option-3',
3031
text: 'Option 4',
3132
},
33+
{
34+
id: 'option-4',
35+
text:
36+
'An example option that is really long to show what should be done to handle long text',
37+
},
3238
];
3339

3440
const props = () => ({
35-
disabled: boolean('Disabled (disabled)', false),
41+
id: text('Combobox ID (id)', 'carbon-combobox-example'),
3642
placeholder: text('Placeholder text (placeholder)', 'Filter...'),
43+
titleText: text('Title (titleText)', 'Combobox title'),
44+
helperText: text('Helper text (helperText)', 'Optional helper text here'),
45+
light: boolean('Light (light)', false),
46+
disabled: boolean('Disabled (disabled)', false),
47+
invalid: boolean('Invalid (invalid)', false),
48+
invalidText: text('Invalid text (invalidText)', 'A valid value is required'),
3749
onChange: action('onChange'),
3850
});
3951

src/components/ComboBox/ComboBox.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import Downshift from 'downshift';
1010
import PropTypes from 'prop-types';
1111
import React from 'react';
1212
import { settings } from 'carbon-components';
13+
import WarningFilled16 from '@carbon/icons-react/lib/warning--filled/16';
1314
import ListBox, { PropTypes as ListBoxPropTypes } from '../ListBox';
15+
import { componentsX } from '../../internal/FeatureFlags';
1416

1517
const { prefix } = settings;
1618

@@ -51,6 +53,11 @@ const findHighlightedIndex = ({ items, itemToString }, inputValue) => {
5153

5254
export default class ComboBox extends React.Component {
5355
static propTypes = {
56+
/**
57+
* 'aria-label' of the ListBox component.
58+
*/
59+
ariaLabel: PropTypes.string,
60+
5461
/**
5562
* An optional className to add to the container node
5663
*/
@@ -64,7 +71,7 @@ export default class ComboBox extends React.Component {
6471
/**
6572
* Specify a custom `id` for the input
6673
*/
67-
id: PropTypes.string,
74+
id: PropTypes.string.isRequired,
6875

6976
/**
7077
* Allow users to pass in an arbitrary item or a string (in case their items are an array of strings)
@@ -147,7 +154,7 @@ export default class ComboBox extends React.Component {
147154
itemToString: defaultItemToString,
148155
shouldFilterItem: defaultShouldFilterItem,
149156
type: 'default',
150-
ariaLabel: 'ListBox input field',
157+
ariaLabel: 'Choose an item',
151158
light: false,
152159
};
153160

@@ -218,6 +225,8 @@ export default class ComboBox extends React.Component {
218225
id,
219226
items,
220227
itemToString,
228+
titleText,
229+
helperText,
221230
placeholder,
222231
initialSelectedItem,
223232
ariaLabel,
@@ -231,13 +240,25 @@ export default class ComboBox extends React.Component {
231240
onInputChange, // eslint-disable-line no-unused-vars
232241
...rest
233242
} = this.props;
234-
const className = cx(
235-
`${prefix}--form-item`,
236-
`${prefix}--combo-box`,
237-
containerClassName
238-
);
239-
240-
return (
243+
const className = cx(`${prefix}--combo-box`, containerClassName, {
244+
[`${prefix}--form-item`]: !componentsX,
245+
});
246+
const titleClasses = cx(`${prefix}--label`, {
247+
[`${prefix}--label--disabled`]: disabled,
248+
});
249+
const title = titleText ? (
250+
<label htmlFor={id} className={titleClasses}>
251+
{titleText}
252+
</label>
253+
) : null;
254+
const helperClasses = cx(`${prefix}--form__helper-text`, {
255+
[`${prefix}--form__helper-text--disabled`]: disabled,
256+
});
257+
const helper = helperText ? (
258+
<div className={helperClasses}>{helperText}</div>
259+
) : null;
260+
const wrapperClasses = cx(`${prefix}--list-box__wrapper`);
261+
const input = (
241262
<Downshift
242263
onChange={this.handleOnChange}
243264
onInputValueChange={this.handleOnInputValueChange}
@@ -260,16 +281,25 @@ export default class ComboBox extends React.Component {
260281
disabled={disabled}
261282
invalid={invalid}
262283
invalidText={invalidText}
284+
isOpen={isOpen}
263285
light={light}
264286
{...getRootProps({ refKey: 'innerRef' })}>
265287
<ListBox.Field
288+
id={id}
266289
{...getButtonProps({
267290
disabled,
268291
onClick: this.onToggleClick(isOpen),
269292
})}>
293+
{componentsX && invalid && (
294+
<WarningFilled16
295+
className={`${prefix}--list-box__invalid-icon`}
296+
/>
297+
)}
270298
<input
271299
className={`${prefix}--text-input`}
272300
aria-label={ariaLabel}
301+
aria-controls={`${id}__menu`}
302+
aria-autocomplete="list"
273303
ref={this.textInput}
274304
{...rest}
275305
{...getInputProps({
@@ -291,7 +321,7 @@ export default class ComboBox extends React.Component {
291321
/>
292322
</ListBox.Field>
293323
{isOpen && (
294-
<ListBox.Menu>
324+
<ListBox.Menu aria-label={ariaLabel} id={id}>
295325
{this.filterItems(items, itemToString, inputValue).map(
296326
(item, index) => (
297327
<ListBox.MenuItem
@@ -313,5 +343,15 @@ export default class ComboBox extends React.Component {
313343
)}
314344
</Downshift>
315345
);
346+
347+
return componentsX ? (
348+
<div className={wrapperClasses}>
349+
{title}
350+
{helper}
351+
{input}
352+
</div>
353+
) : (
354+
input
355+
);
316356
}
317357
}

src/components/Dropdown/Dropdown-story.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const types = {
4646
};
4747

4848
const props = () => ({
49+
id: text('Dropdown ID (id)', 'carbon-dropdown-example'),
4950
type: select('Dropdown type (type)', types, 'default'),
5051
label: text('Label (label)', 'Dropdown menu options'),
5152
ariaLabel: text('Aria Label (ariaLabel)', 'Dropdown'),

src/components/Dropdown/Dropdown-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('Dropdown', () => {
2323
let mockProps;
2424
beforeEach(() => {
2525
mockProps = {
26+
id: 'test-dropdown',
2627
items: generateItems(5, generateGenericItem),
2728
onChange: jest.fn(),
2829
label: 'input',

src/components/Dropdown/Dropdown.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export default class Dropdown extends React.Component {
4646
PropTypes.string,
4747
]),
4848

49+
/**
50+
* Specify a custom `id`
51+
*/
52+
id: PropTypes.string.isRequired,
53+
4954
/**
5055
* Specify whether you want the inline version of this control
5156
*/
@@ -195,7 +200,6 @@ export default class Dropdown extends React.Component {
195200
{title}
196201
{!inline && helper}
197202
<Downshift
198-
id={id}
199203
onChange={this.handleOnChange}
200204
itemToString={itemToString}
201205
defaultSelectedItem={initialSelectedItem}
@@ -211,10 +215,10 @@ export default class Dropdown extends React.Component {
211215
getLabelProps,
212216
}) => (
213217
<ListBox
218+
id={id}
214219
type={type}
215220
className={className({ isOpen })}
216221
disabled={disabled}
217-
ariaLabel={ariaLabel}
218222
isOpen={isOpen}
219223
invalid={invalid}
220224
invalidText={invalidText}
@@ -224,7 +228,10 @@ export default class Dropdown extends React.Component {
224228
className={`${prefix}--list-box__invalid-icon`}
225229
/>
226230
)}
227-
<ListBox.Field {...getButtonProps({ disabled })}>
231+
<ListBox.Field
232+
id={id}
233+
tabIndex="0"
234+
{...getButtonProps({ disabled })}>
228235
<span
229236
className={`${prefix}--list-box__label`}
230237
{...getLabelProps()}>
@@ -233,7 +240,7 @@ export default class Dropdown extends React.Component {
233240
<ListBox.MenuIcon isOpen={isOpen} />
234241
</ListBox.Field>
235242
{isOpen && (
236-
<ListBox.Menu>
243+
<ListBox.Menu aria-label={ariaLabel} id={id}>
237244
{items.map((item, index) => (
238245
<ListBox.MenuItem
239246
key={itemToString(item)}

0 commit comments

Comments
 (0)