150 lines
4.2 KiB
JavaScript
150 lines
4.2 KiB
JavaScript
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import ClassNames from 'classnames';
|
|
import Input from '../input';
|
|
import events from '../utils/events';
|
|
import style from './style';
|
|
|
|
class Dropdown extends React.Component {
|
|
static propTypes = {
|
|
allowBlank: React.PropTypes.bool,
|
|
auto: React.PropTypes.bool,
|
|
className: React.PropTypes.string,
|
|
disabled: React.PropTypes.bool,
|
|
error: React.PropTypes.string,
|
|
label: React.PropTypes.string,
|
|
onBlur: React.PropTypes.func,
|
|
onChange: React.PropTypes.func,
|
|
onFocus: React.PropTypes.func,
|
|
source: React.PropTypes.array.isRequired,
|
|
template: React.PropTypes.func,
|
|
value: React.PropTypes.oneOfType([
|
|
React.PropTypes.string,
|
|
React.PropTypes.number
|
|
])
|
|
};
|
|
|
|
static defaultProps = {
|
|
auto: true,
|
|
className: '',
|
|
allowBlank: true,
|
|
disabled: false
|
|
};
|
|
|
|
state = {
|
|
active: false,
|
|
up: false
|
|
};
|
|
|
|
componentWillUpdate (nextProps, nextState) {
|
|
if (!this.state.active && nextState.active) {
|
|
events.addEventsToDocument({click: this.handleDocumentClick});
|
|
}
|
|
}
|
|
|
|
componentDidUpdate (prevProps, prevState) {
|
|
if (prevState.active && !this.state.active) {
|
|
events.removeEventsFromDocument({click: this.handleDocumentClick});
|
|
}
|
|
}
|
|
|
|
componentWillUnmount () {
|
|
if (this.state.active) {
|
|
events.removeEventsFromDocument({click: this.handleDocumentClick});
|
|
}
|
|
}
|
|
|
|
close = () => {
|
|
if (this.state.active) {
|
|
this.setState({active: false});
|
|
}
|
|
}
|
|
|
|
handleDocumentClick = (event) => {
|
|
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
|
|
this.setState({active: false});
|
|
}
|
|
};
|
|
|
|
handleMouseDown = (event) => {
|
|
events.pauseEvent(event);
|
|
const client = event.target.getBoundingClientRect();
|
|
const screen_height = window.innerHeight || document.documentElement.offsetHeight;
|
|
const up = this.props.auto ? client.top > ((screen_height / 2) + client.height) : false;
|
|
if (this.props.onFocus) this.props.onFocus(event);
|
|
this.setState({active: true, up});
|
|
};
|
|
|
|
handleSelect = (item, event) => {
|
|
if (this.props.onBlur) this.props.onBlur(event);
|
|
if (!this.props.disabled && this.props.onChange) {
|
|
this.props.onChange(item, event);
|
|
this.setState({active: false});
|
|
}
|
|
};
|
|
|
|
getSelectedItem = () => {
|
|
for (const item of this.props.source) {
|
|
if (item.value === this.props.value) return item;
|
|
}
|
|
if (!this.props.allowBlank) {
|
|
return this.props.source[0];
|
|
}
|
|
};
|
|
|
|
renderTemplateValue (selected) {
|
|
const className = ClassNames(style.field, {
|
|
[style.errored]: this.props.error,
|
|
[style.disabled]: this.props.disabled
|
|
});
|
|
|
|
return (
|
|
<div className={className} onMouseDown={this.handleMouseDown}>
|
|
<div className={`${style.templateValue} ${style.value}`}>
|
|
{this.props.template(selected)}
|
|
</div>
|
|
{this.props.label ? <label className={style.label}>{this.props.label}</label> : null}
|
|
{this.props.error ? <span className={style.error}>{this.props.error}</span> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
renderValue (item, idx) {
|
|
const className = item.value === this.props.value ? style.selected : null;
|
|
return (
|
|
<li key={idx} className={className} onMouseDown={this.handleSelect.bind(this, item.value)}>
|
|
{this.props.template ? this.props.template(item) : item.label}
|
|
</li>
|
|
);
|
|
}
|
|
|
|
render () {
|
|
const {template, source, ...others} = this.props;
|
|
const selected = this.getSelectedItem();
|
|
const className = ClassNames(style.root, {
|
|
[style.up]: this.state.up,
|
|
[style.active]: this.state.active,
|
|
[style.disabled]: this.props.disabled
|
|
}, this.props.className);
|
|
|
|
return (
|
|
<div data-react-toolbox='dropdown' className={className}>
|
|
<Input
|
|
{...others}
|
|
className={style.value}
|
|
onMouseDown={this.handleMouseDown}
|
|
readOnly
|
|
type={template && selected ? 'hidden' : null}
|
|
value={selected && selected.label}
|
|
/>
|
|
{template && selected ? this.renderTemplateValue(selected) : null}
|
|
<ul className={style.values} ref='values'>
|
|
{source.map(this.renderValue.bind(this))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Dropdown;
|