UNPKG

8.98 kBJavaScriptView Raw
1import React from 'react';
2var ReactDom = require('react-dom');
3
4export class Select extends React.Component {
5 constructor(props) {
6 super(props);
7 this.getVisibleItems = this.getVisibleItems.bind(this);
8 this.handleOutsideClick = this.handleOutsideClick.bind(this);
9 this.inputOnChange = this.inputOnChange.bind(this);
10 this.inputOnKeyPress = this.inputOnKeyPress.bind(this);
11 this.inputOnKeyDown = this.inputOnKeyDown.bind(this);
12 this.linkOnKeyDown = this.linkOnKeyDown.bind(this);
13 this.setNextHighlightedItem = this.setNextHighlightedItem.bind(this);
14 this.findIndex = this.findIndex.bind(this);
15 }
16
17 componentWillMount() {
18 this.setState({
19 open: false,
20 items: this.props.items ? this.props.items : {},
21 filter: '',
22 selectedItem: '',
23 selectedItemLabel: '',
24 visibleItems: [],
25 tabIndex: this.props.tabIndex ? this.props.tabIndex : null,
26 currentlyHighlighted: ''
27 });
28 document.addEventListener('click', this.handleOutsideClick, false);
29 }
30
31 componentDidMount() {
32 this.getVisibleItems();
33 document.addEventListener('keydown', (e)=> {
34 if (e.key === "Escape") {
35 this.setState({
36 open: false,
37 filter: ''
38 });
39 this.setState({
40 currentlyHighlighted: '',
41 });
42 this.getVisibleItems();
43 if (ReactDom.findDOMNode(this).contains(e.target)) {
44 this.link.focus();
45 }
46 }
47 });
48 };
49
50 componentWillUnmount() {
51 document.removeEventListener('click', this.handleOutsideClick, false);
52 };
53
54 toggle(value) {
55 this.setState({
56 open: value
57 });
58 if (value == false) {
59 this.setState({
60 filter: ''
61 });
62 } else {
63 if(this.state.selectedItem){
64 this.setState({
65 currentlyHighlighted: this.state.selectedItem
66 });
67 }
68 }
69 };
70
71 componentWillReceiveProps(nextProps) {
72 if (nextProps.items !== this.state.items) {
73 this.setState({items: nextProps.items}, () => {
74 this.getVisibleItems();
75 });
76 }
77 }
78
79 submit(value) {
80 this.props.onChange(value);
81 this.setState({
82 selectedItem: value,
83 selectedItemLabel: this.props.items[value],
84 open: false,
85 currentlyHighlighted: ''
86 }, ()=> {
87 this.getVisibleItems();
88 })
89 }
90
91 getVisibleItems(isSearching) {
92 var first = true;
93 const visibleItems = [];
94 Object.keys(this.props.items).forEach((key)=> {
95 if (
96 !this.state.filter
97 ||
98 this.props.items[key].toLowerCase().indexOf(this.state.filter.toLowerCase().trim())
99 !== -1
100 ) {
101 var className = '';
102 if (isSearching) {
103 if (first == true) {
104 first = false;
105 className = 'item item-selected';
106 this.setState({
107 currentlyHighlighted: key
108 })
109 } else {
110 className = 'item'
111 }
112 } else {
113 className = (
114 (key == this.state.currentlyHighlighted && this.state.selectedItem == '')
115 ||
116 (key == this.state.selectedItem && this.state.currentlyHighlighted == '')
117 ||
118 (
119 this.state.currentlyHighlighted != ''
120 &&
121 this.state.selectedItem != ''
122 &&
123 key == this.state.currentlyHighlighted
124 )
125 )
126 ? 'item item-selected' : 'item';
127 }
128 visibleItems.push(
129 <div
130 onClick={() => {
131 this.submit(key);
132 }}
133 key={key}
134 className={className}
135 >{this.props.items[key]}
136 </div>
137 );
138
139 }
140 });
141
142 if (visibleItems.length === 0) {
143 visibleItems.push(
144 <div
145 key={null}
146 className="item item-no-results"
147 >No results found</div>
148 );
149 this.setState({
150 currentlyHighlighted: ''
151 })
152 }
153
154 this.setState({
155 visibleItems: visibleItems
156 })
157 };
158
159 handleOutsideClick(e) {
160 if (!ReactDom.findDOMNode(this).contains(e.target)) {
161 this.setState({
162 open: false,
163 filter: ''
164 }, ()=> {
165 this.getVisibleItems();
166 })
167 }
168 };
169
170 findIndex(item) {
171 return item.key == this.state.currentlyHighlighted;
172 }
173
174 setNextHighlightedItem(direction = null, isSearching = false) {
175 const currentIndex = this.state.visibleItems.findIndex(this.findIndex);
176 let newIndex = 0;
177 if (direction == 'down' && currentIndex < this.state.visibleItems.length - 1 && currentIndex != -1) {
178 newIndex = currentIndex + 1;
179 } else if (direction == 'up' && currentIndex > 0) {
180 newIndex = currentIndex - 1;
181 } else if (!direction){
182 newIndex = 0;
183 }
184 this.setState({
185 currentlyHighlighted: this.state.visibleItems[newIndex].key
186 }, ()=> {
187 this.getVisibleItems(isSearching);
188 });
189 };
190
191 inputOnKeyDown(e) {
192 if (e.key === 'ArrowDown') {
193 this.setNextHighlightedItem('down');
194 }
195
196 if (e.key === 'ArrowUp') {
197 this.setNextHighlightedItem('up');
198 }
199 }
200
201 inputOnKeyPress(e) {
202 if (e.key === 'Esc') {
203 this.toggle(!this.state.open);
204
205 }
206 if (e.key === 'Enter') {
207 e.preventDefault();
208 if (this.state.currentlyHighlighted != '') {
209 this.submit(this.state.currentlyHighlighted);
210 this.toggle(!this.state.open);
211 this.link.focus();
212 }
213 return false;
214 }
215 }
216
217 inputOnChange(e) {
218 this.setState({
219 filter: e.target.value
220 }, ()=> {
221 this.setNextHighlightedItem(null, true);
222 });
223 }
224
225 linkOnKeyDown(e) {
226 if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
227 this.setState({
228 open: true
229 })
230 }
231 }
232
233 render() {
234 return (
235
236 <div className="select-react-redux-container">
237 <a href="#"
238 tabIndex={this.state.tabIndex}
239 onClick={()=> {
240 this.toggle(!this.state.open)
241 }}
242 onKeyPress={()=> {
243 this.setState({open: true})
244 }}
245 ref={(e) => {
246 this.link = e;
247 }}
248 onKeyDown={this.linkOnKeyDown}
249 className={this.state.open ? 'selected selected-open' : 'selected'}
250 >
251 <div
252 className={Object.keys(this.state.items).length == 0 ? 'top-bar top-bar-empty' : 'top-bar'}>
253 {this.state.selectedItemLabel
254 ? this.state.selectedItemLabel
255 : Object.keys(this.state.items).length == 0 ? 'No options available' : 'Please select...'}
256 </div>
257 </a>
258
259 <div className={this.state.open ? 'results-container open' : 'results-container' }>
260
261 <div className="input-container">
262 <input
263 type="text"
264 autoCorrect="off"
265 autoCapitalize="off"
266 spellCheck="false"
267 autoComplete="off"
268 ref={search => search && search.focus()}
269 value={this.state.filter}
270 onKeyPress={this.inputOnKeyPress}
271 onChange={this.inputOnChange}
272 onKeyDown={this.inputOnKeyDown}
273 />
274 </div>
275 {this.state.visibleItems}
276 </div>
277 </div>
278 );
279 }
280}