From a6f21563864babedc2636cfb0aba0cb2d7a79d66 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 27 May 2020 12:31:53 -0300 Subject: [PATCH] Update account options menu design (#8654) The `AccountDetailsDropdown` component has been rewritten to use the new `Menu` component, and to follow the latest designs. This should be functionally equivalent. A couple of the icons have changed, but that's about it. Support for a subtitle was added to `MenuItem` to support the `origin` subtitle used for the explorer link for custom RPC endpoints. A few adjustments were required to `test/helper.js` to accommodate the use of `Menu` from a JSDOM context (this is the first time it's been used in a unit test). A `popover-content` element was added to the fake DOM, and another global was added that `react-popper` used internally. An additional driver method (`clickPoint`) was added to the e2e driver to allow clicking the background behind the menu to dismiss it. This wasn't possible using the `clickElement` method, because that method would refuse to click an obscured element. The only non-obscured element to click was the menu backdrop, and that didn't work either because the center was obscured by the menu (Selenium clicks the center of whichever element is targeted). --- app/images/expand.svg | 5 - app/images/hide.svg | 6 - ...es-white.svg => connected-sites-black.svg} | 2 +- app/images/info.svg | 8 - app/images/open-etherscan.svg | 3 - test/e2e/metamask-responsive-ui.spec.js | 7 +- test/e2e/webdriver/driver.js | 9 ++ test/helper.js | 7 + .../account-details-dropdown.component.js | 151 ------------------ .../account-details-dropdown.container.js | 35 ---- .../account-details-dropdown/index.js | 1 - .../account-details-dropdown/index.scss | 6 - ui/app/components/app/index.scss | 2 - .../app/menu-bar/account-options-menu.js | 140 ++++++++++++++++ ui/app/components/app/menu-bar/index.scss | 17 +- ui/app/components/app/menu-bar/menu-bar.js | 16 +- .../app/menu-bar/tests/menu-bar.test.js | 10 +- ui/app/components/ui/menu/menu-item.js | 5 +- ui/app/components/ui/menu/menu.scss | 6 +- .../css/itcss/components/newui-sections.scss | 7 - 20 files changed, 200 insertions(+), 243 deletions(-) delete mode 100644 app/images/expand.svg delete mode 100644 app/images/hide.svg rename app/images/icons/{connected-sites-white.svg => connected-sites-black.svg} (85%) delete mode 100644 app/images/info.svg delete mode 100644 app/images/open-etherscan.svg delete mode 100644 ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.component.js delete mode 100644 ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.container.js delete mode 100644 ui/app/components/app/dropdowns/account-details-dropdown/index.js delete mode 100644 ui/app/components/app/dropdowns/account-details-dropdown/index.scss create mode 100644 ui/app/components/app/menu-bar/account-options-menu.js diff --git a/app/images/expand.svg b/app/images/expand.svg deleted file mode 100644 index 3d670922f..000000000 --- a/app/images/expand.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/images/hide.svg b/app/images/hide.svg deleted file mode 100644 index 3fb2bbf34..000000000 --- a/app/images/hide.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/images/icons/connected-sites-white.svg b/app/images/icons/connected-sites-black.svg similarity index 85% rename from app/images/icons/connected-sites-white.svg rename to app/images/icons/connected-sites-black.svg index 8a4421938..a2411b42b 100644 --- a/app/images/icons/connected-sites-white.svg +++ b/app/images/icons/connected-sites-black.svg @@ -1,3 +1,3 @@ - + diff --git a/app/images/info.svg b/app/images/info.svg deleted file mode 100644 index 6411fa964..000000000 --- a/app/images/info.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/images/open-etherscan.svg b/app/images/open-etherscan.svg deleted file mode 100644 index 17e6b827b..000000000 --- a/app/images/open-etherscan.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index b3fd15a6d..edd10758e 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -117,9 +117,12 @@ describe('MetaMask', function () { describe('Show account information', function () { it('show account details dropdown menu', async function () { - await driver.clickElement(By.css('button.menu-bar__account-options')) - const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) + await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) + const options = await driver.findElements(By.css('.account-options-menu .menu-item')) assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option + // click outside of menu to dismiss + // account menu button chosen because the menu never covers it. + await driver.clickPoint(By.css('.account-menu__icon'), 0, 0) await driver.delay(regularDelayMs) }) }) diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 5fb9ad58a..c5cd917d4 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -71,6 +71,15 @@ class Driver { await element.click() } + async clickPoint (locator, x, y) { + const element = await this.findElement(locator) + await this.driver + .actions() + .move({ origin: element, x, y }) + .click() + .perform() + } + async scrollToElement (element) { await this.driver.executeScript('arguments[0].scrollIntoView(true)', element) } diff --git a/test/helper.js b/test/helper.js index 4f64dd340..b608057bf 100644 --- a/test/helper.js +++ b/test/helper.js @@ -57,6 +57,13 @@ global.document = window.document // required by `react-tippy` global.navigator = window.navigator global.Element = window.Element +// required by `react-popper` +global.HTMLElement = window.HTMLElement + +// required by any components anchored on `popover-content` +const popoverContent = window.document.createElement('div') +popoverContent.setAttribute('id', 'popover-content') +window.document.body.appendChild(popoverContent) // delete AbortController added by jsdom so it can be polyfilled correctly below delete window.AbortController diff --git a/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.component.js b/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.component.js deleted file mode 100644 index d4ea34165..000000000 --- a/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.component.js +++ /dev/null @@ -1,151 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { CONNECTED_ROUTE } from '../../../../helpers/constants/routes' -import { Menu, Item, CloseArea } from '../components/menu' - -export default class AccountDetailsDropdown extends Component { - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - } - - static propTypes = { - selectedIdentity: PropTypes.object.isRequired, - network: PropTypes.string.isRequired, - keyrings: PropTypes.array.isRequired, - showAccountDetailModal: PropTypes.func.isRequired, - viewOnEtherscan: PropTypes.func.isRequired, - showRemoveAccountConfirmationModal: PropTypes.func.isRequired, - rpcPrefs: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - } - - onClose = (e) => { - e.stopPropagation() - this.props.onClose() - } - - render () { - const { - selectedIdentity, - network, - keyrings, - showAccountDetailModal, - viewOnEtherscan, - showRemoveAccountConfirmationModal, - rpcPrefs, - history, - } = this.props - - const address = selectedIdentity.address - - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(address) - }) - - const isRemovable = keyring.type !== 'HD Key Tree' - - return ( - - - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked Expand View', - }, - }) - global.platform.openExtensionInBrowser() - this.props.onClose() - }} - text={this.context.t('expandView')} - icon={( - - )} - /> - { - e.stopPropagation() - showAccountDetailModal() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Viewed Account Details', - }, - }) - this.props.onClose() - }} - text={this.context.t('accountDetails')} - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked View on Etherscan', - }, - }) - viewOnEtherscan(address, network, rpcPrefs) - this.props.onClose() - }} - text={ - rpcPrefs.blockExplorerUrl - ? this.context.t('viewinExplorer') - : this.context.t('viewOnEtherscan') - } - subText={ - rpcPrefs.blockExplorerUrl - ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] - : null - } - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Opened Connected Sites', - }, - }) - history.push(CONNECTED_ROUTE) - this.props.onClose() - }} - text={this.context.t('connectedSites')} - icon={( - - )} - /> - { - isRemovable - ? ( - { - e.stopPropagation() - showRemoveAccountConfirmationModal(selectedIdentity) - this.props.onClose() - }} - text={this.context.t('removeAccount')} - icon={} - /> - ) - : null - } - - ) - } -} diff --git a/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.container.js b/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.container.js deleted file mode 100644 index 8a76ce1e3..000000000 --- a/ui/app/components/app/dropdowns/account-details-dropdown/account-details-dropdown.container.js +++ /dev/null @@ -1,35 +0,0 @@ -import { compose } from 'redux' -import { withRouter } from 'react-router-dom' -import { connect } from 'react-redux' -import AccountDetailsDropdown from './account-details-dropdown.component' -import * as actions from '../../../../store/actions' -import { - getSelectedIdentity, - getRpcPrefsForCurrentProvider, -} from '../../../../selectors' -import genAccountLink from '../../../../../lib/account-link' - -function mapStateToProps (state) { - return { - selectedIdentity: getSelectedIdentity(state), - network: state.metamask.network, - keyrings: state.metamask.keyrings, - rpcPrefs: getRpcPrefsForCurrentProvider(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - showAccountDetailModal: () => { - dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) - }, - viewOnEtherscan: (address, network, rpcPrefs) => { - global.platform.openTab({ url: genAccountLink(address, network, rpcPrefs) }) - }, - showRemoveAccountConfirmationModal: (identity) => { - return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity })) - }, - } -} - -export default compose(withRouter, connect(mapStateToProps, mapDispatchToProps))(AccountDetailsDropdown) diff --git a/ui/app/components/app/dropdowns/account-details-dropdown/index.js b/ui/app/components/app/dropdowns/account-details-dropdown/index.js deleted file mode 100644 index 513831212..000000000 --- a/ui/app/components/app/dropdowns/account-details-dropdown/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './account-details-dropdown.container' diff --git a/ui/app/components/app/dropdowns/account-details-dropdown/index.scss b/ui/app/components/app/dropdowns/account-details-dropdown/index.scss deleted file mode 100644 index ae906e568..000000000 --- a/ui/app/components/app/dropdowns/account-details-dropdown/index.scss +++ /dev/null @@ -1,6 +0,0 @@ -.account-details-dropdown { - position: absolute; - top: 120px; - right: 24px; - z-index: 1; -} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 227e9ce76..aa2d9bc96 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -102,8 +102,6 @@ @import './connected-status-indicator/index'; -@import './dropdowns/account-details-dropdown/index'; - @import '../ui/check-box/index'; @import '../ui/dropdown/dropdown'; diff --git a/ui/app/components/app/menu-bar/account-options-menu.js b/ui/app/components/app/menu-bar/account-options-menu.js new file mode 100644 index 000000000..860204be5 --- /dev/null +++ b/ui/app/components/app/menu-bar/account-options-menu.js @@ -0,0 +1,140 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { useHistory } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' + +import { showModal } from '../../../store/actions' +import { CONNECTED_ROUTE } from '../../../helpers/constants/routes' +import { Menu, MenuItem } from '../../ui/menu' +import genAccountLink from '../../../../lib/account-link' +import { getCurrentKeyring, getCurrentNetwork, getRpcPrefsForCurrentProvider, getSelectedIdentity } from '../../../selectors' +import { useI18nContext } from '../../../hooks/useI18nContext' +import { useMetricEvent } from '../../../hooks/useMetricEvent' + +export default function AccountOptionsMenu ({ anchorElement, onClose }) { + const t = useI18nContext() + const dispatch = useDispatch() + const history = useHistory() + const openFullscreenEvent = useMetricEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked Expand View', + }, + }) + const viewAccountDetailsEvent = useMetricEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Viewed Account Details', + }, + }) + const viewOnEtherscanEvent = useMetricEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked View on Etherscan', + }, + }) + const openConnectedSitesEvent = useMetricEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Opened Connected Sites', + }, + }) + + const keyring = useSelector(getCurrentKeyring) + const network = useSelector(getCurrentNetwork) + const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider) + const selectedIdentity = useSelector(getSelectedIdentity) + + const address = selectedIdentity.address + const isRemovable = keyring.type !== 'HD Key Tree' + + return ( + + { + openFullscreenEvent() + global.platform.openExtensionInBrowser() + onClose() + }} + iconClassName="fas fa-expand-alt" + > + { t('expandView') } + + { + dispatch(showModal({ name: 'ACCOUNT_DETAILS' })) + viewAccountDetailsEvent() + onClose() + }} + iconClassName="fas fa-qrcode" + > + { t('accountDetails') } + + { + viewOnEtherscanEvent() + global.platform.openTab({ url: genAccountLink(address, network, rpcPrefs) }) + onClose() + }} + subtitle={ + rpcPrefs.blockExplorerUrl + ? ( + + { rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] } + + ) + : null + } + iconClassName="fas fa-external-link-alt" + > + { + rpcPrefs.blockExplorerUrl + ? t('viewinExplorer') + : t('viewOnEtherscan') + } + + { + openConnectedSitesEvent() + history.push(CONNECTED_ROUTE) + onClose() + }} + iconClassName="account-options-menu__connected-sites" + > + { t('connectedSites') } + + { + isRemovable + ? ( + { + dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', selectedIdentity })) + onClose() + }} + iconClassName="fas fa-trash-alt" + > + { t('removeAccount') } + + ) + : null + } + + ) +} + +AccountOptionsMenu.propTypes = { + anchorElement: PropTypes.instanceOf(window.Element), + onClose: PropTypes.func.isRequired, +} + +AccountOptionsMenu.defaultProps = { + anchorElement: undefined, +} diff --git a/ui/app/components/app/menu-bar/index.scss b/ui/app/components/app/menu-bar/index.scss index 2bb0497b0..e82c1ef4f 100644 --- a/ui/app/components/app/menu-bar/index.scss +++ b/ui/app/components/app/menu-bar/index.scss @@ -11,8 +11,6 @@ background: none; font-size: inherit; padding: 0 8px 0 5px; - - position: relative; // to allow the dropdown to position itself absolutely place-self: center end; } @@ -21,3 +19,18 @@ place-self: center stretch; } } + +.account-options-menu { + &__connected-sites:before { + content: ""; + background-image: url(/images/icons/connected-sites-black.svg); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + padding: 8px; + } + &__explorer-origin { + color: $Grey-500; + font-size: 12px; + } +} diff --git a/ui/app/components/app/menu-bar/menu-bar.js b/ui/app/components/app/menu-bar/menu-bar.js index 260b355ce..0bf486ae3 100644 --- a/ui/app/components/app/menu-bar/menu-bar.js +++ b/ui/app/components/app/menu-bar/menu-bar.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import SelectedAccount from '../selected-account' import ConnectedStatusIndicator from '../connected-status-indicator' -import AccountDetailsDropdown from '../dropdowns/account-details-dropdown' +import AccountOptionsMenu from './account-options-menu' import { getEnvironmentType } from '../../../../../app/scripts/lib/util' import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums' import { CONNECTED_ACCOUNTS_ROUTE } from '../../../helpers/constants/routes' @@ -19,7 +19,8 @@ export default function MenuBar () { }, }) const history = useHistory() - const [accountDetailsMenuOpen, setAccountDetailsMenuOpen] = useState(false) + const [accountOptionsButtonElement, setAccountOptionsButtonElement] = useState(null) + const [accountOptionsMenuOpen, setAccountOptionsMenuOpen] = useState(false) return (
@@ -33,17 +34,20 @@ export default function MenuBar () { ) @@ -20,12 +21,14 @@ MenuItem.propTypes = { className: PropTypes.string, iconClassName: PropTypes.string, onClick: PropTypes.func, + subtitle: PropTypes.node, } MenuItem.defaultProps = { className: undefined, iconClassName: undefined, onClick: undefined, + subtitle: undefined, } export default MenuItem diff --git a/ui/app/components/ui/menu/menu.scss b/ui/app/components/ui/menu/menu.scss index d552c66b4..5674aee69 100644 --- a/ui/app/components/ui/menu/menu.scss +++ b/ui/app/components/ui/menu/menu.scss @@ -33,8 +33,9 @@ font-family: inherit; font-size: inherit; - display: flex; - flex-direction: row; + display: grid; + grid-template-columns: min-content auto; + text-align: start; align-items: center; width: 100%; padding: 14px 0; @@ -42,6 +43,7 @@ &__icon { margin-right: 8px; + grid-row: 1 / span 2; } .disconnect-icon { diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 53a7e5ee2..fea8d17f2 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -161,13 +161,6 @@ $wallet-view-bg: $alabaster; } } -// account options dropdown -.account-options-menu { - align-items: center; - justify-content: flex-start; - margin: 5% 7% 0%; -} - .fiat-amount { text-transform: uppercase; }