Skip to content

Commit

Permalink
feat(ComboBox): add selectedItem prop (carbon-design-system#4488)
Browse files Browse the repository at this point in the history
  • Loading branch information
emyarod authored and asudoh committed Nov 11, 2019
1 parent 952ebcc commit e907adf
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 2 deletions.
46 changes: 45 additions & 1 deletion packages/react/src/components/ComboBox/ComboBox-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import ComboBox from '../ComboBox';
import Button from '../Button';
import WithState from '../../tools/withState';

const items = [
Expand Down Expand Up @@ -59,6 +60,40 @@ const itemToElement = item => {
);
};

const ControlledComboBoxApp = props => {
const [selectedItem, setSelectedItem] = useState(items[0]);
let uid = items.length;
return (
<>
<ComboBox
{...props}
items={items}
itemToString={item => (item ? item.text : '')}
onChange={({ selectedItem }) => setSelectedItem(selectedItem)}
initialSelectedItem={items[0]}
selectedItem={selectedItem}
/>
<Button
style={{ marginTop: '1rem' }}
onClick={() => {
items.push({
id: `id-${uid++}`,
text: `Option ${uid}`,
});
setSelectedItem(items[items.length - 1]);
}}>
Add new item
</Button>
</>
);
};
ControlledComboBoxApp.__docgenInfo = {
...ComboBox.__docgenInfo,
props: {
...ComboBox.__docgenInfo.props,
},
};

storiesOf('ComboBox', module)
.addDecorator(withKnobs)
.add(
Expand Down Expand Up @@ -120,4 +155,13 @@ storiesOf('ComboBox', module)
text: `Sometimes you want to perform an async action to trigger a backend call on input change.`,
},
}
)
.add(
'application-level control for selection',
() => <ControlledComboBoxApp {...props()} />,
{
info: {
text: `Controlled ComboBox example application`,
},
}
);
46 changes: 46 additions & 0 deletions packages/react/src/components/ComboBox/ComboBox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
findMenuItemNode,
openMenu,
assertMenuOpen,
assertMenuClosed,
generateItems,
generateGenericItem,
} from '../ListBox/test-helpers';
Expand Down Expand Up @@ -93,6 +94,26 @@ describe('ComboBox', () => {
expect(wrapper.find(`.mock-item`).length).toBe(mockProps.items.length);
});

it('should let the user select an option by clicking on the option node', () => {
const wrapper = mount(<ComboBox {...mockProps} />);
openMenu(wrapper);
findMenuItemNode(wrapper, 0).simulate('click');
expect(mockProps.onChange).toHaveBeenCalledTimes(1);
expect(mockProps.onChange).toHaveBeenCalledWith({
selectedItem: mockProps.items[0],
});
assertMenuClosed(wrapper);

mockProps.onChange.mockClear();

openMenu(wrapper);
findMenuItemNode(wrapper, 1).simulate('click');
expect(mockProps.onChange).toHaveBeenCalledTimes(1);
expect(mockProps.onChange).toHaveBeenCalledWith({
selectedItem: mockProps.items[1],
});
});

describe('should display initially selected item found in `initialSelectedItem`', () => {
it('using an object type for the `initialSelectedItem` prop', () => {
const wrapper = mount(
Expand All @@ -118,6 +139,31 @@ describe('ComboBox', () => {
});
});

describe('should display selected item found in `selectedItem`', () => {
it('using an object type for the `selectedItem` prop', () => {
const wrapper = mount(
<ComboBox {...mockProps} selectedItem={mockProps.items[0]} />
);
expect(findInputNode(wrapper).prop('value')).toEqual(
mockProps.items[0].label
);
});

it('using a string type for the `selectedItem` prop', () => {
// Replace the 'items' property in mockProps with a list of strings
mockProps = {
...mockProps,
items: ['1', '2', '3'],
};

const wrapper = mount(
<ComboBox {...mockProps} selectedItem={mockProps.items[1]} />
);

expect(findInputNode(wrapper).prop('value')).toEqual(mockProps.items[1]);
});
});

describe('when disabled', () => {
it('should not let the user edit the input node', () => {
const wrapper = mount(<ComboBox {...mockProps} disabled={true} />);
Expand Down
13 changes: 12 additions & 1 deletion packages/react/src/components/ComboBox/ComboBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const defaultItemToString = item => {
const defaultShouldFilterItem = () => true;

const getInputValue = (props, state) => {
if (props.selectedItem) {
return props.itemToString(props.selectedItem);
}
// TODO: consistent `initialSelectedItem` behavior with other listbox components in v11
if (props.initialSelectedItem) {
return props.itemToString(props.initialSelectedItem);
}
Expand Down Expand Up @@ -134,6 +138,11 @@ export default class ComboBox extends React.Component {
*/
invalidText: PropTypes.string,

/**
* For full control of the selection
*/
selectedItem: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),

/**
* Specify a custom translation function that takes in a message identifier
* and returns the localized string for the message
Expand Down Expand Up @@ -246,6 +255,7 @@ export default class ComboBox extends React.Component {
helperText,
placeholder,
initialSelectedItem,
selectedItem,
ariaLabel,
translateWithId,
invalid,
Expand Down Expand Up @@ -288,7 +298,8 @@ export default class ComboBox extends React.Component {
onStateChange={this.handleOnStateChange}
inputValue={this.state.inputValue || ''}
itemToString={itemToString}
defaultSelectedItem={initialSelectedItem}>
defaultSelectedItem={initialSelectedItem}
selectedItem={selectedItem}>
{({
getButtonProps,
getInputProps,
Expand Down

0 comments on commit e907adf

Please sign in to comment.