From 1e21b07e07b19f7a7675eece848c093a3d937d4c Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 20 Oct 2016 10:25:20 +0100 Subject: [PATCH] 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) --- js/src/modals/Transfer/transfer.css | 4 - .../ui/Form/AddressSelect/addressSelect.css | 10 ++ js/src/ui/Form/AddressSelect/addressSelect.js | 93 ++++++++++++++---- js/src/ui/Form/AutoComplete/autocomplete.js | 95 +++++++++++++++++++ js/src/ui/Form/AutoComplete/index.js | 17 ++++ js/src/ui/Form/InputAddress/inputAddress.css | 4 + js/src/ui/Form/InputAddress/inputAddress.js | 33 ++++++- 7 files changed, 229 insertions(+), 27 deletions(-) create mode 100644 js/src/ui/Form/AutoComplete/autocomplete.js create mode 100644 js/src/ui/Form/AutoComplete/index.js diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index 645f813f5..cd084a368 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -73,10 +73,6 @@ position: relative; } -.address input { - padding-left: 48px !important; -} - .from { padding: 25px 0 0 48px !important; line-height: 32px; diff --git a/js/src/ui/Form/AddressSelect/addressSelect.css b/js/src/ui/Form/AddressSelect/addressSelect.css index a5df06b97..af95c55f0 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.css +++ b/js/src/ui/Form/AddressSelect/addressSelect.css @@ -36,3 +36,13 @@ vertical-align: top; text-transform: uppercase; } + +.icon { + position: absolute; + left: 0; + top: 35px; +} + +.paddedInput input { + padding-left: 46px !important; +} diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 617d02481..9be422571 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -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 ( - +
+ + + { this.renderIdentityIcon() } +
); } - 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 ( + + ); + } + + 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); } } diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js new file mode 100644 index 000000000..c5d405eaa --- /dev/null +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -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 . + +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 ( + + ); + } + + 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: ( + + ) + })); + } + + 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); + } + +} diff --git a/js/src/ui/Form/AutoComplete/index.js b/js/src/ui/Form/AutoComplete/index.js new file mode 100644 index 000000000..509a99444 --- /dev/null +++ b/js/src/ui/Form/AutoComplete/index.js @@ -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 . + +export default from './autocomplete'; diff --git a/js/src/ui/Form/InputAddress/inputAddress.css b/js/src/ui/Form/InputAddress/inputAddress.css index d3ac9678e..2f65476dc 100644 --- a/js/src/ui/Form/InputAddress/inputAddress.css +++ b/js/src/ui/Form/InputAddress/inputAddress.css @@ -22,6 +22,10 @@ padding-left: 48px !important; } +.inputEmpty input { + padding-left: 0 !important; +} + .icon { position: absolute; top: 35px; diff --git a/js/src/ui/Form/InputAddress/inputAddress.js b/js/src/ui/Form/InputAddress/inputAddress.js index 37025c1c8..c3e2bb4ab 100644 --- a/js/src/ui/Form/InputAddress/inputAddress.js +++ b/js/src/ui/Form/InputAddress/inputAddress.js @@ -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 (
{ this.renderIcon() }
@@ -75,6 +93,13 @@ class InputAddress extends Component { ); } + + handleInputChange = (event, value) => { + const isEmpty = (value.length === 0); + + this.setState({ isEmpty }); + this.props.onChange(event, value); + } } function mapStateToProps (state) {