Most functions done
parent
bef2601391
commit
72c9cab755
|
@ -3,7 +3,7 @@
|
|||
// For example, a text input with a popup selection list
|
||||
// ...Or maybe a button with a popup menu
|
||||
// (c) Vitaliy Filippov 2019+
|
||||
// Version 2019-08-27
|
||||
// Version 2019-08-30
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
@ -15,6 +15,7 @@ export class Picker extends React.Component
|
|||
direction: PropTypes.string,
|
||||
clearOnClick: PropTypes.bool,
|
||||
minWidth: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
renderInput: PropTypes.func.isRequired,
|
||||
renderPicker: PropTypes.func.isRequired,
|
||||
|
@ -75,7 +76,7 @@ export class Picker extends React.Component
|
|||
|
||||
render()
|
||||
{
|
||||
return (<div style={this.props.style}>
|
||||
return (<div style={this.props.style} className={this.props.className}>
|
||||
{this.props.renderInput({
|
||||
onFocus: this.focus,
|
||||
onBlur: this.blur,
|
||||
|
|
132
Selectbox.js
132
Selectbox.js
|
@ -1,16 +1,44 @@
|
|||
// Simple Dropdown/Autocomplete with single/multiple selection and easy customisation via CSS modules
|
||||
// Version 2019-08-27
|
||||
// Version 2019-08-30
|
||||
// (c) Vitaliy Filippov 2019+
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import autocomplete_css from './autocomplete.css';
|
||||
import input_css from './input.css';
|
||||
import { Picker } from './Picker.js';
|
||||
|
||||
export default class Selectbox extends React.PureComponent
|
||||
{
|
||||
static propTypes = {
|
||||
// multi-select
|
||||
multiple: PropTypes.bool,
|
||||
// make text input readonly (disable user input). still allows value change
|
||||
readOnly: PropTypes.bool,
|
||||
// show "clear" icon (cross)
|
||||
allowClear: PropTypes.bool,
|
||||
// select/autocomplete options
|
||||
source: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
// current value
|
||||
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
|
||||
// change callback
|
||||
onChange: PropTypes.func,
|
||||
// item name key - default "name"
|
||||
labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// item id key - default "id"
|
||||
valueKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
suggestionMatch: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['disabled'])]),
|
||||
suggestionMsg: PropTypes.any,
|
||||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
minWidth: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onQueryChange: PropTypes.func,
|
||||
}
|
||||
|
||||
state = {
|
||||
active: null,
|
||||
query: null,
|
||||
|
@ -19,7 +47,17 @@ export default class Selectbox extends React.PureComponent
|
|||
|
||||
setQuery = (ev) =>
|
||||
{
|
||||
this.setState({ query: ev.target.value });
|
||||
const query = ev.target.value;
|
||||
this.setState({ query });
|
||||
const f = this.props.onQueryChange;
|
||||
if (f)
|
||||
{
|
||||
f(query);
|
||||
}
|
||||
if (!query.length && !this.props.multiple && this.props.allowClear)
|
||||
{
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown = (ev) =>
|
||||
|
@ -45,6 +83,25 @@ export default class Selectbox extends React.PureComponent
|
|||
}
|
||||
}
|
||||
|
||||
clear = () =>
|
||||
{
|
||||
this.setState({ query: null });
|
||||
const f = this.props.onChange;
|
||||
f && f(null);
|
||||
}
|
||||
|
||||
removeValue = (ev) =>
|
||||
{
|
||||
const n = ev.currentTarget.getAttribute('data-n');
|
||||
if (n != null)
|
||||
{
|
||||
const v = [ ...this.props.value ];
|
||||
v.splice(n, 1);
|
||||
const f = this.props.onChange;
|
||||
f && f(v);
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDown = () =>
|
||||
{
|
||||
this.setState({ query: null });
|
||||
|
@ -66,7 +123,8 @@ export default class Selectbox extends React.PureComponent
|
|||
value.splice(already, 1);
|
||||
}
|
||||
}
|
||||
this.props.onChange(value);
|
||||
const f = this.props.onChange;
|
||||
f && f(value);
|
||||
}
|
||||
|
||||
onMouseOver = (ev) =>
|
||||
|
@ -87,32 +145,76 @@ export default class Selectbox extends React.PureComponent
|
|||
this.input.focus();
|
||||
}
|
||||
|
||||
onFocus = () =>
|
||||
{
|
||||
if (!this.props.multiple && this.state.active === null)
|
||||
{
|
||||
const v = this.props.value, vk = this.props.valueKey||'id';
|
||||
for (let i = 0; i < this.filtered_items.length; i++)
|
||||
{
|
||||
if (v == this.filtered_items[i][vk])
|
||||
{
|
||||
this.setState({ active: i });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.picker.focus();
|
||||
const f = this.props.onFocus;
|
||||
f && f();
|
||||
}
|
||||
|
||||
onBlur = () =>
|
||||
{
|
||||
this.picker.blur();
|
||||
const f = this.props.onBlur;
|
||||
f && f();
|
||||
if (!this.props.multiple && !this.props.allowClear && !this.input.value.length)
|
||||
{
|
||||
this.setState({ query: null });
|
||||
}
|
||||
}
|
||||
|
||||
renderInput = (p) =>
|
||||
{
|
||||
const value = this.state.query == null ? (this.props.multiple ? '' : this.item_hash[this.props.value]||'') : this.state.query;
|
||||
const value = this.state.query == null
|
||||
? (this.props.multiple ? '' : this.item_hash[this.props.value]||'')
|
||||
: this.state.query;
|
||||
const input = <input
|
||||
readOnly={this.props.readOnly}
|
||||
placeholder={this.props.multiple ? undefined : this.props.placeholder}
|
||||
ref={this.setInput}
|
||||
className={autocomplete_css.inputInputElement}
|
||||
style={this.props.multiple ? { height: '29px', padding: 0, display: 'inline-block', width: this.state.inputWidth+'px' } : undefined}
|
||||
onFocus={p.onFocus}
|
||||
className={autocomplete_css.inputElement}
|
||||
style={this.props.multiple ? { width: this.state.inputWidth+'px' } : undefined}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
value={value}
|
||||
onChange={this.setQuery}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>;
|
||||
return (<div ref={p.ref} className={autocomplete_css.input}
|
||||
style={this.props.multiple ? { fontSize: '13px', display: 'flex', overflow: 'hidden', cursor: 'text', height: 'auto' } : undefined}
|
||||
return (<div ref={p.ref}
|
||||
className={autocomplete_css.input + (this.props.disabled ? ' '+(autocomplete_css.disabled||'') : '') +
|
||||
(this.props.multiple
|
||||
? ' '+(autocomplete_css.multiple||'')
|
||||
: (this.props.allowClear ? ' '+(autocomplete_css.withClear||'') : ''))}
|
||||
onClick={this.props.multiple ? this.focusInput : undefined}>
|
||||
{this.props.multiple
|
||||
? <div style={{overflow: 'hidden', flex: 1, margin: '0 2em 0 .5em'}}>
|
||||
<span className={autocomplete_css.inputInputElement} ref={this.setSizer}
|
||||
style={{display: 'inline-block', width: 'auto', position: 'absolute', padding: '0 2px', top: '-100px', whiteSpace: 'pre', visibility: 'hidden'}}>
|
||||
? <div className={autocomplete_css.values}>
|
||||
<span className={autocomplete_css.inputElement+' '+autocomplete_css.sizer} ref={this.setSizer}>
|
||||
{value}
|
||||
</span>
|
||||
{(this.props.value||[]).map(id => <span style={{cursor: 'pointer', display: 'inline-block', borderRadius: '2px', background: '#e0f0ff', border: '1px solid #c0e0ff', padding: '3px', margin: '2px 5px 2px 0'}}>{this.item_hash[id]}</span>)}
|
||||
{(this.props.value||[]).map((id, idx) => <span className={autocomplete_css.value} key={idx}>
|
||||
{this.props.allowClear
|
||||
? <span data-n={idx} className={autocomplete_css.clearValue} onClick={this.removeValue}></span>
|
||||
: null}
|
||||
{this.item_hash[id]}
|
||||
</span>)}
|
||||
{input}
|
||||
</div>
|
||||
: input}
|
||||
{this.props.allowClear && !this.props.multiple && this.props.value != null
|
||||
? <div className={autocomplete_css.clear} onClick={this.clear}></div>
|
||||
: null}
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
@ -123,6 +225,7 @@ export default class Selectbox extends React.PureComponent
|
|||
className={autocomplete_css.suggestion+(this.state.active == i ? ' '+autocomplete_css.active : '')}>
|
||||
{e[this.props.labelKey||'name']}
|
||||
</div>))}
|
||||
{this.props.suggestionMsg}
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
@ -183,6 +286,7 @@ export default class Selectbox extends React.PureComponent
|
|||
clearOnClick={true}
|
||||
renderInput={this.renderInput}
|
||||
renderPicker={this.renderSuggestions}
|
||||
className={this.props.className}
|
||||
style={this.props.style}
|
||||
/>);
|
||||
}
|
||||
|
|
106
autocomplete.css
106
autocomplete.css
|
@ -1,18 +1,12 @@
|
|||
.autocomplete
|
||||
{
|
||||
padding: 0;
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.input
|
||||
{
|
||||
background: white;
|
||||
border: 1px solid #d8d8d8;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
padding: 0;
|
||||
height: 28px;
|
||||
height: 29px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -24,8 +18,8 @@
|
|||
position: absolute;
|
||||
right: 1em;
|
||||
text-align: right;
|
||||
font-size: 1.5em;
|
||||
margin-top: -0.375em;
|
||||
font-size: 20px;
|
||||
margin-top: -10px;
|
||||
font-family: FontAwesome;
|
||||
content: "\f107";
|
||||
color: #c0c0c0;
|
||||
|
@ -33,6 +27,15 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input.multiple
|
||||
{
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
height: auto;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.suggestions
|
||||
{
|
||||
margin-top: 0;
|
||||
|
@ -60,18 +63,7 @@
|
|||
background: #d6e8f6;
|
||||
}
|
||||
|
||||
.clear
|
||||
{
|
||||
top: 4px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.values
|
||||
{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.inputInputElement
|
||||
.inputElement
|
||||
{
|
||||
font-size: inherit;
|
||||
line-height: 200%;
|
||||
|
@ -84,47 +76,66 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.inputInputElement::-ms-clear
|
||||
.inputElement::-ms-clear
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inputBar
|
||||
.multiple .inputElement
|
||||
{
|
||||
display: none;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
height: 29px;
|
||||
}
|
||||
|
||||
.inputInputElement:focus + .inputBar
|
||||
.multiple .inputElement.sizer
|
||||
{
|
||||
content: " ";
|
||||
display: block;
|
||||
border: 1px solid #4196d4;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 0 2px;
|
||||
top: -100px;
|
||||
white-space: pre;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.inputBar:after, .inputBar:before
|
||||
.multiple .values
|
||||
{
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
margin: 0 2em 0 5px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.withclear input
|
||||
.multiple .values .value
|
||||
{
|
||||
text-indent: 0;
|
||||
padding-right: 50px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
background: #e0f0ff;
|
||||
border: 1px solid #c0e0ff;
|
||||
line-height: 16px;
|
||||
padding: 3px;
|
||||
margin: 2px 4px 2px 0;
|
||||
}
|
||||
|
||||
.clear
|
||||
.withClear .inputElement
|
||||
{
|
||||
padding-right: 52px;
|
||||
}
|
||||
|
||||
.clear:after
|
||||
{
|
||||
content: "✕";
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #aaa;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: auto;
|
||||
top: 4px;
|
||||
right: 28px;
|
||||
|
@ -133,3 +144,16 @@
|
|||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clearValue:after
|
||||
{
|
||||
content: "✕";
|
||||
color: #444;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
margin: 0 3px 0 0;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
27
main.js
27
main.js
|
@ -14,6 +14,7 @@ class Test extends React.PureComponent
|
|||
{
|
||||
state = {
|
||||
value: [ 'day' ],
|
||||
value2: 'day',
|
||||
}
|
||||
|
||||
onChange = (v) =>
|
||||
|
@ -21,15 +22,41 @@ class Test extends React.PureComponent
|
|||
this.setState({ value: v });
|
||||
}
|
||||
|
||||
onChange2 = (v) =>
|
||||
{
|
||||
this.setState({ value2: v });
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
return <div style={{padding: '20px', width: '300px', background: '#e0e8ff'}}>
|
||||
<Selectbox
|
||||
source={OPTIONS}
|
||||
allowClear={true}
|
||||
multiple={true}
|
||||
suggestionMatch={true}
|
||||
value={this.state.value}
|
||||
style={{marginBottom: '20px'}}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<Selectbox
|
||||
source={OPTIONS}
|
||||
allowClear={true}
|
||||
multiple={false}
|
||||
suggestionMatch={true}
|
||||
value={this.state.value2}
|
||||
style={{marginBottom: '20px'}}
|
||||
onChange={this.onChange2}
|
||||
/>
|
||||
<Selectbox
|
||||
source={OPTIONS}
|
||||
placeholder="Выберите значение"
|
||||
allowClear={false}
|
||||
multiple={false}
|
||||
suggestionMatch={true}
|
||||
value={this.state.value2}
|
||||
onChange={this.onChange2}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue