update(components): Add support to create new items inside EditableList

- Adds logic to allow simple item creation
- Adds new onItemCreated callback
- Updates specs
This commit is contained in:
Juan Tejada 2015-12-07 12:38:38 -08:00
parent 68a0a81f77
commit 31796e396d
3 changed files with 87 additions and 8 deletions

View file

@ -38,6 +38,37 @@ describe('EditableList', ()=> {
});
});
describe('_onCreateInputKeyDown', ()=> {
it('calls onItemCreated', ()=> {
const onItemCreated = jasmine.createSpy('onItemCreated');
const list = makeList(['1', '2'], {initialState: {creatingItem: true}, onItemCreated});
const createItem = findRenderedDOMComponentWithClass(list, 'create-item');
const input = findRenderedDOMComponentWithTag(createItem, 'input');
findDOMNode(input).value = 'New Item';
Simulate.keyDown(input, {key: 'Enter'});
expect(onItemCreated).toHaveBeenCalledWith('New Item');
});
});
describe('_onCreateItem', ()=> {
it('should call prop callback when provided', ()=> {
const onCreateItem = jasmine.createSpy('onCreateItem');
const list = makeList(['1', '2'], {onCreateItem});
list._onCreateItem();
expect(onCreateItem).toHaveBeenCalled();
});
it('should set state for creating item when no callback provided', ()=> {
const list = makeList(['1', '2']);
spyOn(list, 'setState');
list._onCreateItem();
expect(list.setState).toHaveBeenCalledWith({creatingItem: true});
});
});
describe('_renderItem', ()=> {
const makeItem = (item, idx, state = {}, handlers = {})=> {
const list = makeList();
@ -109,18 +140,26 @@ describe('EditableList', ()=> {
const innerList = findDOMNode(
findRenderedDOMComponentWithClass(list, 'items-wrapper')
);
expect(()=> {
findRenderedDOMComponentWithClass(list, 'create-item');
}).toThrow();
expect(innerList.childNodes.length).toEqual(3);
items.forEach((item, idx)=> expect(innerList.childNodes[idx].textContent).toEqual(item));
});
it('renders create input as an item when creating', ()=> {
const items = ['1', '2', '3'];
const list = makeList(items, {initialState: {creatingItem: true}});
const createItem = findRenderedDOMComponentWithClass(list, 'create-item');
expect(createItem).toBeDefined();
});
it('renders add button', ()=> {
const onCreateItem = jasmine.createSpy('onCreateItem');
const list = makeList([], {onCreateItem});
const list = makeList();
const button = scryRenderedDOMComponentsWithClass(list, 'btn-editable-list')[0];
Simulate.click(button);
expect(onCreateItem).toHaveBeenCalled();
expect(findDOMNode(button).textContent).toEqual('+');
});
it('renders delete button', ()=> {
@ -130,6 +169,7 @@ describe('EditableList', ()=> {
Simulate.click(button);
expect(findDOMNode(button).textContent).toEqual('—');
expect(onDeleteItem).toHaveBeenCalledWith('2', 1);
});
});

View file

@ -16,15 +16,16 @@ class EditableList extends Component {
onDeleteItem: PropTypes.func,
onItemEdited: PropTypes.func,
onItemSelected: PropTypes.func,
onItemCreated: PropTypes.func,
initialState: PropTypes.object,
}
static defaultProps = {
children: [],
onCreateItem: ()=> {},
onDeleteItem: ()=> {},
onItemEdited: ()=> {},
onItemSelected: ()=> {},
onItemCreated: ()=> {},
}
constructor(props) {
@ -34,6 +35,7 @@ class EditableList extends Component {
this.state = props.initialState || {
editing: null,
selected: null,
creatingItem: false,
};
}
@ -54,6 +56,13 @@ class EditableList extends Component {
}
}
_onCreateInputKeyDown = (event)=> {
if (_.includes(['Enter', 'Return'], event.key)) {
this.setState({creatingItem: false});
this.props.onItemCreated(event.target.value);
}
}
_onItemClick = (event, item, idx)=> {
this._selectItem(item, idx);
}
@ -83,7 +92,11 @@ class EditableList extends Component {
}
_onCreateItem = ()=> {
this.props.onCreateItem();
if (this.props.onCreateItem) {
this.props.onCreateItem();
} else {
this.setState({creatingItem: true});
}
}
_onDeleteItem = ()=> {
@ -137,7 +150,23 @@ class EditableList extends Component {
);
}
_renderCreateItem = (onCreateInputKeyDown = this._onCreateInputKeyDown)=> {
return (
<div className="create-item">
<input
autoFocus
type="text"
onKeyDown={onCreateInputKeyDown} />
</div>
);
}
render() {
let items = this._items.map((item, idx)=> this._renderItem(item, idx));
if (this.state.creatingItem === true) {
items = items.concat(this._renderCreateItem());
}
return (
<div className={`nylas-editable-list ${this.props.className}`}>
<div
@ -145,7 +174,7 @@ class EditableList extends Component {
tabIndex="1"
onBlur={this._onListBlur}
onKeyDown={this._onListKeyDown}>
{this._items.map((item, idx)=> this._renderItem(item, idx))}
{items}
</div>
<div className="buttons-wrapper">
<button className="btn btn-small btn-editable-list" onClick={this._onCreateItem}>+</button>

View file

@ -31,6 +31,16 @@
color: @text-color-inverse-very-subtle;
}
}
.create-item {
padding: @padding-small-vertical @padding-small-horizontal;
border-top: 1px solid @border-color-divider;
input {
border: none;
padding: 0;
font-size: inherit;
}
}
}
.buttons-wrapper {