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