add generic onChange prop to MultiSelect

This commit is contained in:
Keith Grant
2019-08-27 16:28:54 -07:00
parent 4d31d83e1e
commit 9edc686ab5
4 changed files with 69 additions and 8 deletions

View File

@@ -5,7 +5,9 @@ import styled from 'styled-components';
const Container = styled.div`
margin: 15px 0;
transition: all 0.2s ease-out;
overflow: hidden;
${props => !props.isExpanded && `
overflow: hidden;
`}
`;
function ExpandingContainer({ isExpanded, children }) {
@@ -21,6 +23,7 @@ function ExpandingContainer({ isExpanded, children }) {
css={`
height: ${height}px;
`}
isExpanded={isExpanded}
>
{children}
</Container>

View File

@@ -45,10 +45,17 @@ class MultiSelect extends Component {
name: PropTypes.string.isRequired,
})
).isRequired,
onAddNewItem: PropTypes.func.isRequired,
onRemoveItem: PropTypes.func.isRequired,
onAddNewItem: PropTypes.func,
onRemoveItem: PropTypes.func,
onChange: PropTypes.func,
};
static defaultProps = {
onAddNewItem: () => {},
onRemoveItem: () => {},
onChange: () => {},
}
constructor(props) {
super(props);
this.state = {
@@ -92,19 +99,21 @@ class MultiSelect extends Component {
handleSelection(e, item) {
const { chipItems } = this.state;
const { onAddNewItem } = this.props;
const { onAddNewItem, onChange } = this.props;
e.preventDefault();
const items = chipItems.concat({ name: item.name, id: item.id });
this.setState({
chipItems: chipItems.concat({ name: item.name, id: item.id }),
chipItems: items,
isExpanded: false,
});
onAddNewItem(item);
onChange(items);
}
handleAddItem(event) {
const { input, chipItems } = this.state;
const { onAddNewItem } = this.props;
const { onAddNewItem, onChange } = this.props;
const isIncluded = chipItems.some(chipItem => chipItem.name === input);
if (!input) {
@@ -120,12 +129,14 @@ class MultiSelect extends Component {
}
if (event.key === 'Enter') {
event.preventDefault();
const items = chipItems.concat({ name: input, id: input });
this.setState({
chipItems: chipItems.concat({ name: input, id: input }),
chipItems: items,
isExpanded: false,
input: '',
});
onAddNewItem(input);
onChange(items);
} else if (event.key === 'Tab') {
this.setState({ input: '' });
}
@@ -136,12 +147,13 @@ class MultiSelect extends Component {
}
removeChip(e, item) {
const { onRemoveItem } = this.props;
const { onRemoveItem, onChange } = this.props;
const { chipItems } = this.state;
const chips = chipItems.filter(chip => chip.id !== item.id);
this.setState({ chipItems: chips });
onRemoveItem(item);
onChange(chips);
e.preventDefault();
}

View File

@@ -28,6 +28,7 @@ describe('<MultiSelect />', () => {
expect(getInitialChipItems).toBeCalled();
expect(component.state().chipItems.length).toBe(2);
});
test('handleSelection add item to chipItems', async () => {
const wrapper = mountWithContexts(
<MultiSelect
@@ -45,12 +46,15 @@ describe('<MultiSelect />', () => {
await sleep(1);
expect(component.state().chipItems.length).toBe(2);
});
test('handleAddItem adds a chip only when Tab is pressed', () => {
const onAddNewItem = jest.fn();
const onChange = jest.fn();
const wrapper = mountWithContexts(
<MultiSelect
onAddNewItem={onAddNewItem}
onRemoveItem={jest.fn()}
onChange={onChange}
associatedItems={associatedItems}
options={options}
/>
@@ -68,14 +72,18 @@ describe('<MultiSelect />', () => {
expect(component.state().input.length).toBe(0);
expect(component.state().isExpanded).toBe(false);
expect(onAddNewItem).toBeCalled();
expect(onChange).toBeCalled();
});
test('removeChip removes chip properly', () => {
const onRemoveItem = jest.fn();
const onChange = jest.fn();
const wrapper = mountWithContexts(
<MultiSelect
onAddNewItem={jest.fn()}
onRemoveItem={onRemoveItem}
onChange={onChange}
associatedItems={associatedItems}
options={options}
/>
@@ -89,5 +97,6 @@ describe('<MultiSelect />', () => {
.removeChip(event, { name: 'Foo', id: 1, organization: 1 });
expect(component.state().chipItems.length).toBe(1);
expect(onRemoveItem).toBeCalled();
expect(onChange).toBeCalled();
});
});

View File

@@ -501,6 +501,7 @@ class JobTemplateForm extends Component {
id="template-job-slicing"
name="job_slice_count"
type="number"
min="1"
label={i18n._(t`Job Slicing`)}
tooltip={i18n._(t`Divide the work done by this job template
into the specified number of job slices, each running the
@@ -551,6 +552,42 @@ class JobTemplateForm extends Component {
t`Select the Instance Groups for this Organization to run on.`
)}
/>
<FormGroup label={i18n._(t`Job Tags`)} fieldId="template-job-tags">
<Tooltip
position="right"
content={i18n._(t`Tags are useful when you have a large
playbook, and you want to run a specific part of a play
or task. Use commas to separate multiple tags. Refer to
Ansible Tower documentation for details on the usage of
tags.`)}
>
<QuestionCircleIcon />
</Tooltip>
<MultiSelect
onAddNewItem={this.handleNewLabel}
onRemoveItem={this.removeLabel}
associatedItems={template.job_tags.split(',')}
options={loadedLabels}
/>
</FormGroup>
<FormGroup label={i18n._(t`Skip Tags`)} fieldId="template-skip-tags">
<Tooltip
position="right"
content={i18n._(t`Skip tags are useful when you have a
large playbook, and you want to skip specific parts of a
play or task. Use commas to separate multiple tags. Refer
to Ansible Tower documentation for details on the usage
of tags.`)}
>
<QuestionCircleIcon />
</Tooltip>
<MultiSelect
onAddNewItem={this.handleNewLabel}
onRemoveItem={this.removeLabel}
associatedItems={template.skip_tags.split(',')}
options={loadedLabels}
/>
</FormGroup>
</CollapsibleSection>
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
</Form>