1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Refactor asset list items (#8586)

All asset list items now use the same component (`AssetListItem`).
Previously the tokens and the Ethereum balance were totally separate
components, despite being styled similarly.

Various unnecessary DOM elements and style rules were removed, but the
overall list looks identical to how it looked before.
This commit is contained in:
Mark Stacey 2020-05-13 17:41:15 -03:00 committed by GitHub
parent f64106ce21
commit 0ca5d1dc8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 186 additions and 240 deletions

View File

@ -151,7 +151,7 @@ describe('MetaMask', function () {
})
it('balance renders', async function () {
const balance = await driver.findElement(By.css('.balance-display .token-amount'))
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
await driver.delay(regularDelayMs)
})

View File

@ -206,7 +206,7 @@ describe('MetaMask', function () {
})
it('balance renders', async function () {
const balance = await driver.findElement(By.css('.balance-display .token-amount'))
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
await driver.delay(regularDelayMs)
})
@ -991,7 +991,7 @@ describe('MetaMask', function () {
const txStatuses = await driver.findElements(By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/), 10000)
await driver.clickElement(By.css('.wallet-balance'))
await driver.clickElement(By.css('[data-testid="wallet-balance"]'))
await driver.clickElement(By.css('.token-cell'))
await driver.delay(1000)

View File

@ -96,7 +96,7 @@ describe('MetaMask', function () {
})
it('balance renders', async function () {
const balance = await driver.findElement(By.css('.balance-display .token-amount'))
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
await driver.delay(regularDelayMs)
})
@ -202,7 +202,7 @@ describe('MetaMask', function () {
})
it('balance renders', async function () {
const balance = await driver2.findElement(By.css('.balance-display .token-amount'))
const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/))
await driver2.delay(regularDelayMs)
})

View File

@ -0,0 +1,67 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Identicon from '../../ui/identicon'
const AssetListItem = ({
active,
children,
className,
'data-testid': dataTestId,
iconClassName,
menu,
onClick,
tokenAddress,
tokenImage,
warning,
}) => {
return (
<div
className={classnames('asset-list-item__container', className, {
'asset-list-item__container--active': active,
})}
data-testid={dataTestId}
onClick={onClick}
>
<Identicon
className={iconClassName}
diameter={50}
address={tokenAddress}
image={tokenImage}
/>
<div
className="asset-list-item__balance"
>
{ children }
</div>
{ warning }
{ menu }
</div>
)
}
AssetListItem.propTypes = {
active: PropTypes.bool,
children: PropTypes.node.isRequired,
className: PropTypes.string,
'data-testid': PropTypes.string,
iconClassName: PropTypes.string,
menu: PropTypes.node,
onClick: PropTypes.func.isRequired,
tokenAddress: PropTypes.string,
tokenImage: PropTypes.string,
warning: PropTypes.node,
}
AssetListItem.defaultProps = {
active: undefined,
className: undefined,
'data-testid': undefined,
menu: undefined,
iconClassName: undefined,
tokenAddress: undefined,
tokenImage: undefined,
warning: undefined,
}
export default AssetListItem

View File

@ -0,0 +1,20 @@
.asset-list-item {
&__container {
display: flex;
padding: 20px 25px;
align-items: center;
&--active {
background: $manatee;
color: $white;
}
}
&__balance {
display: flex;
flex-direction: column;
margin-left: 15px;
flex: 1;
min-width: 0;
}
}

View File

@ -0,0 +1 @@
export { default } from './asset-list-item'

View File

