2016-05-29 20:37:40 +03:00
|
|
|
import React, { Component, PropTypes } from 'react';
|
2016-01-22 20:06:32 +03:00
|
|
|
import ReactDOM from 'react-dom';
|
2016-05-22 22:28:48 +03:00
|
|
|
import classnames from 'classnames';
|
|
|
|
import { themr } from 'react-css-themr';
|
2016-05-29 20:37:40 +03:00
|
|
|
import { DROPDOWN } from '../identifiers.js';
|
|
|
|
import InjectInput from '../input/Input.js';
|
|
|
|
import events from '../utils/events.js';
|
|
|
|
|
|
|
|
const factory = (Input) => {
|
|
|
|
class Dropdown extends Component {
|
|
|
|
static propTypes = {
|
|
|
|
allowBlank: PropTypes.bool,
|
|
|
|
auto: PropTypes.bool,
|
|
|
|
className: PropTypes.string,
|
|
|
|
disabled: PropTypes.bool,
|
|
|
|
error: PropTypes.string,
|
|
|
|
label: PropTypes.string,
|
2016-06-09 20:21:00 +03:00
|
|
|
name: PropTypes.string,
|
2016-05-29 20:37:40 +03:00
|
|
|
onBlur: PropTypes.func,
|
|
|
|
onChange: PropTypes.func,
|
2016-08-27 18:42:29 +03:00
|
|
|
onClick: PropTypes.func,
|
2016-05-29 20:37:40 +03:00
|
|
|
onFocus: PropTypes.func,
|
2016-10-11 23:12:12 +03:00
|
|
|
required: PropTypes.bool,
|
2016-05-29 20:37:40 +03:00
|
|
|
source: PropTypes.array.isRequired,
|
|
|
|
template: PropTypes.func,
|
|
|
|
theme: PropTypes.shape({
|
2016-06-04 00:44:33 +03:00
|
|
|
active: PropTypes.string,
|
|
|
|
disabled: PropTypes.string,
|
|
|
|
dropdown: PropTypes.string,
|
|
|
|
error: PropTypes.string,
|
|
|
|
errored: PropTypes.string,
|
|
|
|
field: PropTypes.string,
|
|
|
|
label: PropTypes.string,
|
2016-10-11 23:12:12 +03:00
|
|
|
required: PropTypes.bool,
|
2016-06-04 00:44:33 +03:00
|
|
|
selected: PropTypes.string,
|
|
|
|
templateValue: PropTypes.string,
|
|
|
|
up: PropTypes.string,
|
|
|
|
value: PropTypes.string,
|
|
|
|
values: PropTypes.string
|
2016-05-29 20:37:40 +03:00
|
|
|
}),
|
|
|
|
value: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.number
|
|
|
|
])
|
|
|
|
};
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
auto: true,
|
|
|
|
className: '',
|
|
|
|
allowBlank: true,
|
2016-10-11 23:12:12 +03:00
|
|
|
disabled: false,
|
|
|
|
required: false
|
2016-05-29 20:37:40 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
state = {
|
|
|
|
active: false,
|
|
|
|
up: false
|
|
|
|
};
|
|
|
|
|
|
|
|
componentWillUpdate (nextProps, nextState) {
|
|
|
|
if (!this.state.active && nextState.active) {
|
|
|
|
events.addEventsToDocument({click: this.handleDocumentClick});
|
|
|
|
}
|
2016-01-22 20:06:32 +03:00
|
|
|
}
|
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
componentDidUpdate (prevProps, prevState) {
|
|
|
|
if (prevState.active && !this.state.active) {
|
|
|
|
events.removeEventsFromDocument({click: this.handleDocumentClick});
|
|
|
|
}
|
2016-01-23 11:42:56 +03:00
|
|
|
}
|
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
componentWillUnmount () {
|
|
|
|
if (this.state.active) {
|
|
|
|
events.removeEventsFromDocument({click: this.handleDocumentClick});
|
|
|
|
}
|
2016-02-10 19:48:14 +03:00
|
|
|
}
|
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
close = () => {
|
|
|
|
if (this.state.active) {
|
|
|
|
this.setState({active: false});
|
|
|
|
}
|
2016-03-23 14:35:24 +03:00
|
|
|
}
|
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
handleDocumentClick = (event) => {
|
|
|
|
if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
|
|
|
|
this.setState({active: false});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-27 04:19:51 +03:00
|
|
|
handleClick = (event) => {
|
2016-05-29 20:37:40 +03:00
|
|
|
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;
|
2016-08-27 04:19:51 +03:00
|
|
|
if (this.props.onClick) this.props.onClick(event);
|
2016-08-27 18:42:29 +03:00
|
|
|
if (this.props.onFocus) this.props.onFocus(event);
|
2016-05-29 20:37:40 +03:00
|
|
|
this.setState({active: true, up});
|
|
|
|
};
|
|
|
|
|
|
|
|
handleSelect = (item, event) => {
|
|
|
|
if (this.props.onBlur) this.props.onBlur(event);
|
|
|
|
if (!this.props.disabled && this.props.onChange) {
|
2016-06-09 20:21:00 +03:00
|
|
|
if (this.props.name) {
|
|
|
|
event.target.name = this.props.name;
|
|
|
|
}
|
2016-05-29 20:37:40 +03:00
|
|
|
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 { theme } = this.props;
|
|
|
|
const className = classnames(theme.field, {
|
|
|
|
[theme.errored]: this.props.error,
|
2016-10-11 23:12:12 +03:00
|
|
|
[theme.disabled]: this.props.disabled,
|
|
|
|
[theme.required]: this.props.required
|
2016-05-29 20:37:40 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
2016-08-27 04:19:51 +03:00
|
|
|
<div className={className} onClick={this.handleClick}>
|
2016-05-29 20:37:40 +03:00
|
|
|
<div className={`${theme.templateValue} ${theme.value}`}>
|
|
|
|
{this.props.template(selected)}
|
|
|
|
</div>
|
2016-10-11 23:12:12 +03:00
|
|
|
{this.props.label
|
|
|
|
? <label className={theme.label}>
|
|
|
|
{this.props.label}
|
|
|
|
{this.props.required ? <span className={theme.required}> * </span> : null}
|
|
|
|
</label>
|
|
|
|
: null}
|
2016-05-29 20:37:40 +03:00
|
|
|
{this.props.error ? <span className={theme.error}>{this.props.error}</span> : null}
|
|
|
|
</div>
|
|
|
|
);
|
2015-09-19 18:42:57 +03:00
|
|
|
}
|
|
|
|
|
2016-08-06 23:44:05 +03:00
|
|
|
renderValue = (item, idx) => {
|
2016-05-29 20:37:40 +03:00
|
|
|
const { theme } = this.props;
|
|
|
|
const className = item.value === this.props.value ? theme.selected : null;
|
|
|
|
return (
|
2016-08-27 04:19:51 +03:00
|
|
|
<li key={idx} className={className} onClick={this.handleSelect.bind(this, item.value)}>
|
2016-05-29 20:37:40 +03:00
|
|
|
{this.props.template ? this.props.template(item) : item.label}
|
|
|
|
</li>
|
|
|
|
);
|
2016-08-06 23:44:05 +03:00
|
|
|
};
|
2015-12-20 19:01:02 +03:00
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
render () {
|
2016-10-11 23:12:12 +03:00
|
|
|
const {template, theme, source, allowBlank, auto, required, ...others} = this.props; //eslint-disable-line no-unused-vars
|
2016-05-29 20:37:40 +03:00
|
|
|
const selected = this.getSelectedItem();
|
|
|
|
const className = classnames(theme.dropdown, {
|
|
|
|
[theme.up]: this.state.up,
|
|
|
|
[theme.active]: this.state.active,
|
2016-10-11 23:12:12 +03:00
|
|
|
[theme.disabled]: this.props.disabled,
|
|
|
|
[theme.required]: this.props.required
|
2016-05-29 20:37:40 +03:00
|
|
|
}, this.props.className);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div data-react-toolbox='dropdown' className={className}>
|
|
|
|
<Input
|
|
|
|
{...others}
|
|
|
|
className={theme.value}
|
2016-08-27 04:19:51 +03:00
|
|
|
onClick={this.handleClick}
|
2016-10-11 23:12:12 +03:00
|
|
|
required={this.props.required}
|
2016-05-29 20:37:40 +03:00
|
|
|
readOnly
|
|
|
|
type={template && selected ? 'hidden' : null}
|
2016-06-23 20:23:42 +03:00
|
|
|
value={selected && selected.label ? selected.label : ''}
|
2016-05-29 20:37:40 +03:00
|
|
|
/>
|
|
|
|
{template && selected ? this.renderTemplateValue(selected) : null}
|
|
|
|
<ul className={theme.values} ref='values'>
|
2016-08-06 23:44:05 +03:00
|
|
|
{source.map(this.renderValue)}
|
2016-05-29 20:37:40 +03:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2015-10-20 08:40:51 +03:00
|
|
|
}
|
2015-10-09 16:55:00 +03:00
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
return Dropdown;
|
|
|
|
};
|
2015-10-22 02:31:17 +03:00
|
|
|
|
2016-05-29 20:37:40 +03:00
|
|
|
const Dropdown = factory(InjectInput);
|
|
|
|
export default themr(DROPDOWN)(Dropdown);
|
|
|
|
export { factory as dropdownFactory };
|
|
|
|
export { Dropdown };
|