react-blur-admin
Version:
React Blur Admin - Styles And Components
250 lines (212 loc) • 6.75 kB
JavaScript
import _ from 'lodash';
import React from 'react';
export class Select extends React.Component {
static propTypes = {
placeholder: React.PropTypes.string,
maxHeight: React.PropTypes.string,
onChange: React.PropTypes.func,
onRenderValue: React.PropTypes.func,
options: React.PropTypes.arrayOf(
React.PropTypes.shape({
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]),
label: React.PropTypes.node,
}),
),
value: React.PropTypes.node,
isSearchable: React.PropTypes.bool,
isOpen: React.PropTypes.bool,
onSearch: React.PropTypes.func,
onToggleOpen: React.PropTypes.func, // used when the parent needs to know that isOpen was toggled
}
static defaultProps = {
placeholder: '',
onChange: _.noop,
value: '',
isSearchable: false,
isOpen: false,
onToggleOpen: _.noop,
}
constructor(props) {
super(props);
this.state = {
value: this.getValue(),
activeIndex: 0,
isOpen: this.props.isOpen,
searchValue: '',
visibleOptions: this.props.options,
};
}
componentDidMount() {
this.onFocus();
}
componentWillReceiveProps(nextProps) {
if (nextProps.isOpen !== this.props.isOpen) {
this.setState({isOpen: nextProps.isOpen}, this.onFocus);
}
if (nextProps.options !== this.props.options) {
this.setState({visibleOptions: nextProps.options, searchValue: ''});
}
if (this.props.value && ! nextProps.value) {
this.setState({activeIndex: 0, value: this.getValue(nextProps)});
}
this.setState({value: this.getValue(nextProps)});
}
onFocus() {
if (this.state.isOpen && this.props.isSearchable) {
this.refs['select-search'].focus();
}
}
onToggleOpen() {
this.props.onToggleOpen(! this.state.isOpen);
this.setState({ isOpen: ! this.state.isOpen }, this.onFocus);
}
onSetActiveIndex(value) {
this.setState({activeIndex: value, isOpen: true}, this.onFocus);
}
onSelectValue(selectedValue) {
const selectedOpt = _.find(this.props.options, { value: selectedValue });
const value = selectedOpt && selectedOpt.label ? selectedOpt.label : this.props.placeholder;
this.setState({ isOpen: false, value });
this.props.onChange(selectedValue);
}
onTextSearch(event) {
let visibleOptions;
const searchValue = event.currentTarget.value;
// Used if the developer needs custom search functionality.
if (this.props.onSearch) {
visibleOptions = this.props.onSearch(searchValue, this.props.options);
} else {
visibleOptions = this.getVisibleOptions(event.currentTarget.value);
}
this.setState({searchValue, visibleOptions});
}
onHandleKeyDown(e) {
if (e.keyCode === 27) { // esc
return this.onToggleOpen();
} else if (e.keyCode === 13) { // enter
e.preventDefault(); // prevent the onClick event from firing also, which could reopen select options
const selectedOption = _.find(this.state.visibleOptions, (option, index) => {
return index === this.state.activeIndex;
});
if (selectedOption) {
return this.onSelectValue(selectedOption.value);
}
} else if (e.keyCode === 40) { // down
e.preventDefault(); // prevent browser scrolling
let activeIndex = this.state.activeIndex + 1;
if (activeIndex >= this.state.visibleOptions.length) {
activeIndex = this.state.visibleOptions.length - 1; // - 1 because the index starts at 0
}
return this.onSetActiveIndex(activeIndex);
} else if (e.keyCode === 38) { // up
e.preventDefault(); // prevent browser scrolling
let activeIndex = this.state.activeIndex - 1;
if (activeIndex < 0) {
activeIndex = 0;
}
return this.onSetActiveIndex(activeIndex);
}
return e;
}
getValue(props = this.props) {
if (props.value && props.onRenderValue) {
return props.onRenderValue(props.value);
}
const option = _.find(props.options, {value: props.value});
return option && option.label || props.placeholder;
}
getVisibleOptions(searchValue) {
if (! searchValue) {
return this.props.options;
}
return _.filter(this.props.options, option => {
return option.label.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1;
});
}
isOpen() {
return this.state.isOpen ? 'open' : '';
}
renderValue() {
return (
<span className='filter-option pull-left'>
{this.state.value}
</span>
);
}
renderSearch() {
if (! this.props.isSearchable) {
return null;
}
return (
<div className='bs-searchbox'>
<input
ref='select-search'
type='text'
className='form-control'
value={this.state.searchValue}
onKeyDown={e => this.onHandleKeyDown(e)}
onChange={e => this.onTextSearch(e)} />
</div>
);
}
renderOption(option, index, isSelected, isActive) {
return (
<li
key={index}
className={`${isSelected} ${isActive}`}
onClick={e => this.onSelectValue(option.value)}
onMouseOver={e => this.onSetActiveIndex(index)}>
<a tabIndex={index}>
<i className={isSelected ? 'fa fa-check' : ''} /> <span className='text'>{option.label}</span>
</a>
</li>
);
}
renderOptions() {
if (! this.props.options) {
return null;
}
let options = _.map(this.state.visibleOptions, (option, index) => {
const isSelected = this.props.value === option.value ? 'selected' : '';
const isActive = this.state.activeIndex === index ? 'active' : '';
return this.renderOption(option, index, isSelected, isActive);
});
let style = {};
if (this.props.maxHeight) {
style = {
maxHeight: this.props.maxHeight,
overflow: 'auto',
};
}
return (
<ul style={style} className='dropdown-menu inner'>
{options}
</ul>
);
}
render() {
return (
<div className='form-group'>
<div className={`btn-group bootstrap-select form-control ${this.isOpen()}`}>
<button
type="button"
className='btn dropdown-toggle btn-default'
onClick={e => this.onToggleOpen()}
onKeyDown={e => this.onHandleKeyDown(e)}>
{this.renderValue()}
<span className='bs-caret'>
<span className='caret' />
</span>
</button>
<div className='dropdown-menu open'>
{this.renderSearch()}
{this.renderOptions()}
</div>
</div>
</div>
);
}
}