+ {shouldShowAccountsSearch ? this.renderAccountsSearch() : null}
{
+ this.accountsRef = ref
+ }}
>
{ this.renderAccounts() }
diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js
index 00a0666ec..2823a474d 100644
--- a/ui/app/components/app/account-menu/account-menu.container.js
+++ b/ui/app/components/app/account-menu/account-menu.container.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux'
-import { compose } from 'recompose'
+import { compose, withProps } from 'recompose'
import { withRouter } from 'react-router-dom'
import {
toggleAccountMenu,
@@ -13,23 +13,28 @@ import {
} from '../../../store/actions'
import {
getAddressConnectedDomainMap,
- getMetaMaskAccounts,
+ getMetaMaskAccountsOrdered,
+ getMetaMaskKeyrings,
getOriginOfCurrentTab,
+ getSelectedAddress,
} from '../../../selectors/selectors'
-
import AccountMenu from './account-menu.component'
+/**
+ * The min amount of accounts to show search field
+ */
+const SHOW_SEARCH_ACCOUNTS_MIN_COUNT = 5
+
function mapStateToProps (state) {
- const { metamask: { selectedAddress, isAccountMenuOpen, keyrings, identities } } = state
+ const { metamask: { isAccountMenuOpen } } = state
return {
- selectedAddress,
isAccountMenuOpen,
- keyrings,
- identities,
- accounts: getMetaMaskAccounts(state),
addressConnectedDomainMap: getAddressConnectedDomainMap(state),
originOfCurrentTab: getOriginOfCurrentTab(state),
+ selectedAddress: getSelectedAddress(state),
+ keyrings: getMetaMaskKeyrings(state),
+ accounts: getMetaMaskAccountsOrdered(state),
}
}
@@ -65,5 +70,6 @@ function mapDispatchToProps (dispatch) {
export default compose(
withRouter,
- connect(mapStateToProps, mapDispatchToProps)
+ connect(mapStateToProps, mapDispatchToProps),
+ withProps(({ accounts }) => ({ shouldShowAccountsSearch: accounts.length >= SHOW_SEARCH_ACCOUNTS_MIN_COUNT}))
)(AccountMenu)
diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss
index 93b9766d3..c8385b5e7 100644
--- a/ui/app/components/app/account-menu/index.scss
+++ b/ui/app/components/app/account-menu/index.scss
@@ -51,22 +51,26 @@
height: 16px;
}
- &__accounts {
+ &__accounts-container {
display: flex;
- flex-flow: column nowrap;
- overflow-y: auto;
- max-height: 256px;
- position: relative;
+ flex-direction: column;
z-index: 200;
scrollbar-width: none;
- &::-webkit-scrollbar {
- display: none;
- }
+ max-height: 256px;
@media screen and (max-width: 575px) {
max-height: 228px;
}
+ }
+
+ &__accounts {
+ overflow-y: auto;
+ position: relative;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
.keyring-label {
margin-top: 5px;
@@ -77,6 +81,11 @@
}
}
+ &__no-accounts {
+ font-size: 0.8em;
+ padding: 16px 14px;
+ }
+
&__account {
display: flex;
flex-flow: row nowrap;
diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
index 82f377358..689a60208 100644
--- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
+++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.container.js
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import TransactionBreakdown from './transaction-breakdown.component'
-import {getIsMainnet, getNativeCurrency, preferencesSelector} from '../../../selectors/selectors'
+import { getIsMainnet, getNativeCurrency, preferencesSelector } from '../../../selectors/selectors'
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util'
import { sumHexes } from '../../../helpers/utils/transactions.util'
diff --git a/ui/app/components/ui/search-icon/index.js b/ui/app/components/ui/search-icon/index.js
new file mode 100644
index 000000000..f6078b7be
--- /dev/null
+++ b/ui/app/components/ui/search-icon/index.js
@@ -0,0 +1 @@
+export { default } from './search-icon.component'
diff --git a/ui/app/components/ui/search-icon/search-icon.component.js b/ui/app/components/ui/search-icon/search-icon.component.js
new file mode 100644
index 000000000..b793518a4
--- /dev/null
+++ b/ui/app/components/ui/search-icon/search-icon.component.js
@@ -0,0 +1,12 @@
+import React from 'react'
+
+export default function SearchIcon () {
+ return (
+
+ )
+}
diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js
index 12a97ee4d..557e547c5 100644
--- a/ui/app/components/ui/text-field/text-field.component.js
+++ b/ui/app/components/ui/text-field/text-field.component.js
@@ -28,6 +28,24 @@ const styles = {
},
},
materialError: {},
+ materialWhitePaddedRoot: {
+ color: '#aeaeae',
+ },
+ materialWhitePaddedInput: {
+ padding: '8px',
+
+ '&::placeholder': {
+ color: '#aeaeae',
+ },
+ },
+ materialWhitePaddedFocused: {
+ color: '#fff',
+ },
+ materialWhitePaddedUnderline: {
+ '&:after': {
+ borderBottom: '2px solid #fff',
+ },
+ },
// Non-material styles
formLabel: {
'&$formLabelFocused': {
@@ -66,35 +84,99 @@ const styles = {
},
}
-const TextField = props => {
- const { error, classes, material, startAdornment, largeLabel, dir, ...textFieldProps } = props
+const getMaterialThemeInputProps = ({
+ dir,
+ classes: { materialLabel, materialFocused, materialError, materialUnderline },
+ startAdornment,
+}) => ({
+ InputLabelProps: {
+ FormLabelClasses: {
+ root: materialLabel,
+ focused: materialFocused,
+ error: materialError,
+ },
+ },
+ InputProps: {
+ startAdornment,
+ classes: {
+ underline: materialUnderline,
+ },
+ inputProps: {
+ dir,
+ },
+ },
+})
+
+const getMaterialWhitePaddedThemeInputProps = ({
+ dir,
+ classes: { materialWhitePaddedRoot, materialWhitePaddedFocused, materialWhitePaddedInput, materialWhitePaddedUnderline },
+ startAdornment,
+}) => ({
+ InputProps: {
+ startAdornment,
+ classes: {
+ root: materialWhitePaddedRoot,
+ focused: materialWhitePaddedFocused,
+ input: materialWhitePaddedInput,
+ underline: materialWhitePaddedUnderline,
+ },
+ inputProps: {
+ dir,
+ },
+ },
+})
+
+const getBorderedThemeInputProps = ({
+ dir,
+ classes: { formLabel, formLabelFocused, materialError, largeInputLabel, inputLabel, inputRoot, input, inputFocused },
+ largeLabel,
+ startAdornment,
+}) => ({
+ InputLabelProps: {
+ shrink: true,
+ className: largeLabel ? largeInputLabel : inputLabel,
+ FormLabelClasses: {
+ root: formLabel,
+ focused: formLabelFocused,
+ error: materialError,
+ },
+ },
+ InputProps: {
+ startAdornment,
+ disableUnderline: true,
+ classes: {
+ root: inputRoot,
+ input: input,
+ focused: inputFocused,
+ },
+ inputProps: {
+ dir,
+ },
+ },
+})
+
+const themeToInputProps = {
+ 'material': getMaterialThemeInputProps,
+ 'bordered': getBorderedThemeInputProps,
+ 'material-white-padded': getMaterialWhitePaddedThemeInputProps,
+}
+
+const TextField = ({
+ error,
+ classes,
+ theme,
+ startAdornment,
+ largeLabel,
+ dir,
+ ...textFieldProps
+}) => {
+ const inputProps = themeToInputProps[theme]({ classes, startAdornment, largeLabel, dir })
return (
)
@@ -103,13 +185,14 @@ const TextField = props => {
TextField.defaultProps = {
error: null,
dir: 'auto',
+ theme: 'bordered',
}
TextField.propTypes = {
error: PropTypes.string,
classes: PropTypes.object,
dir: PropTypes.string,
- material: PropTypes.bool,
+ theme: PropTypes.oneOf(['bordered', 'material', 'material-white-padded']),
startAdornment: PropTypes.element,
largeLabel: PropTypes.bool,
}
diff --git a/ui/app/components/ui/text-field/text-field.stories.js b/ui/app/components/ui/text-field/text-field.stories.js
index d68f24362..9ef76d1d3 100644
--- a/ui/app/components/ui/text-field/text-field.stories.js
+++ b/ui/app/components/ui/text-field/text-field.stories.js
@@ -33,14 +33,14 @@ storiesOf('TextField', module)
))
.add('Material password', () => (
))
.add('Material error', () => (
@@ -48,6 +48,6 @@ storiesOf('TextField', module)
type="text"
label="Name"
error="Invalid value"
- material
+ theme="material"
/>
))
diff --git a/ui/app/pages/add-token/token-search/token-search.component.js b/ui/app/pages/add-token/token-search/token-search.component.js
index 5542a19ff..eee841654 100644
--- a/ui/app/pages/add-token/token-search/token-search.component.js
+++ b/ui/app/pages/add-token/token-search/token-search.component.js
@@ -36,12 +36,8 @@ export default class TokenSearch extends Component {
error: PropTypes.string,
}
- constructor (props) {
- super(props)
-
- this.state = {
- searchQuery: '',
- }
+ state = {
+ searchQuery: '',
}
handleSearch (searchQuery) {
diff --git a/ui/app/pages/unlock-page/unlock-page.component.js b/ui/app/pages/unlock-page/unlock-page.component.js
index 3aeb2a59b..8620b53f9 100644
--- a/ui/app/pages/unlock-page/unlock-page.component.js
+++ b/ui/app/pages/unlock-page/unlock-page.component.js
@@ -165,7 +165,7 @@ export default class UnlockPage extends Component {
error={error}
autoFocus
autoComplete="current-password"
- material
+ theme="material"
fullWidth
/>
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index bcd21d107..3077421ac 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -1,6 +1,7 @@
import { NETWORK_TYPES } from '../helpers/constants/common'
import { mapObjectValues } from '../../../app/scripts/lib/util'
import { stripHexPrefix, addHexPrefix } from 'ethereumjs-util'
+import { createSelector } from 'reselect'
import abi from 'human-standard-token-abi'
import { multiplyCurrencies } from '../helpers/utils/conversion-util'
@@ -58,6 +59,28 @@ export function getCurrentNetworkId (state) {
return state.metamask.network
}
+export const getMetaMaskAccounts = createSelector(
+ getMetaMaskAccountsRaw,
+ getMetaMaskCachedBalances,
+ (currentAccounts, cachedBalances) => Object.entries(currentAccounts).reduce((selectedAccounts, [accountID, account]) => {
+ if (account.balance === null || account.balance === undefined) {
+ return {
+ ...selectedAccounts,
+ [accountID]: {
+ ...account,
+ balance: cachedBalances && cachedBalances[accountID],
+ },
+
+ }
+ } else {
+ return {
+ ...selectedAccounts,
+ [accountID]: account,
+ }
+ }
+ }, {})
+)
+
export function getSelectedAddress (state) {
const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]
@@ -80,25 +103,37 @@ export function getNumberOfTokens (state) {
return tokens ? tokens.length : 0
}
-export function getMetaMaskAccounts (state) {
- const currentAccounts = state.metamask.accounts
- const cachedBalances = state.metamask.cachedBalances[state.metamask.network]
- const selectedAccounts = {}
-
- Object.keys(currentAccounts).forEach(accountID => {
- const account = currentAccounts[accountID]
- if (account && account.balance === null || account.balance === undefined) {
- selectedAccounts[accountID] = {
- ...account,
- balance: cachedBalances && cachedBalances[accountID],
- }
- } else {
- selectedAccounts[accountID] = account
- }
- })
- return selectedAccounts
+export function getMetaMaskKeyrings (state) {
+ return state.metamask.keyrings
}
+export function getMetaMaskIdentities (state) {
+ return state.metamask.identities
+}
+
+export function getMetaMaskAccountsRaw (state) {
+ return state.metamask.accounts
+}
+
+export function getMetaMaskCachedBalances (state) {
+ const network = getCurrentNetworkId(state)
+
+ return state.metamask.cachedBalances[network]
+}
+
+/**
+ * Get ordered (by keyrings) accounts with identity and balance
+ */
+export const getMetaMaskAccountsOrdered = createSelector(
+ getMetaMaskKeyrings,
+ getMetaMaskIdentities,
+ getMetaMaskAccounts,
+ (keyrings, identities, accounts) => keyrings
+ .reduce((list, keyring) => list.concat(keyring.accounts), [])
+ .filter(address => !!identities[address])
+ .map(address => ({ ...identities[address], ...accounts[address]}))
+)
+
export function isBalanceCached (state) {
const selectedAccountBalance = state.metamask.accounts[getSelectedAddress(state)].balance
const cachedBalance = getSelectedAccountCachedBalance(state)
diff --git a/ui/app/selectors/tests/selectors.test.js b/ui/app/selectors/tests/selectors.test.js
index 813628968..16170bc6b 100644
--- a/ui/app/selectors/tests/selectors.test.js
+++ b/ui/app/selectors/tests/selectors.test.js
@@ -1,8 +1,5 @@
import assert from 'assert'
-import * as selectors from '../selectors'
-const {
- getAddressBook,
-} = selectors
+import { getAddressBook } from '../selectors.js'
import mockState from './selectors-test-data'
describe('selectors', () => {