1 | import React from 'react';
|
2 | import {render} from 'react-dom';
|
3 | import {connect} from 'react-redux';
|
4 | import {createStore} from 'redux';
|
5 | import actions from './actions';
|
6 | import {reducers} from './reducers';
|
7 |
|
8 |
|
9 | const NoItems = () => {
|
10 |
|
11 | return (
|
12 | <div
|
13 | key={null}
|
14 | className="item item-no-results"
|
15 | >No results found</div>
|
16 | );
|
17 | };
|
18 |
|
19 | const Presentation = ({...props}) => {
|
20 | let topBar;
|
21 | const visibleItems = Object.keys(props.visibleItems).map((item) => {
|
22 | return (
|
23 | <div
|
24 | onClick={() => {
|
25 | props.submit({
|
26 | selected: item,
|
27 | selectedItemLabel: props.items[item]
|
28 | });
|
29 | focus();
|
30 | }}
|
31 | key={item}
|
32 | className={
|
33 | (item == props.currentlyHighlighted) ? 'item item-selected' : 'item'
|
34 | }
|
35 | >
|
36 | {props.items[item]}
|
37 | </div>
|
38 | )
|
39 |
|
40 | });
|
41 | const focus = () => {
|
42 | if (topBar)
|
43 | topBar.focus();
|
44 | };
|
45 | return (
|
46 | <div
|
47 | className="select-react-redux-container"
|
48 | ref={(input) => {
|
49 | if (props.initialRender && input) {
|
50 | props.initialRenderFalse();
|
51 | document.addEventListener('click', function (event) {
|
52 | if (!input.contains(event.target)) {
|
53 | props.refresh();
|
54 | }
|
55 | });
|
56 |
|
57 | }
|
58 |
|
59 | }}>
|
60 | <a href="#"
|
61 | tabIndex={props.tabIndex}
|
62 | onClick={props.topBarOnClick}
|
63 | onKeyPress={props.linkOnKeyPress}
|
64 | onKeyDown={e => {
|
65 | if (e.key.indexOf('Arrow') == 0) {
|
66 | props.linkOnKeyPress(e)
|
67 | }
|
68 | }}
|
69 | className={props.open ? 'selected selected-open' : 'selected'}
|
70 | ref={function (input) {
|
71 | if (input && props.open) {
|
72 | topBar = input;
|
73 | focus();
|
74 | }
|
75 | }}
|
76 | >
|
77 | <div
|
78 | className={Object.keys(props.items).length == 0 ? 'top-bar top-bar-empty' : 'top-bar'}>
|
79 | { Object.keys(props.items).length == 0 ? 'No options available' :
|
80 | props.selectedItemLabel
|
81 | ? props.selectedItemLabel
|
82 | : 'Please select...'}
|
83 | </div>
|
84 | </a>
|
85 |
|
86 | <div className={props.open ? 'results-container open' : 'results-container' }>
|
87 | <div className="input-container">
|
88 | <input
|
89 | type="text"
|
90 | autoCorrect="off"
|
91 | autoCapitalize="off"
|
92 | spellCheck="false"
|
93 | autoComplete="off"
|
94 | ref={(item) => {
|
95 | if (item && props.open) {
|
96 | item.focus()
|
97 | }
|
98 | }}
|
99 | value={props.visibilityFilter}
|
100 | onKeyPress={(e) => {
|
101 | if (e.key === 'Enter' && props.open) {
|
102 | props.submit({
|
103 | selected: props.currentlyHighlighted,
|
104 | selectedItemLabel: props.items[props.currentlyHighlighted]
|
105 | });
|
106 | focus();
|
107 | }
|
108 | }}
|
109 | onChange={props.inputOnChange}
|
110 | onKeyDown={(e) => {
|
111 | props.inputOnKeyDown(e);
|
112 | if (e.key == 'Escape') {
|
113 | focus();
|
114 | }
|
115 | }}
|
116 | />
|
117 | </div>
|
118 | {visibleItems.length > 0 ? visibleItems : <NoItems/>}
|
119 | </div>
|
120 | </div>
|
121 | );
|
122 | };
|
123 |
|
124 | export const Select = ({items, selected = null, tabIndex = null, onChange}) => {
|
125 |
|
126 | const store = createStore(reducers);
|
127 |
|
128 | store.dispatch({type: '@@redux/INIT'});
|
129 |
|
130 | window.store = store;
|
131 |
|
132 | store.dispatch({type: actions.SET_ITEMS, payload: items});
|
133 |
|
134 | if (selected) {
|
135 | store.dispatch({
|
136 | type: actions.SET_SELECTED, payload: {
|
137 | selected: selected,
|
138 | selectedItemLabel: items[selected]
|
139 | }
|
140 | });
|
141 | }
|
142 |
|
143 | if (tabIndex) {
|
144 | store.dispatch({type: actions.SET_TABINDEX, payload: tabIndex})
|
145 | }
|
146 |
|
147 | const mapStateToProps = (state = {}) => {
|
148 | return state;
|
149 | };
|
150 |
|
151 | const mapDispatchToProps = (dispatch) => {
|
152 | return {
|
153 | submit: (item) => {
|
154 | if (item.selected) {
|
155 | dispatch({type: actions.SET_SELECTED, payload: item});
|
156 | dispatch({type: actions.SET_OPEN, payload: false});
|
157 | dispatch({type: actions.SET_FILTER, payload: ''});
|
158 | onChange(item.selected);
|
159 | }
|
160 | },
|
161 | linkOnKeyPress: (e) => {
|
162 | if (e.key != 'Escape') {
|
163 | dispatch({type: actions.SET_OPEN, payload: true});
|
164 | dispatch({type: actions.SET_FILTER, payload: ''});
|
165 | }
|
166 | },
|
167 |
|
168 | inputOnChange: (e) => {
|
169 | dispatch({type: actions.SET_FILTER, payload: e.target.value})
|
170 | },
|
171 | inputOnKeyDown: (e) => {
|
172 | if (e.key === 'ArrowDown') {
|
173 | dispatch({type: actions.SET_NEXT_HIGHLIGHTED, payload: false})
|
174 | }
|
175 |
|
176 | if (e.key === 'ArrowUp') {
|
177 | dispatch({type: actions.SET_PREV_HIGHLIGHTED, payload: false})
|
178 | }
|
179 |
|
180 | if (e.key === 'Escape') {
|
181 | dispatch({type: actions.SET_OPEN, payload: false});
|
182 | dispatch({type: actions.SET_FILTER, payload: ''});
|
183 | }
|
184 | },
|
185 | topBarOnClick: () => {
|
186 | dispatch({type: actions.TOGGLE_OPEN});
|
187 | },
|
188 |
|
189 | initialRenderFalse: () => {
|
190 | dispatch({type: actions.SET_INITIAL_RENDER_FALSE});
|
191 | },
|
192 | refresh: () => {
|
193 | dispatch({type: actions.SET_OPEN, payload: false});
|
194 | dispatch({type: actions.SET_FILTER, payload: ''});
|
195 | }
|
196 | }
|
197 | };
|
198 |
|
199 | const SelectWithStore = connect(
|
200 | mapStateToProps,
|
201 | mapDispatchToProps
|
202 | )(Presentation);
|
203 |
|
204 | return (
|
205 | <SelectWithStore store={store}/>
|
206 | )
|
207 | };
|