Merge pull request #690 from LautaroCesso/master
Add autocomplete component
This commit is contained in:
commit
e6466b76ac
|
@ -59,10 +59,11 @@ class TicketViewer extends React.Component {
|
|||
commentEdited: false,
|
||||
commentPrivate: false,
|
||||
edit: false,
|
||||
editTitle: false,
|
||||
editId: 0,
|
||||
tagSelectorLoading: false,
|
||||
editTitle: false,
|
||||
newTitle: this.props.ticket.title,
|
||||
editTitleError: false
|
||||
editTitleError: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -176,7 +177,14 @@ class TicketViewer extends React.Component {
|
|||
onChange={this.onDepartmentDropdownChanged.bind(this)} />
|
||||
</div>
|
||||
<div className="col-md-4">{ticket.author.name}</div>
|
||||
<div className="col-md-4"> <TagSelector items={this.props.tags} values={this.props.ticket.tags} onRemoveClick={this.removeTag.bind(this)} onTagSelected={this.addTag.bind(this)}/></div>
|
||||
<div className="col-md-4">
|
||||
<TagSelector
|
||||
items={this.props.tags}
|
||||
values={this.props.ticket.tags}
|
||||
onRemoveClick={this.removeTag.bind(this)}
|
||||
onTagSelected={this.addTag.bind(this)}
|
||||
loading={this.state.tagSelectorLoading}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ticket-viewer__info-row-header row">
|
||||
<div className="col-md-4">{i18n('PRIORITY')}</div>
|
||||
|
@ -185,7 +193,11 @@ class TicketViewer extends React.Component {
|
|||
</div>
|
||||
<div className="ticket-viewer__info-row-values row">
|
||||
<div className="col-md-4">
|
||||
<DropDown className="ticket-viewer__editable-dropdown" items={priorityList} selectedIndex={priorities[ticket.priority]} onChange={this.onPriorityDropdownChanged.bind(this)} />
|
||||
<DropDown
|
||||
className="ticket-viewer__editable-dropdown"
|
||||
items={priorityList}
|
||||
selectedIndex={priorities[ticket.priority]}
|
||||
onChange={this.onPriorityDropdownChanged.bind(this)} />
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
{this.renderAssignStaffList()}
|
||||
|
@ -510,23 +522,47 @@ class TicketViewer extends React.Component {
|
|||
}
|
||||
|
||||
addTag(tag) {
|
||||
this.setState({
|
||||
tagSelectorLoading: true,
|
||||
})
|
||||
API.call({
|
||||
path: '/ticket/add-tag',
|
||||
data: {
|
||||
ticketNumber: this.props.ticket.ticketNumber,
|
||||
tagId: tag
|
||||
}
|
||||
}).then(this.onTicketModification.bind(this))
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({
|
||||
tagSelectorLoading: false,
|
||||
});
|
||||
this.onTicketModification();
|
||||
})
|
||||
.catch(() => this.setState({
|
||||
tagSelectorLoading: false,
|
||||
}))
|
||||
}
|
||||
|
||||
removeTag(tag) {
|
||||
this.setState({
|
||||
tagSelectorLoading: true,
|
||||
});
|
||||
|
||||
API.call({
|
||||
path: '/ticket/remove-tag',
|
||||
data: {
|
||||
ticketNumber: this.props.ticket.ticketNumber,
|
||||
tagId: tag
|
||||
}
|
||||
}).then(this.onTicketModification.bind(this))
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
tagSelectorLoading: false,
|
||||
});
|
||||
|
||||
this.onTicketModification();
|
||||
}).catch(() => this.setState({
|
||||
tagSelectorLoading: false,
|
||||
}))
|
||||
}
|
||||
|
||||
onCustomResponsesChanged({index}) {
|
||||
|
|
|
@ -265,4 +265,4 @@ let DemoPage = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
export default DemoPage;
|
||||
export default DemoPage;
|
|
@ -0,0 +1,500 @@
|
|||
// LIBS
|
||||
const _ = require('lodash');
|
||||
|
||||
// MOCKS
|
||||
const Tag = ReactMock();
|
||||
const DropDown = ReactMock();
|
||||
|
||||
// COMPONENT
|
||||
const Autocomplete = requireUnit('core-components/autocomplete', {
|
||||
'core-components/drop-down': DropDown,
|
||||
'core-components/tag': Tag,
|
||||
});
|
||||
|
||||
|
||||
const color = [
|
||||
'red',
|
||||
'cyan',
|
||||
'blue',
|
||||
'green',
|
||||
];
|
||||
|
||||
let countries = ["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"];
|
||||
countries = countries.map((name, index) => {
|
||||
return {
|
||||
id: index,
|
||||
name: name.toLowerCase(),
|
||||
content: name,
|
||||
color: color[_.random(0, color.length-1)],
|
||||
}
|
||||
});
|
||||
|
||||
const timeout = function (f, t) {
|
||||
return new Promise(function (res) {
|
||||
setTimeout(function() {
|
||||
res(f());
|
||||
},t);
|
||||
});
|
||||
};
|
||||
|
||||
const searchApi = spy((query, blacklist = []) => {
|
||||
const data = countries.filter(x => !_.includes(blacklist, x.id));
|
||||
|
||||
return new Promise((res,rej) => {
|
||||
setTimeout(function () {
|
||||
const result = data.filter(item => _.includes(item.name, query));
|
||||
res(result.slice(0, 10));
|
||||
}, query == 'brazilq' ? 100 : 50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Autocomplete component with external api', function () {
|
||||
let selectedList = [], autocompleteInput, autocompleteDropdown, autocompleteWithExternalApi, tag;
|
||||
function renderAutocomplete(props) {
|
||||
selectedList = [];
|
||||
|
||||
autocompleteWithExternalApi = TestUtils.renderIntoDocument(
|
||||
<Autocomplete
|
||||
getItemListFromQuery={searchApi}
|
||||
onChange={selectedListAutocomplete => selectedList = selectedListAutocomplete} />
|
||||
);
|
||||
|
||||
autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocompleteWithExternalApi, 'autocomplete__input')[0];
|
||||
autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, DropDown)[0];
|
||||
}
|
||||
|
||||
describe('writing in input', function() {
|
||||
beforeEach(function() {
|
||||
renderAutocomplete();
|
||||
});
|
||||
|
||||
it('should open menu with list', function() {
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "ho";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should select item if enter is pressed', function() {
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "argentina";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("argentina", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("argentina");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(10);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("argentina");
|
||||
expect(selectedList[0].id).to.equal(10);
|
||||
}, 360);
|
||||
});
|
||||
|
||||
it('should sinc', function() {
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "brazilq";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
expect(searchApi).to.have.not.been.calledWith("brazil", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "brazil";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("brazil", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("brazil");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(30);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("brazil");
|
||||
expect(selectedList[0].id).to.equal(30);
|
||||
|
||||
autocompleteDropdown.props.onMenuToggle(true);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(0);
|
||||
}, 360);
|
||||
}, 25);
|
||||
});
|
||||
|
||||
it('should delete item if backspace is pressed and input value is ""', function() {
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "ang";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(3);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("angola");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(6);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("angola");
|
||||
expect(selectedList[0].id).to.equal(6);
|
||||
|
||||
autocompleteInput.value = "ang";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(2);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("anguilla");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(7);
|
||||
expect(selectedList.length).to.equal(1);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(2);
|
||||
expect(selectedList[0].name).to.equal("angola");
|
||||
expect(selectedList[0].id).to.equal(6);
|
||||
expect(selectedList[1].name).to.equal("anguilla");
|
||||
expect(selectedList[1].id).to.equal(7);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("bangladesh");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(18);
|
||||
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("angola");
|
||||
expect(selectedList[0].id).to.equal(6);
|
||||
|
||||
autocompleteInput.value = "ang";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function() {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(2);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("anguilla");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(7);
|
||||
expect(selectedList.length).to.equal(1);
|
||||
}, 360);
|
||||
},360);
|
||||
}, 360);
|
||||
});
|
||||
|
||||
it("should delete item if click is pressed", function() {
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "ang";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function () {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(3);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id));
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("angola");
|
||||
expect(selectedList[0].id).to.equal(6);
|
||||
|
||||
tag = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, Tag)[0];
|
||||
tag.props.onRemoveClick({ preventDefault: stub() });
|
||||
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "ang";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.loading).to.equal(true);
|
||||
|
||||
return timeout(function () {
|
||||
expect(autocompleteDropdown.props.loading).to.equal(false);
|
||||
expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id));
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(3);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("angola");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(6);
|
||||
expect(selectedList.length).to.equal(0);
|
||||
}, 360);
|
||||
}, 360);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Autocomplete component with items', function() {
|
||||
let selectedList = [], autocompleteInput, autocompleteDropdown, itemsList, autocomplete;
|
||||
function renderAutocomplete(props) {
|
||||
selectedList = [];
|
||||
itemsList = [
|
||||
{id: 45, name: 'lautaro', content: 'Lautaro.', color: 'gray'},
|
||||
{id: 46, name: 'dsafa', content: 'dsafa', color: 'black'},
|
||||
{id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'},
|
||||
{id: 48, name: '123123123', content: '123123123.', color: 'blue'},
|
||||
{id: 49, name: 'hola', content: 'hola', color: 'green'},
|
||||
];
|
||||
|
||||
autocomplete = TestUtils.renderIntoDocument(
|
||||
<Autocomplete
|
||||
items={itemsList}
|
||||
onChange={selectedListAutocomplete => selectedList = selectedListAutocomplete} />
|
||||
);
|
||||
|
||||
autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocomplete, 'autocomplete__input')[0];
|
||||
autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocomplete, DropDown)[0];
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
renderAutocomplete();
|
||||
});
|
||||
|
||||
it('should open menu with list', function() {
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "la";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should select item if enter is pressed', function() {
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "la";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(2);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(45);
|
||||
expect(autocompleteDropdown.props.items[1].name).to.equal("hola");
|
||||
expect(autocompleteDropdown.props.items[1].id).to.equal(49);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("lautaro");
|
||||
expect(selectedList[0].id).to.equal(45);
|
||||
|
||||
autocompleteInput.value = "";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(4);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("dsafa");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(46);
|
||||
expect(autocompleteDropdown.props.items[1].name).to.equal("asdasdasd");
|
||||
expect(autocompleteDropdown.props.items[1].id).to.equal(47);
|
||||
expect(autocompleteDropdown.props.items[2].name).to.equal("123123123");
|
||||
expect(autocompleteDropdown.props.items[2].id).to.equal(48);
|
||||
expect(autocompleteDropdown.props.items[3].name).to.equal("hola");
|
||||
expect(autocompleteDropdown.props.items[3].id).to.equal(49);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 2});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(2);
|
||||
expect(selectedList[0].name).to.equal("lautaro");
|
||||
expect(selectedList[0].id).to.equal(45);
|
||||
expect(selectedList[1].name).to.equal("123123123");
|
||||
expect(selectedList[1].id).to.equal(48);
|
||||
|
||||
autocompleteInput.value = "";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(3);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("dsafa");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(46);
|
||||
expect(autocompleteDropdown.props.items[1].name).to.equal("asdasdasd");
|
||||
expect(autocompleteDropdown.props.items[1].id).to.equal(47);
|
||||
expect(autocompleteDropdown.props.items[2].name).to.equal("hola");
|
||||
expect(autocompleteDropdown.props.items[2].id).to.equal(49);
|
||||
|
||||
autocompleteInput.value = "lau";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "la";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("hola");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(49);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(3);
|
||||
expect(selectedList[0].name).to.equal("lautaro");
|
||||
expect(selectedList[0].id).to.equal(45);
|
||||
expect(selectedList[1].name).to.equal("123123123");
|
||||
expect(selectedList[1].id).to.equal(48);
|
||||
expect(selectedList[2].name).to.equal("hola");
|
||||
expect(selectedList[2].id).to.equal(49);
|
||||
});
|
||||
|
||||
it('should delete item if (backspace or click) is pressed and input value is "" ', function() {
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
autocompleteInput.value = "123";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("123123123");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(48);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("123123123");
|
||||
expect(selectedList[0].id).to.equal(48);
|
||||
|
||||
autocompleteInput.value = "la";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(2);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(45);
|
||||
expect(autocompleteDropdown.props.items[1].name).to.equal("hola");
|
||||
expect(autocompleteDropdown.props.items[1].id).to.equal(49);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 1});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(2);
|
||||
expect(selectedList[0].name).to.equal("123123123");
|
||||
expect(selectedList[0].id).to.equal(48);
|
||||
expect(selectedList[1].name).to.equal("hola");
|
||||
expect(selectedList[1].id).to.equal(49);
|
||||
|
||||
autocompleteInput.value = "l";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(true);
|
||||
expect(autocompleteDropdown.props.items.length).to.equal(1);
|
||||
expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro");
|
||||
expect(autocompleteDropdown.props.items[0].id).to.equal(45);
|
||||
|
||||
autocompleteDropdown.props.onChange({index: 0});
|
||||
|
||||
expect(autocompleteDropdown.props.opened).to.equal(false);
|
||||
expect(selectedList.length).to.equal(3);
|
||||
expect(selectedList[0].name).to.equal("123123123");
|
||||
expect(selectedList[0].id).to.equal(48);
|
||||
expect(selectedList[1].name).to.equal("hola");
|
||||
expect(selectedList[1].id).to.equal(49);
|
||||
expect(selectedList[2].name).to.equal("lautaro");
|
||||
expect(selectedList[2].id).to.equal(45);
|
||||
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(selectedList.length).to.equal(2);
|
||||
|
||||
autocompleteInput.value = "asd";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(selectedList.length).to.equal(2);
|
||||
expect(selectedList[0].name).to.equal("123123123");
|
||||
expect(selectedList[0].id).to.equal(48);
|
||||
expect(selectedList[1].name).to.equal("hola");
|
||||
expect(selectedList[1].id).to.equal(49);
|
||||
|
||||
tag = TestUtils.scryRenderedComponentsWithType(autocomplete, Tag)[0];
|
||||
tag.props.onRemoveClick({ preventDefault: stub() });
|
||||
|
||||
expect(selectedList.length).to.equal(1);
|
||||
expect(selectedList[0].name).to.equal("hola");
|
||||
expect(selectedList[0].id).to.equal(49);
|
||||
|
||||
autocompleteInput.value = "a";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(selectedList.length).to.equal(1);
|
||||
|
||||
autocompleteInput.value = "";
|
||||
TestUtils.Simulate.change(autocompleteInput);
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(selectedList.length).to.equal(0);
|
||||
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8});
|
||||
|
||||
expect(selectedList.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,269 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import keyCode from 'keycode';
|
||||
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import Tag from 'core-components/tag';
|
||||
|
||||
const ItemsSchema = React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
name: React.PropTypes.string,
|
||||
content: React.PropTypes.node,
|
||||
color: React.PropTypes.string,
|
||||
}));
|
||||
|
||||
class Autocomplete extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
items: ItemsSchema,
|
||||
onChange: React.PropTypes.func,
|
||||
values: ItemsSchema,
|
||||
onRemoveClick: React.PropTypes.func,
|
||||
onTagSelected: React.PropTypes.func,
|
||||
getItemListFromQuery: React.PropTypes.func,
|
||||
disabled: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
id = 1;
|
||||
|
||||
state = {
|
||||
selectedItems: [],
|
||||
inputValue: "",
|
||||
opened: false,
|
||||
highlightedIndex: 0,
|
||||
itemsFromQuery: [],
|
||||
loading: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { getItemListFromQuery, } = this.props;
|
||||
|
||||
this.setTimeout = _.throttle((query) => {
|
||||
let id = ++this.id;
|
||||
|
||||
getItemListFromQuery(query, this.getSelectedItems().map(item => item.id))
|
||||
.then(result => {
|
||||
if(id === this.id)
|
||||
this.setState({
|
||||
itemsFromQuery: result,
|
||||
loading: false,
|
||||
});
|
||||
})
|
||||
.catch(() => this.setState({
|
||||
loading: false,
|
||||
}));
|
||||
}, 300, {leading: false});
|
||||
|
||||
this.searchApi("");
|
||||
}
|
||||
|
||||
render() {
|
||||
let inputWidth = 0;
|
||||
|
||||
if(this.span) {
|
||||
this.span.style.display = 'inline';
|
||||
this.span.textContent = this.state.inputValue;
|
||||
inputWidth = Math.ceil(this.span.getBoundingClientRect().width)-31;
|
||||
this.span.style.display = 'none';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="autocomplete">
|
||||
<label className="autocomplete__label" onClick={(e) => e.stopPropagation()}>
|
||||
<DropDown
|
||||
className="autocomplete__drop-down"
|
||||
items={this.getDropdownList()}
|
||||
size="large"
|
||||
onChange={e => this.onChangeDropDown(e)}
|
||||
onMenuToggle={e => this.onMenuToggle(e)}
|
||||
opened={this.state.opened}
|
||||
onHighlightedIndexChange={n => this.onHighlightedIndexChange(n)}
|
||||
highlightedIndex={this.state.highlightedIndex}
|
||||
loading={this.state.loading}
|
||||
>
|
||||
{this.renderSelectedItems()}
|
||||
<input
|
||||
className="autocomplete__input"
|
||||
id="query"
|
||||
ref={input => this.input = input}
|
||||
value={this.state.inputValue}
|
||||
onKeyDown={e => this.onKeyDown(e)}
|
||||
onChange={e => this.onChangeInput(e.target.value)}
|
||||
style={this.span ? {width: inputWidth} : {}} />
|
||||
<span className="sizer" ref={span => this.span = span} />
|
||||
</DropDown>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSelectedItems() {
|
||||
return this.getSelectedItems().map(item => this.renderSelectedItem(item));
|
||||
}
|
||||
|
||||
renderSelectedItem(item) {
|
||||
return <Tag
|
||||
name={item.name}
|
||||
color={item.color}
|
||||
showDeleteButton
|
||||
onRemoveClick={this.onRemoveClick.bind(this,item.id)}
|
||||
key={item.id} />
|
||||
}
|
||||
|
||||
getDropdownList() {
|
||||
const {
|
||||
items,
|
||||
} = this.props;
|
||||
let dropdownList = [];
|
||||
|
||||
if(items !== undefined) {
|
||||
const list = this.getUnselectedList(items, this.getSelectedItems());
|
||||
|
||||
dropdownList = list.filter(s => _.includes(s.name, this.state.inputValue));
|
||||
} else {
|
||||
dropdownList = this.getUnselectedList(this.state.itemsFromQuery, this.getSelectedItems());
|
||||
}
|
||||
|
||||
return dropdownList;
|
||||
}
|
||||
|
||||
getUnselectedList(list, selectedList) {
|
||||
|
||||
return list.filter(item => !_.some(selectedList, item));
|
||||
}
|
||||
|
||||
getSelectedItems() {
|
||||
const { values, } = this.props;
|
||||
|
||||
return (values !== undefined) ? values : this.state.selectedItems;
|
||||
}
|
||||
|
||||
onRemoveClick(itemId, event) {
|
||||
const {
|
||||
onChange,
|
||||
onRemoveClick,
|
||||
} = this.props;
|
||||
const newList = this.getSelectedItems().filter(item => item.id != itemId);
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
selectedItems: newList,
|
||||
opened: false,
|
||||
highlightedIndex: 0,
|
||||
});
|
||||
|
||||
onChange && onChange(newList);
|
||||
onRemoveClick && onRemoveClick(itemId);
|
||||
this.searchApi("", newList);
|
||||
}
|
||||
|
||||
onChangeDropDown(e) {
|
||||
const {
|
||||
onChange,
|
||||
onTagSelected,
|
||||
} = this.props;
|
||||
|
||||
if(this.getDropdownList().length) {
|
||||
const itemSelected = this.getDropdownList()[e.index];
|
||||
const newList = [...this.getSelectedItems(), itemSelected];
|
||||
|
||||
this.setState({
|
||||
selectedItems: newList,
|
||||
inputValue: "",
|
||||
highlightedIndex: 0,
|
||||
opened: false,
|
||||
});
|
||||
|
||||
onChange && onChange(newList);
|
||||
onTagSelected && onTagSelected(itemSelected.id);
|
||||
this.searchApi("", newList);
|
||||
}
|
||||
}
|
||||
|
||||
onChangeInput(str) {
|
||||
const { getItemListFromQuery, } = this.props;
|
||||
|
||||
this.setState({
|
||||
inputValue: str,
|
||||
opened: true,
|
||||
highlightedIndex: 0,
|
||||
});
|
||||
|
||||
if(getItemListFromQuery !== undefined) {
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
this.setTimeout(str);
|
||||
}
|
||||
}
|
||||
|
||||
onMenuToggle(e) {
|
||||
this.setState({
|
||||
opened: e,
|
||||
});
|
||||
}
|
||||
|
||||
onHighlightedIndexChange(index) {
|
||||
this.setState({
|
||||
highlightedIndex: index,
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
const {
|
||||
onChange,
|
||||
onRemoveClick,
|
||||
} = this.props;
|
||||
|
||||
if(this.props.disabled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(keyCode(event) === "space") {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if(keyCode(event) === "backspace" && this.state.inputValue === "") {
|
||||
const lastSelectedItemsIndex = this.getSelectedItems().length-1;
|
||||
const newList = this.getSelectedItems().slice(0, lastSelectedItemsIndex);
|
||||
this.setState({
|
||||
selectedItems: newList,
|
||||
highlightedIndex: 0,
|
||||
});
|
||||
onChange && onChange(newList);
|
||||
|
||||
if(this.getSelectedItems().length) {
|
||||
const itemId = this.getSelectedItems()[lastSelectedItemsIndex].id;
|
||||
|
||||
onRemoveClick && onRemoveClick(itemId);
|
||||
}
|
||||
|
||||
this.searchApi("", newList);
|
||||
}
|
||||
}
|
||||
|
||||
searchApi(query, blacklist=this.getSelectedItems()) {
|
||||
const { getItemListFromQuery, } = this.props;
|
||||
|
||||
if(getItemListFromQuery !== undefined) {
|
||||
getItemListFromQuery(query, blacklist.map(item => item.id))
|
||||
.then(result => {
|
||||
this.setState({
|
||||
itemsFromQuery: result,
|
||||
loading: false,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Autocomplete;
|
|
@ -0,0 +1,42 @@
|
|||
@import "../scss/vars";
|
||||
|
||||
.autocomplete {
|
||||
margin-bottom: 30px;
|
||||
text-align: left;
|
||||
|
||||
&__drop-down {
|
||||
.drop-down__current-item {
|
||||
cursor: text;
|
||||
background-color: $very-light-grey;
|
||||
border: 1px solid $grey;
|
||||
min-height: 38px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
padding-left: 5px;
|
||||
width: 10px;
|
||||
max-width: 100%;
|
||||
min-width: 10px;
|
||||
}
|
||||
|
||||
.sizer {
|
||||
line-height: 1.21428571em;
|
||||
padding: .67857143em 2.1em .67857143em 1em;
|
||||
display: none;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
|
@ -2,10 +2,11 @@ import React from 'react';
|
|||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import {Motion, spring} from 'react-motion';
|
||||
import keyCode from 'keycode';
|
||||
import keyCode from 'keycode';
|
||||
|
||||
import Menu from 'core-components/menu';
|
||||
import Icon from 'core-components/icon';
|
||||
import Loading from 'core-components/loading'
|
||||
|
||||
class DropDown extends React.Component {
|
||||
|
||||
|
@ -14,7 +15,11 @@ class DropDown extends React.Component {
|
|||
selectedIndex: React.PropTypes.number,
|
||||
items: Menu.propTypes.items,
|
||||
onChange: React.PropTypes.func,
|
||||
size: React.PropTypes.oneOf(['small', 'medium', 'large'])
|
||||
size: React.PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
highlightedIndex: React.PropTypes.number,
|
||||
onHighlightedIndexChange: React.PropTypes.func,
|
||||
opened: React.PropTypes.bool,
|
||||
onMenuToggle: React.PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -28,7 +33,7 @@ class DropDown extends React.Component {
|
|||
menuId: _.uniqueId('drop-down-menu_'),
|
||||
selectedIndex: props.selectedIndex || props.defaultSelectedIndex,
|
||||
highlightedIndex: props.selectedIndex || props.defaultSelectedIndex,
|
||||
opened: false
|
||||
opened: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,7 +49,7 @@ class DropDown extends React.Component {
|
|||
|
||||
return {
|
||||
defaultStyle: {opacity: 0, translateY: 20},
|
||||
style: (this.state.opened) ? openedStyle : closedStyle
|
||||
style: (this.getOpen()) ? openedStyle : closedStyle
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,19 +61,29 @@ class DropDown extends React.Component {
|
|||
<div {...this.getCurrentItemProps()}>
|
||||
{this.props.children ? this.props.children : this.renderCurrentItem()}
|
||||
</div>
|
||||
<Motion defaultStyle={animation.defaultStyle} style={animation.style} onRest={this.onAnimationFinished.bind(this)}>
|
||||
{this.renderList.bind(this)}
|
||||
<Motion
|
||||
defaultStyle={animation.defaultStyle}
|
||||
style={animation.style}
|
||||
onRest={this.onAnimationFinished.bind(this)}>
|
||||
{this.renderList.bind(this)}
|
||||
</Motion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderList({opacity, translateY}) {
|
||||
let style = { opacity: opacity, transform: `translateY(${translateY}px)`};
|
||||
let style = {
|
||||
opacity: opacity,
|
||||
transform: `translateY(${translateY}px)`
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="drop-down__list-container" style={style}>
|
||||
<Menu {...this.getMenuProps()} />
|
||||
{this.props.loading ?
|
||||
<div><Loading className='drop-down__loading' /></div> :
|
||||
this.props.items.length ?
|
||||
<Menu {...this.getMenuProps()} /> :
|
||||
<div className='drop-down__empty-list'>Empty</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -77,7 +92,6 @@ class DropDown extends React.Component {
|
|||
let item = this.props.items[this.getSelectedIndex()];
|
||||
let iconNode = null;
|
||||
|
||||
|
||||
if (item.icon) {
|
||||
iconNode = <Icon className="drop-down__current-item-icon" name={item.icon} />;
|
||||
}
|
||||
|
@ -90,12 +104,17 @@ class DropDown extends React.Component {
|
|||
}
|
||||
|
||||
getClass() {
|
||||
const {
|
||||
className,
|
||||
size,
|
||||
} = this.props;
|
||||
|
||||
let classes = {
|
||||
'drop-down': true,
|
||||
'drop-down_closed': !this.state.opened,
|
||||
'drop-down_closed': !this.getOpen(),
|
||||
|
||||
['drop-down_' + this.props.size]: (this.props.size),
|
||||
[this.props.className]: (this.props.className)
|
||||
['drop-down_' + size]: (size),
|
||||
[className]: (className)
|
||||
};
|
||||
|
||||
return classNames(classes);
|
||||
|
@ -103,10 +122,10 @@ class DropDown extends React.Component {
|
|||
|
||||
getCurrentItemProps() {
|
||||
return {
|
||||
'aria-expanded': this.state.opened,
|
||||
'aria-expanded': this.getOpen(),
|
||||
'aria-autocomplete': 'list',
|
||||
'aria-owns': this.state.menuId,
|
||||
'aria-activedescendant': this.state.menuId + '__' + this.state.highlightedIndex,
|
||||
'aria-activedescendant': this.state.menuId + '__' + this.getHighlightedIndex(),
|
||||
className: 'drop-down__current-item',
|
||||
onClick: this.handleClick.bind(this),
|
||||
onKeyDown: this.onKeyDown.bind(this),
|
||||
|
@ -123,7 +142,7 @@ class DropDown extends React.Component {
|
|||
items: this.props.items,
|
||||
onItemClick: this.handleItemClick.bind(this),
|
||||
onMouseDown: this.handleListMouseDown.bind(this),
|
||||
selectedIndex: this.state.highlightedIndex,
|
||||
selectedIndex: this.getHighlightedIndex(),
|
||||
role: 'listbox'
|
||||
};
|
||||
}
|
||||
|
@ -138,26 +157,37 @@ class DropDown extends React.Component {
|
|||
}
|
||||
|
||||
getKeyActions(event) {
|
||||
const {highlightedIndex, opened} = this.state;
|
||||
const highlightedIndex = this.getHighlightedIndex();
|
||||
const opened = this.getOpen();
|
||||
const itemsQuantity = this.props.items.length;
|
||||
const {
|
||||
onHighlightedIndexChange,
|
||||
onMenuToggle,
|
||||
} = this.props;
|
||||
|
||||
return {
|
||||
'up': () => {
|
||||
if (opened) {
|
||||
const newHighlightedIndex = this.modulo(highlightedIndex - 1, itemsQuantity);
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
highlightedIndex: this.modulo(highlightedIndex - 1, itemsQuantity)
|
||||
highlightedIndex: newHighlightedIndex,
|
||||
});
|
||||
|
||||
onHighlightedIndexChange && onHighlightedIndexChange(newHighlightedIndex);
|
||||
}
|
||||
},
|
||||
'down': () => {
|
||||
if (opened) {
|
||||
const newHighlightedIndex = this.modulo(highlightedIndex + 1, itemsQuantity);
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
highlightedIndex: this.modulo(highlightedIndex + 1, itemsQuantity)
|
||||
highlightedIndex: newHighlightedIndex,
|
||||
});
|
||||
|
||||
onHighlightedIndexChange && onHighlightedIndexChange(newHighlightedIndex);
|
||||
}
|
||||
},
|
||||
'enter': () => {
|
||||
|
@ -167,6 +197,8 @@ class DropDown extends React.Component {
|
|||
this.setState({
|
||||
opened: true
|
||||
});
|
||||
|
||||
onMenuToggle && onMenuToggle(true);
|
||||
}
|
||||
},
|
||||
'space': () => {
|
||||
|
@ -175,32 +207,44 @@ class DropDown extends React.Component {
|
|||
this.setState({
|
||||
opened: true
|
||||
});
|
||||
|
||||
onMenuToggle && onMenuToggle(true);
|
||||
},
|
||||
'esc': () => {
|
||||
this.setState({
|
||||
opened: false
|
||||
});
|
||||
|
||||
onMenuToggle && onMenuToggle(false);
|
||||
},
|
||||
'tab': () => {
|
||||
if (this.state.opened) {
|
||||
if (this.getOpen()) {
|
||||
event.preventDefault();
|
||||
|
||||
this.onIndexSelected(highlightedIndex)
|
||||
onHighlightedIndexChange && this.onIndexSelected(highlightedIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
const { onMenuToggle, } = this.props;
|
||||
|
||||
this.setState({
|
||||
opened: false
|
||||
});
|
||||
|
||||
onMenuToggle && onMenuToggle(false);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const { onMenuToggle, } = this.props;
|
||||
|
||||
this.setState({
|
||||
opened: !this.state.opened
|
||||
opened: !this.getOpen()
|
||||
});
|
||||
|
||||
onMenuToggle && onMenuToggle(!this.getOpen());
|
||||
}
|
||||
|
||||
handleItemClick(index) {
|
||||
|
@ -208,16 +252,25 @@ class DropDown extends React.Component {
|
|||
}
|
||||
|
||||
onIndexSelected(index) {
|
||||
this.setState({
|
||||
opened: false,
|
||||
selectedIndex: index,
|
||||
highlightedIndex: index
|
||||
});
|
||||
const {
|
||||
onMenuToggle,
|
||||
onHighlightedIndexChange,
|
||||
onChange,
|
||||
loading,
|
||||
} = this.props;
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange({
|
||||
index
|
||||
if (!loading){
|
||||
this.setState({
|
||||
opened: false,
|
||||
selectedIndex: index,
|
||||
highlightedIndex: index
|
||||
});
|
||||
|
||||
onHighlightedIndexChange && onHighlightedIndexChange(index);
|
||||
|
||||
onMenuToggle && onMenuToggle(false);
|
||||
|
||||
onChange && onChange({ index });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,20 +279,37 @@ class DropDown extends React.Component {
|
|||
}
|
||||
|
||||
onAnimationFinished() {
|
||||
if (!this.state.opened && this.state.highlightedIndex !== this.getSelectedIndex()) {
|
||||
const { onHighlightedIndexChange, } = this.props;
|
||||
|
||||
|
||||
if (!this.getOpen() && this.getHighlightedIndex() !== this.getSelectedIndex()) {
|
||||
this.setState({
|
||||
highlightedIndex: this.getSelectedIndex()
|
||||
highlightedIndex: this.getSelectedIndex(),
|
||||
});
|
||||
|
||||
onHighlightedIndexChange && onHighlightedIndexChange(this.getSelectedIndex());
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex;
|
||||
const { selectedIndex, } = this.props;
|
||||
return (selectedIndex !== undefined) ? selectedIndex : this.state.selectedIndex;
|
||||
}
|
||||
|
||||
modulo(number, mod) {
|
||||
return ((number % mod) + mod) % mod;
|
||||
}
|
||||
|
||||
getOpen(){
|
||||
const { opened, } = this.props;
|
||||
return (opened !== undefined) ? opened : this.state.opened;
|
||||
}
|
||||
|
||||
getHighlightedIndex() {
|
||||
const { highlightedIndex, } = this.props;
|
||||
return (highlightedIndex !== undefined) ? highlightedIndex : this.state.highlightedIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DropDown;
|
||||
|
|
|
@ -28,6 +28,25 @@
|
|||
position: absolute;
|
||||
width: 150px;
|
||||
z-index: 100;
|
||||
background-color: white;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
padding-top: 12.5%;
|
||||
|
||||
.loading__icon {
|
||||
border-color: rgba(150, 150, 150, 0.2);
|
||||
border-left-color: $primary-red;
|
||||
}
|
||||
}
|
||||
|
||||
&__empty-list {
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
color: $dark-grey;
|
||||
font-style: italic;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&_closed {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Icon from 'core-components/icon';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
import Tag from 'core-components/tag';
|
||||
import Autocomplete from 'core-components/autocomplete';
|
||||
|
||||
class TagSelector extends React.Component {
|
||||
|
||||
|
@ -13,55 +11,34 @@ class TagSelector extends React.Component {
|
|||
})),
|
||||
values: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
onRemoveClick: React.PropTypes.func,
|
||||
onTagSelected: React.PropTypes.func,
|
||||
loading: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
render() {
|
||||
const items = this.props.items.map(tag => ({...tag, content: this.renderTagOption(tag)}));
|
||||
const values = items.filter(item => _.includes(this.props.values, item.name));
|
||||
|
||||
return (
|
||||
<div className="tag-selector">
|
||||
<DropDown className="tag-selector__drop-down" items={this.renderTagOptions().map(tag => ({content: tag}))} selectedIndex={-1} size="large">
|
||||
{this.renderSelectedTags()}
|
||||
</DropDown>
|
||||
<Autocomplete
|
||||
items={items}
|
||||
values={values}
|
||||
onRemoveClick={this.props.onRemoveClick}
|
||||
onTagSelected={this.props.onTagSelected}
|
||||
disabled={this.props.loading} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSelectedTags() {
|
||||
const itemList = this.props.values.map(value => _.find(this.props.items, {name:value}));
|
||||
|
||||
return itemList.map(this.renderSelectedTag.bind(this));
|
||||
}
|
||||
|
||||
|
||||
renderSelectedTag(item,index) {
|
||||
return <Tag name={item.name} color={item.color} showDeleteButton onRemoveClick={this.onRemoveClick.bind(this,item.id)} key={index}/>;
|
||||
|
||||
}
|
||||
|
||||
renderTagOptions() {
|
||||
const itemList = _.filter(this.props.items,(item) => !_.includes(this.props.values,item.name));
|
||||
|
||||
return itemList.map(this.renderTagOption.bind(this));
|
||||
}
|
||||
|
||||
renderTagOption(item,index) {
|
||||
renderTagOption(item) {
|
||||
return (
|
||||
<div onClick={this.onTagSelected.bind(this,item.id)} className="tag-selector__tag-option" key={index}>
|
||||
<div className="tag-selector__tag-option" key={`tag-option-${item.id}`}>
|
||||
<span className="tag-selector__tag-option-square" style={{backgroundColor:item.color}}/>
|
||||
<span className="tag-selector__tag-option-name" >{item.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onRemoveClick(tagId) {
|
||||
if(this.props.onRemoveClick){
|
||||
this.props.onRemoveClick(tagId);
|
||||
}
|
||||
}
|
||||
|
||||
onTagSelected(tagId) {
|
||||
if(this.props.onTagSelected){
|
||||
this.props.onTagSelected(tagId);
|
||||
}
|
||||
}
|
||||
}
|
||||
export default TagSelector;
|
||||
|
|
Loading…
Reference in New Issue