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;
|
||||
}
|
||||
|
||||
.address input {
|
||||
padding-left: 48px !important;
|
||||
}
|
||||
|
||||
.from {
|
||||
padding: 25px 0 0 48px !important;
|
||||
line-height: 32px;
|
||||
|
@ -36,3 +36,13 @@
|
||||
vertical-align: top;
|
||||
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 { MenuItem } from 'material-ui';
|
||||
|
||||
import AutoComplete from '../AutoComplete';
|
||||
import IdentityIcon from '../../IdentityIcon';
|
||||
import IdentityName from '../../IdentityName';
|
||||
import Select from '../Select';
|
||||
|
||||
import styles from './addressSelect.css';
|
||||
|
||||
@ -36,31 +36,66 @@ export default class AddressSelect extends Component {
|
||||
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 () {
|
||||
const { disabled, error, hint, label, value } = this.props;
|
||||
const { disabled, error, hint, label } = this.props;
|
||||
const { entries } = this.state;
|
||||
|
||||
return (
|
||||
<Select
|
||||
<div>
|
||||
<AutoComplete
|
||||
className={ error ? '' : styles.paddedInput }
|
||||
disabled={ disabled }
|
||||
label={ label }
|
||||
hint={ hint }
|
||||
hint={ `search for ${hint}` }
|
||||
error={ error }
|
||||
value={ value }
|
||||
onChange={ this.onChange }>
|
||||
{ this.renderSelectEntries() }
|
||||
</Select>
|
||||
onChange={ this.onChange }
|
||||
value={ this.getSearchText() }
|
||||
filter={ this.handleFilter }
|
||||
entries={ entries }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
|
||||
{ this.renderIdentityIcon() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSelectEntries () {
|
||||
const { accounts, contacts } = this.props;
|
||||
const entries = Object.values(Object.assign({}, accounts || {}, contacts || {}));
|
||||
|
||||
if (!entries.length) {
|
||||
renderIdentityIcon () {
|
||||
if (this.props.error) {
|
||||
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) => {
|
||||
@ -89,7 +124,27 @@ export default class AddressSelect extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (event, idx, value) => {
|
||||
this.props.onChange(event, value);
|
||||
getSearchText () {
|
||||
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;
|
||||
}
|
||||
|
||||
.inputEmpty input {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
|
@ -38,22 +38,40 @@ class InputAddress extends Component {
|
||||
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 () {
|
||||
const { className, disabled, error, label, hint, value, text, onChange, onSubmit, accountsInfo, tokens } = this.props;
|
||||
const classes = `${styles.input} ${className}`;
|
||||
const { className, disabled, error, label, hint, value, text, onSubmit, accountsInfo, tokens } = this.props;
|
||||
const { isEmpty } = this.state;
|
||||
|
||||
const classes = [ className ];
|
||||
classes.push(isEmpty ? styles.inputEmpty : styles.input);
|
||||
|
||||
const account = accountsInfo[value] || tokens[value];
|
||||
const hasAccount = account && !account.meta.deleted;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<Input
|
||||
className={ classes }
|
||||
className={ classes.join(' ') }
|
||||
disabled={ disabled }
|
||||
label={ label }
|
||||
hint={ hint }
|
||||
error={ error }
|
||||
value={ text && hasAccount ? account.name : value }
|
||||
onChange={ onChange }
|
||||
onChange={ this.handleInputChange }
|
||||
onSubmit={ onSubmit } />
|
||||
{ this.renderIcon() }
|
||||
</div>
|
||||
@ -75,6 +93,13 @@ class InputAddress extends Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleInputChange = (event, value) => {
|
||||
const isEmpty = (value.length === 0);
|
||||
|
||||
this.setState({ isEmpty });
|
||||
this.props.onChange(event, value);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
Loading…
Reference in New Issue
Block a user