Make address selection searchable (#2739)
* Remove padding on address input if empty (#2141) * Use Autocomplete to make address selection searchable (#2141) * Adds AutoComplete Wrapper (#2141)
This commit is contained in:
parent
9b246245bf
commit
1e21b07e07
@ -73,10 +73,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.address input {
|
|
||||||
padding-left: 48px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.from {
|
.from {
|
||||||
padding: 25px 0 0 48px !important;
|
padding: 25px 0 0 48px !important;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
@ -36,3 +36,13 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddedInput input {
|
||||||
|
padding-left: 46px !important;
|
||||||
|
}
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { MenuItem } from 'material-ui';
|
import { MenuItem } from 'material-ui';
|
||||||
|
|
||||||
|
import AutoComplete from '../AutoComplete';
|
||||||
import IdentityIcon from '../../IdentityIcon';
|
import IdentityIcon from '../../IdentityIcon';
|
||||||
import IdentityName from '../../IdentityName';
|
import IdentityName from '../../IdentityName';
|
||||||
import Select from '../Select';
|
|
||||||
|
|
||||||
import styles from './addressSelect.css';
|
import styles from './addressSelect.css';
|
||||||
|
|
||||||
@ -36,31 +36,66 @@ export default class AddressSelect extends Component {
|
|||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
entries: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
const { accounts, contacts } = this.props;
|
||||||
|
const entries = Object.assign({}, accounts || {}, contacts || {});
|
||||||
|
this.setState({ entries });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (newProps) {
|
||||||
|
const { accounts, contacts } = newProps;
|
||||||
|
const entries = Object.assign({}, accounts || {}, contacts || {});
|
||||||
|
this.setState({ entries });
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { disabled, error, hint, label, value } = this.props;
|
const { disabled, error, hint, label } = this.props;
|
||||||
|
const { entries } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<div>
|
||||||
disabled={ disabled }
|
<AutoComplete
|
||||||
label={ label }
|
className={ error ? '' : styles.paddedInput }
|
||||||
hint={ hint }
|
disabled={ disabled }
|
||||||
error={ error }
|
label={ label }
|
||||||
value={ value }
|
hint={ `search for ${hint}` }
|
||||||
onChange={ this.onChange }>
|
error={ error }
|
||||||
{ this.renderSelectEntries() }
|
onChange={ this.onChange }
|
||||||
</Select>
|
value={ this.getSearchText() }
|
||||||
|
filter={ this.handleFilter }
|
||||||
|
entries={ entries }
|
||||||
|
renderItem={ this.renderItem }
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ this.renderIdentityIcon() }
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelectEntries () {
|
renderIdentityIcon () {
|
||||||
const { accounts, contacts } = this.props;
|
if (this.props.error) {
|
||||||
const entries = Object.values(Object.assign({}, accounts || {}, contacts || {}));
|
|
||||||
|
|
||||||
if (!entries.length) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries.map(this.renderSelectEntry);
|
const { value } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IdentityIcon
|
||||||
|
className={ styles.icon }
|
||||||
|
inline center
|
||||||
|
address={ value } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem = (entry) => {
|
||||||
|
return {
|
||||||
|
text: entry.address,
|
||||||
|
value: this.renderSelectEntry(entry)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelectEntry = (entry) => {
|
renderSelectEntry = (entry) => {
|
||||||
@ -89,7 +124,27 @@ export default class AddressSelect extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (event, idx, value) => {
|
getSearchText () {
|
||||||
this.props.onChange(event, value);
|
const { value } = this.props;
|
||||||
|
if (!value) return '';
|
||||||
|
|
||||||
|
const { entries } = this.state;
|
||||||
|
const entry = entries[value];
|
||||||
|
if (!entry) return '';
|
||||||
|
|
||||||
|
return entry.name ? entry.name.toUpperCase() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFilter = (searchText, address) => {
|
||||||
|
const entry = this.state.entries[address];
|
||||||
|
const lowCaseSearch = searchText.toLowerCase();
|
||||||
|
|
||||||
|
return [ entry.name, entry.address ]
|
||||||
|
.some(text => text.toLowerCase().indexOf(lowCaseSearch) !== -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (entry) => {
|
||||||
|
const address = entry ? entry.address : '';
|
||||||
|
this.props.onChange(null, address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
95
js/src/ui/Form/AutoComplete/autocomplete.js
Normal file
95
js/src/ui/Form/AutoComplete/autocomplete.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui';
|
||||||
|
|
||||||
|
export default class AutoComplete extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
label: PropTypes.string,
|
||||||
|
hint: PropTypes.string,
|
||||||
|
error: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
filter: PropTypes.func,
|
||||||
|
renderItem: PropTypes.func,
|
||||||
|
entries: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { disabled, error, hint, label, value, className, filter } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MUIAutoComplete
|
||||||
|
className={ className }
|
||||||
|
disabled={ disabled }
|
||||||
|
floatingLabelText={ label }
|
||||||
|
hintText={ hint }
|
||||||
|
errorText={ error }
|
||||||
|
onNewRequest={ this.onChange }
|
||||||
|
searchText={ value }
|
||||||
|
onFocus={ this.onFocus }
|
||||||
|
|
||||||
|
filter={ filter }
|
||||||
|
openOnFocus
|
||||||
|
fullWidth
|
||||||
|
floatingLabelFixed
|
||||||
|
dataSource={ this.getDataSource() }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataSource () {
|
||||||
|
const { renderItem, entries } = this.props;
|
||||||
|
const entriesArray = (entries instanceof Array)
|
||||||
|
? entries
|
||||||
|
: Object.values(entries);
|
||||||
|
|
||||||
|
if (renderItem && typeof renderItem === 'function') {
|
||||||
|
return entriesArray.map(entry => renderItem(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
return entriesArray.map(entry => ({
|
||||||
|
text: entry,
|
||||||
|
value: (
|
||||||
|
<MenuItem
|
||||||
|
primaryText={ entry }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (item, idx) => {
|
||||||
|
const { onChange, entries } = this.props;
|
||||||
|
const entriesArray = (entries instanceof Array)
|
||||||
|
? entries
|
||||||
|
: Object.values(entries);
|
||||||
|
|
||||||
|
const entry = (idx === -1) ? null : entriesArray[idx];
|
||||||
|
|
||||||
|
onChange(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus = () => {
|
||||||
|
this.props.onChange(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
js/src/ui/Form/AutoComplete/index.js
Normal file
17
js/src/ui/Form/AutoComplete/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './autocomplete';
|
@ -22,6 +22,10 @@
|
|||||||
padding-left: 48px !important;
|
padding-left: 48px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputEmpty input {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 35px;
|
top: 35px;
|
||||||
|
@ -38,22 +38,40 @@ class InputAddress extends Component {
|
|||||||
onSubmit: PropTypes.func
|
onSubmit: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isEmpty: false
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
const { value, text, accountsInfo, tokens } = this.props;
|
||||||
|
const account = accountsInfo[value] || tokens[value];
|
||||||
|
const hasAccount = account && !account.meta.deleted;
|
||||||
|
const inputValue = text && hasAccount ? account.name : value;
|
||||||
|
const isEmpty = (inputValue.length === 0);
|
||||||
|
|
||||||
|
this.setState({ isEmpty });
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, disabled, error, label, hint, value, text, onChange, onSubmit, accountsInfo, tokens } = this.props;
|
const { className, disabled, error, label, hint, value, text, onSubmit, accountsInfo, tokens } = this.props;
|
||||||
const classes = `${styles.input} ${className}`;
|
const { isEmpty } = this.state;
|
||||||
|
|
||||||
|
const classes = [ className ];
|
||||||
|
classes.push(isEmpty ? styles.inputEmpty : styles.input);
|
||||||
|
|
||||||
const account = accountsInfo[value] || tokens[value];
|
const account = accountsInfo[value] || tokens[value];
|
||||||
const hasAccount = account && !account.meta.deleted;
|
const hasAccount = account && !account.meta.deleted;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<Input
|
<Input
|
||||||
className={ classes }
|
className={ classes.join(' ') }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
error={ error }
|
error={ error }
|
||||||
value={ text && hasAccount ? account.name : value }
|
value={ text && hasAccount ? account.name : value }
|
||||||
onChange={ onChange }
|
onChange={ this.handleInputChange }
|
||||||
onSubmit={ onSubmit } />
|
onSubmit={ onSubmit } />
|
||||||
{ this.renderIcon() }
|
{ this.renderIcon() }
|
||||||
</div>
|
</div>
|
||||||
@ -75,6 +93,13 @@ class InputAddress extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleInputChange = (event, value) => {
|
||||||
|
const isEmpty = (value.length === 0);
|
||||||
|
|
||||||
|
this.setState({ isEmpty });
|
||||||
|
this.props.onChange(event, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
Loading…
Reference in New Issue
Block a user