Clearable dropdown & autocomplete, fix dropdown behaviour, always-controlled inputs
parent
9020a14bba
commit
83dd1c93b7
|
@ -19,6 +19,8 @@ const POSITION = {
|
|||
const factory = (Chip, Input) => {
|
||||
class Autocomplete extends Component {
|
||||
static propTypes = {
|
||||
allowClear: PropTypes.bool,
|
||||
clearTooltip: PropTypes.string,
|
||||
allowCreate: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
direction: PropTypes.oneOf(['auto', 'up', 'down']),
|
||||
|
@ -60,6 +62,8 @@ const factory = (Chip, Input) => {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
allowClear: false,
|
||||
clearTooltip: 'Clear',
|
||||
allowCreate: false,
|
||||
className: '',
|
||||
direction: 'auto',
|
||||
|
@ -390,7 +394,7 @@ const factory = (Chip, Input) => {
|
|||
|
||||
render() {
|
||||
const {
|
||||
allowCreate, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
|
||||
allowClear, allowCreate, clearTooltip, error, label, source, suggestionMatch, query, // eslint-disable-line no-unused-vars
|
||||
selectedPosition, keepFocusOnChange, showSuggestionsWhenValueIsSet, showSelectedWhenNotInSource, onQueryChange, // eslint-disable-line no-unused-vars
|
||||
theme, ...other
|
||||
} = this.props;
|
||||
|
@ -400,12 +404,16 @@ const factory = (Chip, Input) => {
|
|||
|
||||
return (
|
||||
<div data-react-toolbox="autocomplete" className={className}>
|
||||
{this.props.selectedPosition === 'above' ? this.renderSelected() : null}
|
||||
{selectedPosition === 'above' ? this.renderSelected() : null}
|
||||
{allowClear && this.state.query != '' ? <span
|
||||
className={'material-icons '+theme.clear}
|
||||
title={clearTooltip}
|
||||
onClick={(e) => this.handleChange([], e)}>clear</span> : null}
|
||||
<Input
|
||||
{...other}
|
||||
ref={(node) => { this.inputNode = node; }}
|
||||
autoComplete="off"
|
||||
className={theme.input}
|
||||
className={theme.input+(allowClear && this.state.query != '' ? ' '+theme.withclear : '')}
|
||||
error={error}
|
||||
label={label}
|
||||
onBlur={this.handleQueryBlur}
|
||||
|
|
|
@ -19,6 +19,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.clear {
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 12px;
|
||||
left: -4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.withclear input {
|
||||
text-indent: 28px !important;
|
||||
}
|
||||
|
||||
.values {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -12,6 +12,8 @@ const factory = (Input) => {
|
|||
class Dropdown extends Component {
|
||||
static propTypes = {
|
||||
allowBlank: PropTypes.bool,
|
||||
allowClear: PropTypes.bool,
|
||||
clearTooltip: PropTypes.string,
|
||||
auto: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
|
@ -55,6 +57,8 @@ const factory = (Input) => {
|
|||
auto: true,
|
||||
className: '',
|
||||
allowBlank: true,
|
||||
allowClear: false,
|
||||
clearTooltip: 'Clear',
|
||||
disabled: false,
|
||||
labelKey: 'label',
|
||||
required: false,
|
||||
|
@ -99,7 +103,6 @@ const factory = (Input) => {
|
|||
};
|
||||
|
||||
handleSelect = (item, event) => {
|
||||
if (this.props.onBlur) this.props.onBlur(event);
|
||||
if (!this.props.disabled && this.props.onChange) {
|
||||
if (this.props.name) event.target.name = this.props.name;
|
||||
this.props.onChange(item, event);
|
||||
|
@ -122,6 +125,7 @@ const factory = (Input) => {
|
|||
close = () => {
|
||||
if (this.state.active) {
|
||||
this.setState({ active: false });
|
||||
if (this.props.onBlur) this.props.onBlur(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,18 +135,17 @@ const factory = (Input) => {
|
|||
const screenHeight = window.innerHeight || document.documentElement.offsetHeight;
|
||||
const up = this.props.auto ? client.top > ((screenHeight / 2) + client.height) : false;
|
||||
this.setState({ active: true, up });
|
||||
if (this.props.onFocus) this.props.onFocus(event);
|
||||
};
|
||||
|
||||
handleFocus = (event) => {
|
||||
event.stopPropagation();
|
||||
if (!this.props.disabled) this.open(event);
|
||||
if (this.props.onFocus) this.props.onFocus(event);
|
||||
};
|
||||
|
||||
handleBlur = (event) => {
|
||||
event.stopPropagation();
|
||||
if (this.state.active) this.close();
|
||||
if (this.props.onBlur) this.props.onBlur(event);
|
||||
}
|
||||
|
||||
renderTemplateValue(selected) {
|
||||
|
@ -189,7 +192,7 @@ const factory = (Input) => {
|
|||
|
||||
render() {
|
||||
const {
|
||||
allowBlank, auto, labelKey, required, onChange, onFocus, onBlur, // eslint-disable-line no-unused-vars
|
||||
allowBlank, allowClear, clearTooltip, auto, labelKey, required, onChange, onFocus, onBlur, // eslint-disable-line no-unused-vars
|
||||
source, template, theme, valueKey, ...others
|
||||
} = this.props;
|
||||
const selected = this.getSelectedItem();
|
||||
|
@ -198,21 +201,27 @@ const factory = (Input) => {
|
|||
[theme.active]: this.state.active,
|
||||
[theme.disabled]: this.props.disabled,
|
||||
[theme.required]: this.props.required,
|
||||
[theme.withclear]: allowClear && selected,
|
||||
}, this.props.className);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={{outline: 'none'}}
|
||||
data-react-toolbox="dropdown"
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
tabIndex="-1"
|
||||
>
|
||||
{allowClear && selected ? <span
|
||||
className={'material-icons '+theme.clear}
|
||||
title={clearTooltip}
|
||||
onClick={(e) => this.props.onChange(null, e)}>clear</span> : null}
|
||||
<Input
|
||||
{...others}
|
||||
tabIndex="0"
|
||||
className={theme.value}
|
||||
onClick={this.handleClick}
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
required={this.props.required}
|
||||
readOnly
|
||||
type={template && selected ? 'hidden' : null}
|
||||
|
@ -221,7 +230,7 @@ const factory = (Input) => {
|
|||
value={selected && selected[labelKey] ? selected[labelKey] : ''}
|
||||
/>
|
||||
{template && selected ? this.renderTemplateValue(selected) : null}
|
||||
<ul className={theme.values}>
|
||||
<ul className={theme.values} style={this.state.up ? {bottom: '100%'} : {top: '100%'}}>
|
||||
{source.map(this.renderValue)}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -42,6 +42,10 @@
|
|||
cursor: normal;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.withclear input {
|
||||
text-indent: 28px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
|
@ -164,3 +168,13 @@
|
|||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.clear {
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 12px;
|
||||
left: -4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ const factory = (FontIcon) => {
|
|||
disabled,
|
||||
required,
|
||||
type,
|
||||
value,
|
||||
value: value == null ? '' : value,
|
||||
};
|
||||
if (!multiline) {
|
||||
inputElementProps.maxLength = maxLength;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "react-toolbox",
|
||||
"description": "A set of React components implementing Google's Material Design specification with the power of CSS Modules.",
|
||||
"homepage": "http://www.react-toolbox.io",
|
||||
"version": "2.0.0-beta.11",
|
||||
"version": "2.0.0-beta.13",
|
||||
"main": "./lib",
|
||||
"module": "./components",
|
||||
"author": {
|
||||
|
|
Loading…
Reference in New Issue