select-react-redux
Version:
A searchable select box, similar to select2.
229 lines (200 loc) • 7.2 kB
JavaScript
import React from 'react';
import {render} from 'react-dom';
import {connect} from 'react-redux';
import {createStore} from 'redux';
import actions from './actions';
import {reducers} from './reducers';
const NoItems = () => {
return (
<div
key={null}
className="item item-no-results"
>No results found</div>
);
};
const Presentation = ({...props}) => {
let topBar;
const visibleItems = Object.keys(props.visibleItems).map((item) => {
return (
<div
onClick={() => {
props.submit({
selected: item,
selectedItemLabel: props.items[item]
});
focus();
}}
key={item}
className={
(item == props.currentlyHighlighted) ? 'item item-selected' : 'item'
}
>
{props.items[item]}
</div>
)
});
const focus = () => {
if (topBar)
topBar.focus();
};
return (
<div
className="select-react-redux-container"
ref={(input) => {
if (props.initialRender && input) {
props.initialRenderFalse();
document.addEventListener('click', function (event) {
if (!input.contains(event.target)) {
props.refresh();
}
});
}
}}>
<a
tabIndex={props.tabIndex}
onClick={props.topBarOnClick}
onKeyPress={props.linkOnKeyPress}
autoFocus
onKeyDown={e => {
if (e.key.indexOf('Arrow') == 0) {
props.linkOnKeyPress(e)
}
}}
className={props.open ? 'selected selected-open' : 'selected'}
ref={function (input) {
if (input && props.open) {
topBar = input;
focus();
}
}}
>
<div
className={Object.keys(props.items).length == 0 ? 'top-bar top-bar-empty' : 'top-bar'}>
{ Object.keys(props.items).length == 0 ? 'No options available' :
props.selectedItemLabel
? props.selectedItemLabel
: 'Please select...'}
</div>
</a>
<div className={props.open ? 'results-container open' : 'results-container' }>
<div className="input-container">
<input
type="text"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
autoComplete="off"
ref={(item) => {
if (item && props.open) {
item.focus()
}
}}
value={props.visibilityFilter}
onKeyPress={(e) => {
if (e.key === 'Enter' && props.open) {
props.submit({
selected: props.currentlyHighlighted,
selectedItemLabel: props.items[props.currentlyHighlighted]
});
focus();
e.preventDefault();
return false;
}
}}
onChange={props.inputOnChange}
onKeyDown={(e) => {
props.inputOnKeyDown(e);
if (e.key == 'Escape') {
focus();
}
}}
/>
</div>
{visibleItems.length > 0 ? visibleItems : <NoItems/>}
</div>
</div>
);
};
const Stateless = ({items, selected = null, tabIndex = null, onChange}) => {
const store = createStore(reducers);
store.dispatch({type: '@@redux/INIT'});
window.store = store;
store.dispatch({type: actions.SET_ITEMS, payload: items});
if (selected) {
store.dispatch({
type: actions.SET_SELECTED, payload: {
selected: selected,
selectedItemLabel: items[selected]
}
});
}
if (tabIndex) {
store.dispatch({type: actions.SET_TABINDEX, payload: tabIndex})
}
const mapStateToProps = (state = {}) => {
return state;
};
const mapDispatchToProps = (dispatch) => {
return {
submit: (item) => {
if (item.selected) {
dispatch({type: actions.SET_SELECTED, payload: item});
dispatch({type: actions.SET_OPEN, payload: false});
dispatch({type: actions.SET_FILTER, payload: ''});
onChange(item.selected);
}
},
linkOnKeyPress: (e) => {
if (e.key != 'Escape') {
dispatch({type: actions.SET_OPEN, payload: true});
dispatch({type: actions.SET_FILTER, payload: ''});
}
},
inputOnChange: (e) => {
dispatch({type: actions.SET_FILTER, payload: e.target.value})
},
inputOnKeyDown: (e) => {
if (e.key === 'ArrowDown') {
dispatch({type: actions.SET_NEXT_HIGHLIGHTED, payload: false})
}
if (e.key === 'ArrowUp') {
dispatch({type: actions.SET_PREV_HIGHLIGHTED, payload: false})
}
if (e.key === 'Escape') {
dispatch({type: actions.SET_OPEN, payload: false});
dispatch({type: actions.SET_FILTER, payload: ''});
}
},
topBarOnClick: (e) => {
dispatch({type: actions.TOGGLE_OPEN});
},
initialRenderFalse: () => {
dispatch({type: actions.SET_INITIAL_RENDER_FALSE});
},
refresh: () => {
dispatch({type: actions.SET_OPEN, payload: false});
dispatch({type: actions.SET_FILTER, payload: ''});
}
}
};
const SelectWithStore = connect(
mapStateToProps,
mapDispatchToProps
)(Presentation);
return (
<SelectWithStore store={store}/>
)
};
export class Select extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(newProps) {
return JSON.stringify(newProps.items) != JSON.stringify(this.props.items);
}
render() {
return (
<Stateless {...this.props} />
)
}
}