@ -1,10 +1,11 @@
import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import BalanceComponent from '../../ui/balance'
import AddTokenButton from '../add-token-button'
import TokenList from '../token-list'
import { ADD_TOKEN_ROUTE } from '../../../helpers/constants/routes'
import AssetListItem from '../asset-list-item'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
export default class AssetList extends Component {
static contextTypes = {
@ -18,32 +19,44 @@ export default class AssetList extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
selectedAccountBalance: PropTypes.string,
selectedTokenAddress: PropTypes.string,
setSelectedToken: PropTypes.func.isRequired,
showFiat: PropTypes.bool.isRequired,
unsetSelectedToken: PropTypes.func.isRequired,
}
renderWalletBalance () {
const {
selectedAccountBalance,
selectedTokenAddress,
showFiat,
unsetSelectedToken,
} = this.props
return (
<div
className={classnames('flex-column', 'wallet-balance-wrapper', {
'wallet-balance-wrapper--active': !selectedTokenAddress,
})}
<AssetListItem
active={!selectedTokenAddress}
onClick={unsetSelectedToken}
data-testid="wallet-balance"
>
<div
className="wallet-balance"
onClick={() => {
unsetSelectedToken()
}}
>
<BalanceComponent />
</div>
</div>
<UserPreferencedCurrencyDisplay
className="asset-list__primary-amount"
ethNumberOfDecimals={4}
type={PRIMARY}
value={selectedAccountBalance}
/>
{
showFiat && (
<UserPreferencedCurrencyDisplay
className="asset-list__secondary-amount"
ethNumberOfDecimals={4}
type={SECONDARY}
value={selectedAccountBalance}
/>
)
}
</AssetListItem>
)
}

View File

@ -3,10 +3,13 @@ import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import AssetList from './asset-list.component'
import { setSelectedToken } from '../../../store/actions'
import { getCurrentAccountWithSendEtherInfo, getShouldShowFiat } from '../../../selectors/selectors'
function mapStateToProps (state) {
return {
selectedAccountBalance: getCurrentAccountWithSendEtherInfo(state).balance,
selectedTokenAddress: state.metamask.selectedTokenAddress,
showFiat: getShouldShowFiat(state),
}
}

View File

@ -1,64 +1,10 @@
$wallet-balance-bg: #e7e7e7;
$wallet-balance-breakpoint: 890px;
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
.asset-list {
&__primary-amount {
font-size: 1.5rem;
}
.wallet-balance-wrapper {
flex: 0 0 auto;
transition: linear 200ms;
background: rgba($wallet-balance-bg, 0);
&--active {
background: $manatee;
color: $white;
}
}
.wallet-balance {
background: inherit;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
flex: 0 0 auto;
cursor: pointer;
border-top: 1px solid $wallet-balance-bg;
.balance-container {
display: flex;
justify-content: flex-start;
align-items: center;
margin: 20px 24px;
flex-direction: row;
min-width: 0;
@media #{$wallet-balance-breakpoint-range} {
margin: 10% 4%;
}
}
.balance-display {
margin-left: 15px;
min-width: 0;
.token-amount {
font-size: 1.5rem;
}
.fiat-amount {
margin-top: .25%;
font-size: 105%;
}
@media #{$wallet-balance-breakpoint-range} {
margin-left: 4%;
.token-amount {
font-size: 105%;
}
.fiat-amount {
font-size: 95%;
}
}
&__secondary-amount {
margin-top: .25%;
font-size: 105%;
}
}

View File

@ -10,6 +10,8 @@
@import 'asset-list/asset-list';
@import 'asset-list-item/asset-list-item';
@import '../ui/breadcrumbs/index';
@import '../ui/button-group/index';

View File

@ -1,11 +1,11 @@
import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import Identicon from '../../ui/identicon'
import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util'
import TokenMenuDropdown from '../dropdowns/token-menu-dropdown.js'
import Tooltip from '../../ui/tooltip-v2'
import { I18nContext } from '../../../contexts/i18n'
import AssetListItem from '../asset-list-item'
export default class TokenCell extends Component {
static contextType = I18nContext
@ -71,55 +71,8 @@ export default class TokenCell extends Component {
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
return (
<div
className={classnames('token-cell', {
'token-cell--active': selectedTokenAddress === address,
'token-cell--outdated': outdatedBalance,
})}
onClick={onClick.bind(null, address)}
>
<Identicon
className="token-cell__identicon"
diameter={50}
address={address}
image={image}
/>
<div className="token-cell__balance-ellipsis">
<div className="token-cell__balance-wrapper">
<div className="token-cell__token-balance">{string || 0}</div>
<div className="token-cell__token-symbol">{symbol}</div>
{showFiat && (
<div className="token-cell__fiat-amount">
{formattedFiat}
</div>
)}
</div>
</div>
{
outdatedBalance && (
<Tooltip
interactive
position="bottom"
html={(
<div className="token-cell__outdated-tooltip">
{ t('troubleTokenBalances') }
<a
href={`https://ethplorer.io/address/${userAddress}`}
rel="noopener noreferrer"
target="_blank"
style={{ color: '#F7861C' }}
>
{ t('here') }
</a>
</div>
)}
>
<i className={classnames(['fa', 'fa-exclamation-circle', 'token-cell__outdated-icon'])} />
</Tooltip>
)
}
const menu = (
<>
<div>
<i
className="fa fa-ellipsis-h fa-lg token-cell__ellipsis cursor-pointer"
@ -135,7 +88,54 @@ export default class TokenCell extends Component {
token={{ symbol, address }}
/>
)}
</div>
</>
)
const warning = outdatedBalance
? (
<Tooltip
interactive
position="bottom"
html={(
<div className="token-cell__outdated-tooltip">
{ t('troubleTokenBalances') }
<a
href={`https://ethplorer.io/address/${userAddress}`}
rel="noopener noreferrer"
target="_blank"
style={{ color: '#F7861C' }}
>
{ t('here') }
</a>
</div>
)}
>
<i className={classnames(['fa', 'fa-exclamation-circle', 'token-cell__outdated-icon'])} />
</Tooltip>
)
: null
return (
<AssetListItem
active={selectedTokenAddress === address}
className={classnames('token-cell', { 'token-cell--outdated': outdatedBalance })}
iconClassName="token-cell__icon"
menu={menu}
onClick={onClick.bind(null, address)}
tokenAddress={address}
tokenImage={image}
warning={warning}
>
<div className="token-cell__balance-wrapper">
<div className="token-cell__token-balance">{string || 0}</div>
<div className="token-cell__token-symbol">{symbol}</div>
{showFiat && (
<div className="token-cell__fiat-amount">
{formattedFiat}
</div>
)}
</div>
</AssetListItem>
)
}
}

View File

@ -2,16 +2,8 @@ $wallet-balance-breakpoint: 890px;
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
.token-cell {
display: flex;
flex-flow: row nowrap;
align-items: center;
padding: 20px 24px;
cursor: pointer;
transition: linear 200ms;
background-color: rgba($wallet-balance-bg, 0);
position: relative;
flex: 1;
min-width: 0;
&__token-balance {
margin-right: 4px;
@ -42,37 +34,10 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
}
}
@media #{$wallet-balance-breakpoint-range} {
padding: 10% 4%;
}
&--active {
background-color: $manatee;
color: $white;
}
&__identicon {
margin-right: 15px;
border: '1px solid #dedede';
min-width: 50px;
@media #{$wallet-balance-breakpoint-range} {
margin-right: 4%;
}
}
&--outdated > &__identicon {
&--outdated &__icon {
opacity: 0.5
}
&__balance-ellipsis {
display: flex;
align-items: center;
min-width: 0;
flex: 1;
}
&--outdated > &__balance-ellipsis {
&--outdated &__balance-wrapper {
opacity: 0.5
}

View File

@ -1,48 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Identicon from '../identicon'
import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
export default class Balance extends PureComponent {
static propTypes = {
account: PropTypes.object,
showFiat: PropTypes.bool,
}
renderBalance () {
const { account, showFiat } = this.props
const balanceValue = account && account.balance
return (
<div className="flex-column balance-display">
<UserPreferencedCurrencyDisplay
className="token-amount"
value={balanceValue}
type={PRIMARY}
ethNumberOfDecimals={4}
/>
{
showFiat && (
<UserPreferencedCurrencyDisplay
value={balanceValue}
type={SECONDARY}
ethNumberOfDecimals={4}
/>
)
}
</div>
)
}
render () {
return (
<div className="balance-container">
<Identicon
diameter={50}
/>
{ this.renderBalance() }
</div>
)
}
}

View File

@ -1,22 +0,0 @@
import { connect } from 'react-redux'
import Balance from './balance.component'
import {
getMetaMaskAccounts,
getIsMainnet,
preferencesSelector,
} from '../../../selectors'
const mapStateToProps = (state) => {
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
const accounts = getMetaMaskAccounts(state)
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
const account = accounts[selectedAddress]
return {
account,
showFiat: (isMainnet || !!showFiatInTestnets),
}
}
export default connect(mapStateToProps)(Balance)

View File

@ -1 +0,0 @@
export { default } from './balance.container'