diff --git a/app/images/icons/down-arrow.svg b/app/images/icons/down-arrow.svg
new file mode 100644
index 000000000..6cfb4a38b
--- /dev/null
+++ b/app/images/icons/down-arrow.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/app/app.js b/ui/app/app.js
index 14b199b8e..f320ced0a 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -34,7 +34,7 @@ const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen')
const LoadingNetwork = require('./components/loading-network-screen').default
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
-const AccountMenu = require('./components/account-menu')
+import AccountMenu from './components/account-menu'
// Global Modals
const Modal = require('./components/modals/index').Modal
diff --git a/ui/app/components/account-menu/account-menu.component.js b/ui/app/components/account-menu/account-menu.component.js
new file mode 100644
index 000000000..b2fec647a
--- /dev/null
+++ b/ui/app/components/account-menu/account-menu.component.js
@@ -0,0 +1,301 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import debounce from 'lodash.debounce'
+import { Menu, Item, Divider, CloseArea } from '../dropdowns/components/menu'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../app/scripts/lib/util'
+import Tooltip from '../tooltip'
+import Identicon from '../identicon'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
+import { PRIMARY } from '../../constants/common'
+import {
+ SETTINGS_ROUTE,
+ INFO_ROUTE,
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
+ DEFAULT_ROUTE,
+} from '../../routes'
+
+export default class AccountMenu extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ accounts: PropTypes.object,
+ history: PropTypes.object,
+ identities: PropTypes.object,
+ isAccountMenuOpen: PropTypes.bool,
+ keyrings: PropTypes.array,
+ lockMetamask: PropTypes.func,
+ selectedAddress: PropTypes.string,
+ showAccountDetail: PropTypes.func,
+ showRemoveAccountConfirmationModal: PropTypes.func,
+ toggleAccountMenu: PropTypes.func,
+ }
+
+ state = {
+ atAccountListBottom: false,
+ }
+
+ componentDidUpdate (prevProps) {
+ const { prevIsAccountMenuOpen } = prevProps
+ const { isAccountMenuOpen } = this.props
+
+ if (!prevIsAccountMenuOpen && isAccountMenuOpen) {
+ this.setAtAccountListBottom()
+ }
+ }
+
+ renderAccounts () {
+ const {
+ identities,
+ accounts,
+ selectedAddress,
+ keyrings,
+ showAccountDetail,
+ } = this.props
+
+ const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
+
+ return accountOrder.filter(address => !!identities[address]).map(address => {
+ const identity = identities[address]
+ const isSelected = identity.address === selectedAddress
+
+ const balanceValue = accounts[address] ? accounts[address].balance : ''
+ const simpleAddress = identity.address.substring(2).toLowerCase()
+
+ const keyring = keyrings.find(kr => {
+ return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address)
+ })
+
+ return (
+
showAccountDetail(identity.address)}
+ key={identity.address}
+ >
+
+
+
+
+ { identity.name || '' }
+
+
+
+ { this.renderKeyringType(keyring) }
+ { this.renderRemoveAccount(keyring, identity) }
+
+ )
+ })
+ }
+
+ renderRemoveAccount (keyring, identity) {
+ const { t } = this.context
+ // Any account that's not from the HD wallet Keyring can be removed
+ const { type } = keyring
+ const isRemovable = type !== 'HD Key Tree'
+
+ return isRemovable && (
+
+ this.removeAccount(e, identity)}
+ />
+
+ )
+ }
+
+ removeAccount (e, identity) {
+ e.preventDefault()
+ e.stopPropagation()
+ const { showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal(identity)
+ }
+
+ renderKeyringType (keyring) {
+ const { t } = this.context
+
+ // Sometimes keyrings aren't loaded yet
+ if (!keyring) {
+ return null
+ }
+
+ const { type } = keyring
+ let label
+
+ switch (type) {
+ case 'Trezor Hardware':
+ case 'Ledger Hardware':
+ label = t('hardware')
+ break
+ case 'Simple Key Pair':
+ label = t('imported')
+ break
+ }
+
+ return label && (
+
+ { label }
+
+ )
+ }
+
+ setAtAccountListBottom = () => {
+ const target = document.querySelector('.account-menu__accounts')
+ const { scrollTop, offsetHeight, scrollHeight } = target
+ const atAccountListBottom = scrollTop + offsetHeight >= scrollHeight
+ this.setState({ atAccountListBottom })
+ }
+
+ onScroll = debounce(this.setAtAccountListBottom, 25)
+
+ handleScrollDown = e => {
+ e.stopPropagation()
+ const target = document.querySelector('.account-menu__accounts')
+ const { scrollHeight } = target
+ target.scroll({ left: 0, top: scrollHeight, behavior: 'smooth' })
+ this.setAtAccountListBottom()
+ }
+
+ renderScrollButton () {
+ const { accounts } = this.props
+ const { atAccountListBottom } = this.state
+
+ return !atAccountListBottom && Object.keys(accounts).length > 3 && (
+
+
+
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const {
+ isAccountMenuOpen,
+ toggleAccountMenu,
+ lockMetamask,
+ history,
+ } = this.props
+
+ return (
+
+ )
+ }
+}
diff --git a/ui/app/components/account-menu/account-menu.container.js b/ui/app/components/account-menu/account-menu.container.js
new file mode 100644
index 000000000..93246ec72
--- /dev/null
+++ b/ui/app/components/account-menu/account-menu.container.js
@@ -0,0 +1,62 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { withRouter } from 'react-router-dom'
+import {
+ toggleAccountMenu,
+ showAccountDetail,
+ hideSidebar,
+ lockMetamask,
+ hideWarning,
+ showConfigPage,
+ showInfoPage,
+ showModal,
+} from '../../actions'
+import { getMetaMaskAccounts } from '../../selectors'
+import AccountMenu from './account-menu.component'
+
+function mapStateToProps (state) {
+ const { metamask: { selectedAddress, isAccountMenuOpen, keyrings, identities } } = state
+
+ return {
+ selectedAddress,
+ isAccountMenuOpen,
+ keyrings,
+ identities,
+ accounts: getMetaMaskAccounts(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toggleAccountMenu: () => dispatch(toggleAccountMenu()),
+ showAccountDetail: address => {
+ dispatch(showAccountDetail(address))
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ lockMetamask: () => {
+ dispatch(lockMetamask())
+ dispatch(hideWarning())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showConfigPage: () => {
+ dispatch(showConfigPage())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showInfoPage: () => {
+ dispatch(showInfoPage())
+ dispatch(hideSidebar())
+ dispatch(toggleAccountMenu())
+ },
+ showRemoveAccountConfirmationModal: identity => {
+ return dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(AccountMenu)
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index e88389096..b2b4e4c6f 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -1,249 +1 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const connect = require('react-redux').connect
-const { compose } = require('recompose')
-const { withRouter } = require('react-router-dom')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const actions = require('../../actions')
-const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
-const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
-const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
-const Tooltip = require('../tooltip')
-import Identicon from '../identicon'
-import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
-import { PRIMARY } from '../../constants/common'
-import { getMetaMaskAccounts } from '../../selectors'
-
-const {
- SETTINGS_ROUTE,
- INFO_ROUTE,
- NEW_ACCOUNT_ROUTE,
- IMPORT_ACCOUNT_ROUTE,
- CONNECT_HARDWARE_ROUTE,
- DEFAULT_ROUTE,
-} = require('../../routes')
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(AccountMenu)
-
-AccountMenu.contextTypes = {
- t: PropTypes.func,
-}
-
-inherits(AccountMenu, Component)
-function AccountMenu () { Component.call(this) }
-
-function mapStateToProps (state) {
- return {
- selectedAddress: state.metamask.selectedAddress,
- isAccountMenuOpen: state.metamask.isAccountMenuOpen,
- keyrings: state.metamask.keyrings,
- identities: state.metamask.identities,
- accounts: getMetaMaskAccounts(state),
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
- showAccountDetail: address => {
- dispatch(actions.showAccountDetail(address))
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- lockMetamask: () => {
- dispatch(actions.lockMetamask())
- dispatch(actions.hideWarning())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showConfigPage: () => {
- dispatch(actions.showConfigPage())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showInfoPage: () => {
- dispatch(actions.showInfoPage())
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
- showRemoveAccountConfirmationModal: (identity) => {
- return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
- },
- }
-}
-
-AccountMenu.prototype.render = function () {
- const {
- isAccountMenuOpen,
- toggleAccountMenu,
- lockMetamask,
- history,
- } = this.props
-
- return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
- h(CloseArea, { onClick: toggleAccountMenu }),
- h(Item, {
- className: 'account-menu__header',
- }, [
- this.context.t('myAccounts'),
- h('button.account-menu__logout-button', {
- onClick: () => {
- lockMetamask()
- history.push(DEFAULT_ROUTE)
- },
- }, this.context.t('logout')),
- ]),
- h(Divider),
- h('div.account-menu__accounts', this.renderAccounts()),
- h(Divider),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(NEW_ACCOUNT_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
- text: this.context.t('createAccount'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(IMPORT_ACCOUNT_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
- text: this.context.t('importAccount'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
- } else {
- history.push(CONNECT_HARDWARE_ROUTE)
- }
- },
- icon: h('img.account-menu__item-icon', { src: 'images/connect-icon.svg' }),
- text: this.context.t('connectHardwareWallet'),
- }),
- h(Divider),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(INFO_ROUTE)
- },
- icon: h('img', { src: 'images/mm-info-icon.svg' }),
- text: this.context.t('infoHelp'),
- }),
- h(Item, {
- onClick: () => {
- toggleAccountMenu()
- history.push(SETTINGS_ROUTE)
- },
- icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
- text: this.context.t('settings'),
- }),
- ])
-}
-
-AccountMenu.prototype.renderAccounts = function () {
- const {
- identities,
- accounts,
- selectedAddress,
- keyrings,
- showAccountDetail,
- } = this.props
-
- const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
- return accountOrder.filter(address => !!identities[address]).map((address) => {
-
- const identity = identities[address]
- const isSelected = identity.address === selectedAddress
-
- const balanceValue = accounts[address] ? accounts[address].balance : ''
- const simpleAddress = identity.address.substring(2).toLowerCase()
-
- const keyring = keyrings.find((kr) => {
- return kr.accounts.includes(simpleAddress) ||
- kr.accounts.includes(identity.address)
- })
-
- return h(
- 'div.account-menu__account.menu__item--clickable',
- { onClick: () => showAccountDetail(identity.address) },
- [
- h('div.account-menu__check-mark', [
- isSelected ? h('div.account-menu__check-mark-icon') : null,
- ]),
-
- h(
- Identicon,
- {
- address: identity.address,
- diameter: 24,
- },
- ),
-
- h('div.account-menu__account-info', [
- h('div.account-menu__name', identity.name || ''),
- h(UserPreferencedCurrencyDisplay, {
- className: 'account-menu__balance',
- value: balanceValue,
- type: PRIMARY,
- }),
- ]),
-
- this.renderKeyringType(keyring),
- this.renderRemoveAccount(keyring, identity),
- ],
- )
- })
-}
-
-AccountMenu.prototype.renderRemoveAccount = function (keyring, identity) {
- // Any account that's not from the HD wallet Keyring can be removed
- const type = keyring.type
- const isRemovable = type !== 'HD Key Tree'
- if (isRemovable) {
- return h(Tooltip, {
- title: this.context.t('removeAccount'),
- position: 'bottom',
- }, [
- h('a.remove-account-icon', {
- onClick: (e) => this.removeAccount(e, identity),
- }, ''),
- ])
- }
- return null
-}
-
-AccountMenu.prototype.removeAccount = function (e, identity) {
- e.preventDefault()
- e.stopPropagation()
- const { showRemoveAccountConfirmationModal } = this.props
- showRemoveAccountConfirmationModal(identity)
-}
-
-AccountMenu.prototype.renderKeyringType = function (keyring) {
- try { // Sometimes keyrings aren't loaded yet:
- const type = keyring.type
- let label
- switch (type) {
- case 'Trezor Hardware':
- case 'Ledger Hardware':
- label = this.context.t('hardware')
- break
- case 'Simple Key Pair':
- label = this.context.t('imported')
- break
- default:
- label = ''
- }
-
- return label !== '' ? h('.keyring-label.allcaps', label) : null
-
- } catch (e) { return }
-}
+export { default } from './account-menu.container'
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/components/account-menu/index.scss
similarity index 85%
rename from ui/app/css/itcss/components/account-menu.scss
rename to ui/app/components/account-menu/index.scss
index b14753e23..9a61bf887 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/components/account-menu/index.scss
@@ -55,7 +55,7 @@
display: flex;
flex-flow: column nowrap;
overflow-y: auto;
- max-height: 240px;
+ max-height: 256px;
position: relative;
z-index: 200;
@@ -64,7 +64,7 @@
}
@media screen and (max-width: 575px) {
- max-height: 215px;
+ max-height: 228px;
}
.keyring-label {
@@ -150,4 +150,28 @@
line-height: 18px;
cursor: pointer;
}
+
+ &__accounts-container {
+ position: relative;
+ }
+
+ &__scroll-button {
+ position: absolute;
+ bottom: 12px;
+ right: 12px;
+ height: 28px;
+ width: 28px;
+ border-radius: 14px;
+ background: #3f3f3f;
+ z-index: 201;
+ cursor: pointer;
+ opacity: .8;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
}
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 78c1216f7..f1ecbbc3d 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -1,7 +1,9 @@
-@import './app-header/index';
+@import './account-menu/index';
@import './add-token-button/index';
+@import './app-header/index';
+
@import './button-group/index';
@import './card/index';
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index 63aa62eb3..b11b76f35 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -30,8 +30,6 @@
@import './currency-display.scss';
-@import './account-menu.scss';
-
@import './menu.scss';
@import './gas-slider.scss';