react-toolbox/components/dropdown/Dropdown.js

174 lines
5.3 KiB
JavaScript
Raw Normal View History

2016-05-29 20:37:40 +03:00
import React, { Component, PropTypes } from 'react';
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,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onFocus: PropTypes.func,
source: PropTypes.array.isRequired,
template: PropTypes.func,
theme: PropTypes.shape({
active: PropTypes.string.isRequired,
disabled: PropTypes.string.isRequired,
dropdown: PropTypes.string.isRequired,
error: PropTypes.string.isRequired,
errored: PropTypes.string.isRequired,
field: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
selected: PropTypes.string.isRequired,
templateValue: PropTypes.string.isRequired,
up: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
values: PropTypes.string.isRequired
}),
value: PropTypes.oneOfType([
PropTypes.string,
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});
}
}
2016-05-29 20:37:40 +03:00
componentDidUpdate (prevProps, prevState) {
if (prevState.active && !this.state.active) {
events.removeEventsFromDocument({click: this.handleDocumentClick});
}
}
2016-05-29 20:37:40 +03:00
componentWillUnmount () {
if (this.state.active) {
events.removeEventsFromDocument({click: this.handleDocumentClick});
}
}
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});
}
};
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 { theme } = this.props;
const className = classnames(theme.field, {
[theme.errored]: this.props.error,
[theme.disabled]: this.props.disabled
});
return (
<div className={className} onMouseDown={this.handleMouseDown}>
<div className={`${theme.templateValue} ${theme.value}`}>
{this.props.template(selected)}
</div>
{this.props.label ? <label className={theme.label}>{this.props.label}</label> : null}
{this.props.error ? <span className={theme.error}>{this.props.error}</span> : null}
</div>
);
2015-09-19 18:42:57 +03:00
}
2016-05-29 20:37:40 +03:00
renderValue (item, idx) {
const { theme } = this.props;
const className = item.value === this.props.value ? theme.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>
);
}
2016-05-29 20:37:40 +03:00
render () {
const {template, theme, source, ...others} = this.props;
const selected = this.getSelectedItem();
const className = classnames(theme.dropdown, {
[theme.up]: this.state.up,
[theme.active]: this.state.active,
[theme.disabled]: this.props.disabled
}, this.props.className);
return (
<div data-react-toolbox='dropdown' className={className}>
<Input
{...others}
className={theme.value}
onMouseDown={this.handleMouseDown}
readOnly
type={template && selected ? 'hidden' : null}
value={selected && selected.label}
/>
{template && selected ? this.renderTemplateValue(selected) : null}
<ul className={theme.values} ref='values'>
{source.map(this.renderValue.bind(this))}
</ul>
</div>
);
}
}
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 };