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

View File

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

View File

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

View File

@@ -501,6 +501,7 @@ class JobTemplateForm extends Component {
id="template-job-slicing" id="template-job-slicing"
name="job_slice_count" name="job_slice_count"
type="number" type="number"
min="1"
label={i18n._(t`Job Slicing`)} label={i18n._(t`Job Slicing`)}
tooltip={i18n._(t`Divide the work done by this job template tooltip={i18n._(t`Divide the work done by this job template
into the specified number of job slices, each running the 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.` 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> </CollapsibleSection>
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} /> <FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
</Form> </Form>