mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
UX: Multichain: Added TokenList Component (#17859)
* added redesign storybook * updated token list * updated css * fixed lint error * updated the new token list component * fixed redesign folder error * reverted changes in settings.json * updated redesign to multichain * added feature flag * reverted settings.json * added detect token banner * added button componeny * fixed lint errors * removed settings * fixed lint errors * added stories for multichain * updated no token found string * updated lint error * updated padding values * updated padding values * updated tabs with role button * updated hover state * updated components with multichain * fixed lint errors * updated multichain import token link * updated a tag * updated fixes * updated onClick to handleClick * updated setShowDetectedTokens proptype * updated multichain tokenlist with item suffix * updated tests * updated tests * updated token list css * updated snapshot * updated text * reverted story * added story for multichain token list * updated story * updated tooltip * updated the new token list component * fixed redesign folder error * added feature flag * reverted unused setting change * removed token list * fixed lint error * updated status * updated tooltip * updated token-list-item changes * updated actionbutton click for detect token banner * updated snapshot * updated symbol * updated styles * updated eth decimal and token url * updated snapshot * updated scripts * updated snapshots
This commit is contained in:
parent
68f928c8a2
commit
fcfb8a8938
@ -12,21 +12,26 @@ import {
|
|||||||
getNativeCurrencyImage,
|
getNativeCurrencyImage,
|
||||||
getDetectedTokensInCurrentNetwork,
|
getDetectedTokensInCurrentNetwork,
|
||||||
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
|
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
|
||||||
|
getTokenList,
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
|
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
|
||||||
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay';
|
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay';
|
||||||
import Typography from '../../ui/typography/typography';
|
|
||||||
import Box from '../../ui/box/box';
|
import Box from '../../ui/box/box';
|
||||||
import {
|
import {
|
||||||
Color,
|
Color,
|
||||||
TypographyVariant,
|
TextVariant,
|
||||||
FONT_WEIGHT,
|
TEXT_ALIGN,
|
||||||
JustifyContent,
|
|
||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
|
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
|
||||||
import DetectedToken from '../detected-token/detected-token';
|
import DetectedToken from '../detected-token/detected-token';
|
||||||
|
import {
|
||||||
|
DetectedTokensBanner,
|
||||||
|
MultichainTokenListItem,
|
||||||
|
MultichainImportTokenLink,
|
||||||
|
} from '../../multichain';
|
||||||
|
import { Text } from '../../component-library';
|
||||||
import DetectedTokensLink from './detetcted-tokens-link/detected-tokens-link';
|
import DetectedTokensLink from './detetcted-tokens-link/detected-tokens-link';
|
||||||
|
|
||||||
const AssetList = ({ onClickAsset }) => {
|
const AssetList = ({ onClickAsset }) => {
|
||||||
@ -69,20 +74,38 @@ const AssetList = ({ onClickAsset }) => {
|
|||||||
const istokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector(
|
const istokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector(
|
||||||
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
|
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
|
||||||
);
|
);
|
||||||
|
const tokenList = useSelector(getTokenList);
|
||||||
|
const tokenData = Object.values(tokenList).find(
|
||||||
|
(token) => token.symbol === primaryCurrencyProperties.suffix,
|
||||||
|
);
|
||||||
|
const title = tokenData?.name || primaryCurrencyProperties.suffix;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AssetListItem
|
{process.env.MULTICHAIN ? (
|
||||||
onClick={() => onClickAsset(nativeCurrency)}
|
<MultichainTokenListItem
|
||||||
data-testid="wallet-balance"
|
onClick={() => onClickAsset(nativeCurrency)}
|
||||||
primary={
|
title={title}
|
||||||
primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value
|
primary={
|
||||||
}
|
primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value
|
||||||
tokenSymbol={primaryCurrencyProperties.suffix}
|
}
|
||||||
secondary={showFiat ? secondaryCurrencyDisplay : undefined}
|
tokenSymbol={primaryCurrencyProperties.suffix}
|
||||||
tokenImage={balanceIsLoading ? null : primaryTokenImage}
|
secondary={showFiat ? secondaryCurrencyDisplay : undefined}
|
||||||
identiconBorder
|
tokenImage={balanceIsLoading ? null : primaryTokenImage}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<AssetListItem
|
||||||
|
onClick={() => onClickAsset(nativeCurrency)}
|
||||||
|
data-testid="wallet-balance"
|
||||||
|
primary={
|
||||||
|
primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value
|
||||||
|
}
|
||||||
|
tokenSymbol={primaryCurrencyProperties.suffix}
|
||||||
|
secondary={showFiat ? secondaryCurrencyDisplay : undefined}
|
||||||
|
tokenImage={balanceIsLoading ? null : primaryTokenImage}
|
||||||
|
identiconBorder
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<TokenList
|
<TokenList
|
||||||
onTokenClick={(tokenAddress) => {
|
onTokenClick={(tokenAddress) => {
|
||||||
onClickAsset(tokenAddress);
|
onClickAsset(tokenAddress);
|
||||||
@ -98,19 +121,36 @@ const AssetList = ({ onClickAsset }) => {
|
|||||||
/>
|
/>
|
||||||
{detectedTokens.length > 0 &&
|
{detectedTokens.length > 0 &&
|
||||||
!istokenDetectionInactiveOnNonMainnetSupportedNetwork && (
|
!istokenDetectionInactiveOnNonMainnetSupportedNetwork && (
|
||||||
<DetectedTokensLink setShowDetectedTokens={setShowDetectedTokens} />
|
<>
|
||||||
|
{process.env.MULTICHAIN ? (
|
||||||
|
<DetectedTokensBanner
|
||||||
|
actionButtonOnClick={() => setShowDetectedTokens(true)}
|
||||||
|
margin={4}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DetectedTokensLink
|
||||||
|
setShowDetectedTokens={setShowDetectedTokens}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Box marginTop={detectedTokens.length > 0 ? 0 : 4}>
|
<Box marginTop={detectedTokens.length > 0 ? 0 : 4}>
|
||||||
<Box justifyContent={JustifyContent.center}>
|
{process.env.MULTICHAIN ? (
|
||||||
<Typography
|
<MultichainImportTokenLink margin={4} />
|
||||||
color={Color.textAlternative}
|
) : (
|
||||||
variant={TypographyVariant.H6}
|
<>
|
||||||
fontWeight={FONT_WEIGHT.NORMAL}
|
<Text
|
||||||
>
|
color={Color.textAlternative}
|
||||||
{t('missingToken')}
|
variant={TextVariant.bodySm}
|
||||||
</Typography>
|
as="h6"
|
||||||
</Box>
|
textAlign={TEXT_ALIGN.CENTER}
|
||||||
<ImportTokenLink />
|
>
|
||||||
|
{t('missingToken')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<ImportTokenLink />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{showDetectedTokens && (
|
{showDetectedTokens && (
|
||||||
<DetectedToken setShowDetectedTokens={setShowDetectedTokens} />
|
<DetectedToken setShowDetectedTokens={setShowDetectedTokens} />
|
||||||
|
@ -3,9 +3,12 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import AssetListItem from '../asset-list-item';
|
import AssetListItem from '../asset-list-item';
|
||||||
import { getSelectedAddress } from '../../../selectors';
|
import { getSelectedAddress, getTokenList } from '../../../selectors';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
|
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
|
||||||
|
import { MultichainTokenListItem } from '../../multichain';
|
||||||
|
import { ButtonLink, Text } from '../../component-library';
|
||||||
|
import { TextColor } from '../../../helpers/constants/design-system';
|
||||||
|
|
||||||
export default function TokenCell({
|
export default function TokenCell({
|
||||||
address,
|
address,
|
||||||
@ -19,39 +22,58 @@ export default function TokenCell({
|
|||||||
}) {
|
}) {
|
||||||
const userAddress = useSelector(getSelectedAddress);
|
const userAddress = useSelector(getSelectedAddress);
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
|
const tokenList = useSelector(getTokenList);
|
||||||
|
const tokenData = Object.values(tokenList).find(
|
||||||
|
(token) => token.symbol === symbol,
|
||||||
|
);
|
||||||
|
const title = tokenData?.name || symbol;
|
||||||
|
const tokenImage = tokenData?.iconUrl || image;
|
||||||
const formattedFiat = useTokenFiatAmount(address, string, symbol);
|
const formattedFiat = useTokenFiatAmount(address, string, symbol);
|
||||||
const warning = balanceError ? (
|
const warning = balanceError ? (
|
||||||
<span>
|
<Text as="span">
|
||||||
{t('troubleTokenBalances')}
|
{t('troubleTokenBalances')}
|
||||||
<a
|
<ButtonLink
|
||||||
href={`https://ethplorer.io/address/${userAddress}`}
|
href={`https://ethplorer.io/address/${userAddress}`}
|
||||||
rel="noopener noreferrer"
|
externalLink
|
||||||
target="_blank"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
style={{ color: 'var(--color-warning-default)' }}
|
textProps={{
|
||||||
|
color: TextColor.warningDefault,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('here')}
|
{t('here')}
|
||||||
</a>
|
</ButtonLink>
|
||||||
</span>
|
</Text>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AssetListItem
|
<>
|
||||||
className={classnames('token-cell', {
|
{process.env.MULTICHAIN ? (
|
||||||
'token-cell--outdated': Boolean(balanceError),
|
<MultichainTokenListItem
|
||||||
})}
|
onClick={() => onClick(address)}
|
||||||
iconClassName="token-cell__icon"
|
tokenSymbol={symbol}
|
||||||
onClick={onClick.bind(null, address)}
|
tokenImage={tokenImage}
|
||||||
tokenAddress={address}
|
primary={`${string || 0}`}
|
||||||
tokenSymbol={symbol}
|
secondary={formattedFiat}
|
||||||
tokenDecimals={decimals}
|
title={title}
|
||||||
tokenImage={image}
|
/>
|
||||||
warning={warning}
|
) : (
|
||||||
primary={`${string || 0}`}
|
<AssetListItem
|
||||||
secondary={formattedFiat}
|
className={classnames('token-cell', {
|
||||||
isERC721={isERC721}
|
'token-cell--outdated': Boolean(balanceError),
|
||||||
/>
|
})}
|
||||||
|
iconClassName="token-cell__icon"
|
||||||
|
onClick={() => onClick(address)}
|
||||||
|
tokenAddress={address}
|
||||||
|
tokenSymbol={symbol}
|
||||||
|
tokenDecimals={decimals}
|
||||||
|
tokenImage={image}
|
||||||
|
warning={warning}
|
||||||
|
primary={`${string || 0}`}
|
||||||
|
secondary={formattedFiat}
|
||||||
|
isERC721={isERC721}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DetectedTokensBanner should render correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-info multichain-detected-token-banner box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-primary-muted box--rounded-sm"
|
||||||
|
data-testid="detected-token-banner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||||
|
style="mask-image: url('./images/icons/info.svg');"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md mm-text--color-text-default box--flex-direction-row"
|
||||||
|
>
|
||||||
|
3 new tokens found in this account
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md mm-text--color-primary-default box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-transparent"
|
||||||
|
>
|
||||||
|
Import tokens
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,60 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import { getDetectedTokensInCurrentNetwork } from '../../../selectors';
|
||||||
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
|
||||||
|
import { BannerAlert } from '../../component-library';
|
||||||
|
|
||||||
|
export const DetectedTokensBanner = ({
|
||||||
|
className,
|
||||||
|
actionButtonOnClick,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
|
||||||
|
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
||||||
|
const detectedTokensDetails = detectedTokens.map(
|
||||||
|
({ address, symbol }) => `${symbol} - ${address}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnClick = () => {
|
||||||
|
actionButtonOnClick();
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_IMPORT_CLICKED,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
properties: {
|
||||||
|
source: EVENT.SOURCE.TOKEN.DETECTED,
|
||||||
|
tokens: detectedTokensDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<BannerAlert
|
||||||
|
className={classNames('multichain-detected-token-banner', className)}
|
||||||
|
actionButtonLabel={t('importTokensCamelCase')}
|
||||||
|
actionButtonOnClick={handleOnClick}
|
||||||
|
data-testid="detected-token-banner"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{detectedTokens.length === 1
|
||||||
|
? t('numberOfNewTokensDetectedSingular')
|
||||||
|
: t('numberOfNewTokensDetectedPlural', [detectedTokens.length])}
|
||||||
|
</BannerAlert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DetectedTokensBanner.propTypes = {
|
||||||
|
/**
|
||||||
|
* Handler to be passed to the DetectedTokenBanner component
|
||||||
|
*/
|
||||||
|
actionButtonOnClick: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* An additional className to the DetectedTokenBanner component
|
||||||
|
*/
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DetectedTokensBanner } from './detected-token-banner';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Multichain/DetectedTokensBanner',
|
||||||
|
component: DetectedTokensBanner,
|
||||||
|
argTypes: {
|
||||||
|
actionButtonOnClick: { action: 'setShowDetectedTokens' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = (args) => <DetectedTokensBanner {...args} />;
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { renderWithProvider, screen, fireEvent } from '../../../../test/jest';
|
||||||
|
import configureStore from '../../../store/store';
|
||||||
|
import testData from '../../../../.storybook/test-data';
|
||||||
|
|
||||||
|
import { DetectedTokensBanner } from './detected-token-banner';
|
||||||
|
|
||||||
|
describe('DetectedTokensBanner', () => {
|
||||||
|
let setShowDetectedTokensSpy;
|
||||||
|
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setShowDetectedTokensSpy = jest.fn();
|
||||||
|
args.actionButtonOnClick = setShowDetectedTokensSpy;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const store = configureStore(testData);
|
||||||
|
const { getByTestId, container } = renderWithProvider(
|
||||||
|
<DetectedTokensBanner {...args} />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByTestId('detected-token-banner')).toBeDefined();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('should render number of tokens detected link', () => {
|
||||||
|
const store = configureStore(testData);
|
||||||
|
renderWithProvider(<DetectedTokensBanner {...args} />, store);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('3 new tokens found in this account'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Import tokens'));
|
||||||
|
expect(setShowDetectedTokensSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
1
ui/components/multichain/detected-token-banner/index.js
Normal file
1
ui/components/multichain/detected-token-banner/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { DetectedTokensBanner } from './detected-token-banner';
|
@ -1,3 +1,6 @@
|
|||||||
export { AccountListItem } from './account-list-item';
|
export { AccountListItem } from './account-list-item';
|
||||||
export { AccountListItemMenu } from './account-list-item-menu';
|
export { AccountListItemMenu } from './account-list-item-menu';
|
||||||
export { AccountListMenu } from './account-list-menu';
|
export { AccountListMenu } from './account-list-menu';
|
||||||
|
export { DetectedTokensBanner } from './detected-token-banner';
|
||||||
|
export { MultichainImportTokenLink } from './multichain-import-token-link';
|
||||||
|
export { MultichainTokenListItem } from './multichain-token-list-item';
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
@import 'account-list-item/index';
|
|
||||||
@import 'account-list-menu/index';
|
|
9
ui/components/multichain/multichain-components.scss
Normal file
9
ui/components/multichain/multichain-components.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Please import your styles in order of atomicity.
|
||||||
|
* The most atomic styles should be imported first.
|
||||||
|
* This will help improve specificity and reduce the chance of
|
||||||
|
* unintended overrides.
|
||||||
|
**/
|
||||||
|
@import 'account-list-item/index';
|
||||||
|
@import 'account-list-menu/index';
|
||||||
|
@import 'multichain-token-list-item/multichain-token-list-item';
|
@ -0,0 +1,75 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Import Token Link should match snapshot for goerli chainId 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box multichain-import-token-link box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-base--size-md mm-button-link mm-text--body-md mm-text--color-primary-default box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-transparent"
|
||||||
|
data-testid="import-token-button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/add.svg');"
|
||||||
|
/>
|
||||||
|
Import tokens
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box box--padding-top-4 box--padding-bottom-4 box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md mm-text--color-primary-default box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-transparent"
|
||||||
|
data-testid="refresh-list-button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/refresh.svg');"
|
||||||
|
/>
|
||||||
|
Refresh list
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Import Token Link should match snapshot for mainnet chainId 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box multichain-import-token-link box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-base--size-md mm-button-link mm-text--body-md mm-text--color-primary-default box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-transparent"
|
||||||
|
data-testid="import-token-button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/add.svg');"
|
||||||
|
/>
|
||||||
|
Import tokens
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box box--padding-top-4 box--padding-bottom-4 box--display-flex box--flex-direction-row box--align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md mm-text--color-primary-default box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-transparent"
|
||||||
|
data-testid="refresh-list-button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="box mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-inherit"
|
||||||
|
style="mask-image: url('./images/icons/refresh.svg');"
|
||||||
|
/>
|
||||||
|
Refresh list
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1 @@
|
|||||||
|
export { MultichainImportTokenLink } from './multichain-import-token-link';
|
@ -0,0 +1,87 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Box from '../../ui/box/box';
|
||||||
|
import { ButtonLink, ICON_NAMES } from '../../component-library';
|
||||||
|
import {
|
||||||
|
AlignItems,
|
||||||
|
DISPLAY,
|
||||||
|
Size,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import { IMPORT_TOKEN_ROUTE } from '../../../helpers/constants/routes';
|
||||||
|
import { detectNewTokens } from '../../../store/actions';
|
||||||
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
|
||||||
|
import {
|
||||||
|
getIsTokenDetectionSupported,
|
||||||
|
getIsTokenDetectionInactiveOnMainnet,
|
||||||
|
} from '../../../selectors';
|
||||||
|
|
||||||
|
export const MultichainImportTokenLink = ({ className, ...props }) => {
|
||||||
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
const t = useI18nContext();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const isTokenDetectionSupported = useSelector(getIsTokenDetectionSupported);
|
||||||
|
const isTokenDetectionInactiveOnMainnet = useSelector(
|
||||||
|
getIsTokenDetectionInactiveOnMainnet,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isTokenDetectionAvailable =
|
||||||
|
isTokenDetectionSupported ||
|
||||||
|
isTokenDetectionInactiveOnMainnet ||
|
||||||
|
Boolean(process.env.IN_TEST);
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className={classnames('multichain-import-token-link', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Box display={DISPLAY.FLEX} alignItems={AlignItems.center}>
|
||||||
|
<ButtonLink
|
||||||
|
size={Size.MD}
|
||||||
|
data-testid="import-token-button"
|
||||||
|
startIconName={ICON_NAMES.ADD}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(IMPORT_TOKEN_ROUTE);
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_IMPORT_BUTTON_CLICKED,
|
||||||
|
category: EVENT.CATEGORIES.NAVIGATION,
|
||||||
|
properties: {
|
||||||
|
location: 'Home',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isTokenDetectionAvailable
|
||||||
|
? t('importTokensCamelCase')
|
||||||
|
: t('importTokensCamelCase').charAt(0).toUpperCase() +
|
||||||
|
t('importTokensCamelCase').slice(1)}
|
||||||
|
</ButtonLink>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
paddingBottom={4}
|
||||||
|
paddingTop={4}
|
||||||
|
>
|
||||||
|
<ButtonLink
|
||||||
|
startIconName={ICON_NAMES.REFRESH}
|
||||||
|
data-testid="refresh-list-button"
|
||||||
|
onClick={() => detectNewTokens()}
|
||||||
|
>
|
||||||
|
{t('refreshList')}
|
||||||
|
</ButtonLink>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MultichainImportTokenLink.propTypes = {
|
||||||
|
/**
|
||||||
|
* An additional className to apply to the TokenList.
|
||||||
|
*/
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MultichainImportTokenLink } from './multichain-import-token-link';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Multichain/MultichainImportTokenLink',
|
||||||
|
component: MultichainImportTokenLink,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = () => <MultichainImportTokenLink />;
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,101 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
|
import { detectNewTokens } from '../../../store/actions';
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
|
import { MultichainImportTokenLink } from './multichain-import-token-link';
|
||||||
|
|
||||||
|
const mockPushHistory = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => {
|
||||||
|
const original = jest.requireActual('react-router-dom');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
useLocation: jest.fn(() => ({ search: '' })),
|
||||||
|
useHistory: () => ({
|
||||||
|
push: mockPushHistory,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../store/actions.ts', () => ({
|
||||||
|
detectNewTokens: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Import Token Link', () => {
|
||||||
|
it('should match snapshot for goerli chainId', () => {
|
||||||
|
const mockState = {
|
||||||
|
metamask: {
|
||||||
|
provider: {
|
||||||
|
chainId: '0x5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureMockStore()(mockState);
|
||||||
|
|
||||||
|
const { container } = renderWithProvider(
|
||||||
|
<MultichainImportTokenLink />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match snapshot for mainnet chainId', () => {
|
||||||
|
const mockState = {
|
||||||
|
metamask: {
|
||||||
|
provider: {
|
||||||
|
chainId: '0x1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureMockStore()(mockState);
|
||||||
|
|
||||||
|
const { container } = renderWithProvider(
|
||||||
|
<MultichainImportTokenLink />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detectNewTokens when clicking refresh', () => {
|
||||||
|
const mockState = {
|
||||||
|
metamask: {
|
||||||
|
provider: {
|
||||||
|
chainId: '0x5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureMockStore()(mockState);
|
||||||
|
|
||||||
|
renderWithProvider(<MultichainImportTokenLink />, store);
|
||||||
|
|
||||||
|
const refreshList = screen.getByTestId('refresh-list-button');
|
||||||
|
fireEvent.click(refreshList);
|
||||||
|
|
||||||
|
expect(detectNewTokens).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should push import token route', () => {
|
||||||
|
const mockState = {
|
||||||
|
metamask: {
|
||||||
|
provider: {
|
||||||
|
chainId: '0x5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureMockStore()(mockState);
|
||||||
|
|
||||||
|
renderWithProvider(<MultichainImportTokenLink />, store);
|
||||||
|
|
||||||
|
const importToken = screen.getByTestId('import-token-button');
|
||||||
|
fireEvent.click(importToken);
|
||||||
|
|
||||||
|
expect(mockPushHistory).toHaveBeenCalledWith('/import-token');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,67 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`MultichainTokenListItem should render correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="box multichain-token-list-item box--display-flex box--gap-4 box--flex-direction-column"
|
||||||
|
data-testid="multichain-token-list-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="box multichain-token-list-item__container-cell box--padding-4 box--display-flex box--flex-direction-row"
|
||||||
|
data-testid="multichain-token-list-button"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box mm-badge-wrapper box--margin-right-3 box--display-inline-block box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box mm-text mm-avatar-base mm-avatar-base--size-md mm-avatar-token mm-avatar-token--with-halo mm-text--body-sm mm-text--text-transform-uppercase mm-text--color-text-default box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-background-alternative box--rounded-full box--border-color-border-default box--border-style-solid box--border-width-1"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box mm-badge-wrapper__badge-container mm-badge-wrapper__badge-container--circular-top-right box--flex-direction-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box mm-text mm-avatar-base mm-avatar-base--size-xs mm-avatar-network mm-text--body-xs mm-text--text-transform-uppercase mm-text--color-text-default box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--background-color-background-alternative box--rounded-full box--border-color-border-muted box--border-style-solid box--border-width-1"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="undefined logo"
|
||||||
|
class="mm-avatar-network__network-image"
|
||||||
|
src="./images/eth_logo.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box multichain-token-list-item__container-cell--text-container box--display-flex box--flex-direction-column box--width-full"
|
||||||
|
style="flex-grow: 1; overflow: hidden;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--display-flex box--gap-1 box--flex-direction-row box--justify-content-space-between"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="box box--flex-direction-row box--width-1/3"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md mm-text--font-weight-medium mm-text--ellipsis mm-text--color-text-default box--flex-direction-row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md mm-text--font-weight-medium mm-text--text-align-end mm-text--color-text-default box--flex-direction-row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="box mm-text mm-text--body-md mm-text--color-text-alternative box--flex-direction-row"
|
||||||
|
>
|
||||||
|
NaN
|
||||||
|
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1 @@
|
|||||||
|
export { MultichainTokenListItem } from './multichain-token-list-item';
|
@ -0,0 +1,162 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import {
|
||||||
|
BLOCK_SIZES,
|
||||||
|
BorderColor,
|
||||||
|
DISPLAY,
|
||||||
|
FLEX_DIRECTION,
|
||||||
|
FONT_WEIGHT,
|
||||||
|
JustifyContent,
|
||||||
|
Size,
|
||||||
|
TextColor,
|
||||||
|
TextVariant,
|
||||||
|
TEXT_ALIGN,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import {
|
||||||
|
AvatarNetwork,
|
||||||
|
AvatarToken,
|
||||||
|
BadgeWrapper,
|
||||||
|
Text,
|
||||||
|
} from '../../component-library';
|
||||||
|
import Box from '../../ui/box/box';
|
||||||
|
import { getNativeCurrencyImage } from '../../../selectors';
|
||||||
|
import Tooltip from '../../ui/tooltip';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
|
||||||
|
export const MultichainTokenListItem = ({
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
tokenSymbol,
|
||||||
|
tokenImage,
|
||||||
|
primary,
|
||||||
|
secondary,
|
||||||
|
title,
|
||||||
|
}) => {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const primaryTokenImage = useSelector(getNativeCurrencyImage);
|
||||||
|
const dataTheme = document.documentElement.getAttribute('data-theme');
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className={classnames('multichain-token-list-item', className)}
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||||
|
gap={4}
|
||||||
|
data-testid="multichain-token-list-item"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="multichain-token-list-item__container-cell"
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.ROW}
|
||||||
|
padding={4}
|
||||||
|
as="a"
|
||||||
|
data-testid="multichain-token-list-button"
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BadgeWrapper
|
||||||
|
badge={
|
||||||
|
<AvatarNetwork
|
||||||
|
size={Size.XS}
|
||||||
|
name={tokenSymbol}
|
||||||
|
src={primaryTokenImage}
|
||||||
|
borderColor={
|
||||||
|
primaryTokenImage
|
||||||
|
? BorderColor.borderMuted
|
||||||
|
: BorderColor.borderDefault
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
marginRight={3}
|
||||||
|
>
|
||||||
|
<AvatarToken
|
||||||
|
name={tokenSymbol}
|
||||||
|
src={tokenImage}
|
||||||
|
showHalo
|
||||||
|
borderColor={
|
||||||
|
tokenImage ? BorderColor.transparent : BorderColor.borderDefault
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</BadgeWrapper>
|
||||||
|
<Box
|
||||||
|
className="multichain-token-list-item__container-cell--text-container"
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||||
|
width={BLOCK_SIZES.FULL}
|
||||||
|
style={{ flexGrow: 1, overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
display={DISPLAY.FLEX}
|
||||||
|
justifyContent={JustifyContent.spaceBetween}
|
||||||
|
gap={1}
|
||||||
|
>
|
||||||
|
<Box width={[BLOCK_SIZES.ONE_THIRD]}>
|
||||||
|
<Tooltip
|
||||||
|
position="bottom"
|
||||||
|
interactive
|
||||||
|
html={title}
|
||||||
|
disabled={title?.length < 12}
|
||||||
|
tooltipInnerClassName="multichain-token-list-item__tooltip"
|
||||||
|
theme={dataTheme === 'light' ? 'dark' : 'light'}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
fontWeight={FONT_WEIGHT.MEDIUM}
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
ellipsis
|
||||||
|
>
|
||||||
|
{title === 'ETH' ? t('networkNameEthereum') : title}
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<Text
|
||||||
|
fontWeight={FONT_WEIGHT.MEDIUM}
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
width={[BLOCK_SIZES.TWO_THIRD]}
|
||||||
|
textAlign={TEXT_ALIGN.END}
|
||||||
|
>
|
||||||
|
{secondary}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Text color={TextColor.textAlternative}>
|
||||||
|
{Number(primary).toFixed(3)} {tokenSymbol}{' '}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MultichainTokenListItem.propTypes = {
|
||||||
|
/**
|
||||||
|
* An additional className to apply to the TokenList.
|
||||||
|
*/
|
||||||
|
className: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* The onClick handler to be passed to the MultichainTokenListItem component
|
||||||
|
*/
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
/**
|
||||||
|
* tokenSymbol represents the symbol of the Token
|
||||||
|
*/
|
||||||
|
tokenSymbol: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* title represents the name of the token and if name is not available then Symbol
|
||||||
|
*/
|
||||||
|
title: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* tokenImage represnts the image of the token icon
|
||||||
|
*/
|
||||||
|
tokenImage: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* primary represents the balance
|
||||||
|
*/
|
||||||
|
primary: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* secondary represents the balance in dollars
|
||||||
|
*/
|
||||||
|
secondary: PropTypes.string,
|
||||||
|
};
|
@ -0,0 +1,8 @@
|
|||||||
|
.multichain-token-list-item {
|
||||||
|
&__container-cell {
|
||||||
|
&:hover,
|
||||||
|
&:focus-within {
|
||||||
|
background-color: var(--color-background-default-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import testData from '../../../../.storybook/test-data';
|
||||||
|
import configureStore from '../../../store/store';
|
||||||
|
import { MultichainTokenListItem } from './multichain-token-list-item';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Multichain/MultichainTokenListItem',
|
||||||
|
component: MultichainTokenListItem,
|
||||||
|
argTypes: {
|
||||||
|
tokenSymbol: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
tokenImage: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
onClick: {
|
||||||
|
action: 'onClick',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
secondary: '$9.80 USD',
|
||||||
|
primary: '88.00687889',
|
||||||
|
tokenImage: './images/eth_logo.svg',
|
||||||
|
tokenSymbol: 'ETH',
|
||||||
|
title: 'Ethereum',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const customNetworkData = {
|
||||||
|
...testData,
|
||||||
|
metamask: { ...testData.metamask, nativeCurrency: '' },
|
||||||
|
};
|
||||||
|
const customNetworkStore = configureStore(customNetworkData);
|
||||||
|
|
||||||
|
const Template = (args) => {
|
||||||
|
return <MultichainTokenListItem {...args} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = Template.bind({});
|
||||||
|
|
||||||
|
export const ChaosStory = (args) => (
|
||||||
|
<div
|
||||||
|
style={{ width: '336px', border: '1px solid var(--color-border-muted)' }}
|
||||||
|
>
|
||||||
|
<MultichainTokenListItem {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
ChaosStory.storyName = 'ChaosStory';
|
||||||
|
|
||||||
|
ChaosStory.args = {
|
||||||
|
title: 'Really long, long name',
|
||||||
|
secondary: '$94556756776.80 USD',
|
||||||
|
primary: '34449765768526.00',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoImagesStory = Template.bind({});
|
||||||
|
|
||||||
|
NoImagesStory.decorators = [
|
||||||
|
(Story) => (
|
||||||
|
<Provider store={customNetworkStore}>
|
||||||
|
<Story />
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
NoImagesStory.args = {
|
||||||
|
tokenImage: '',
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
import { fireEvent } from '@testing-library/react';
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
|
import { MultichainTokenListItem } from './multichain-token-list-item';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
metamask: {
|
||||||
|
provider: {
|
||||||
|
ticker: 'ETH',
|
||||||
|
nickname: '',
|
||||||
|
chainId: '0x1',
|
||||||
|
type: 'mainnet',
|
||||||
|
},
|
||||||
|
useTokenDetection: false,
|
||||||
|
nativeCurrency: 'ETH',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('MultichainTokenListItem', () => {
|
||||||
|
const props = {
|
||||||
|
onClick: jest.fn(),
|
||||||
|
};
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const store = configureMockStore()(state);
|
||||||
|
const { getByTestId, container } = renderWithProvider(
|
||||||
|
<MultichainTokenListItem />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
expect(getByTestId('multichain-token-list-item')).toBeDefined();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with custom className', () => {
|
||||||
|
const store = configureMockStore()(state);
|
||||||
|
const { getByTestId } = renderWithProvider(
|
||||||
|
<MultichainTokenListItem className="multichain-token-list-item-test" />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
expect(getByTestId('multichain-token-list-item')).toHaveClass(
|
||||||
|
'multichain-token-list-item-test',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles click action and fires onClick', () => {
|
||||||
|
const store = configureMockStore()(state);
|
||||||
|
const { queryByTestId } = renderWithProvider(
|
||||||
|
<MultichainTokenListItem {...props} />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(queryByTestId('multichain-token-list-button'));
|
||||||
|
|
||||||
|
expect(props.onClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -26,19 +26,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[x-placement^=top] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
&[x-placement^='top'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
||||||
border-top-color: var(--color-background-default);
|
border-top-color: var(--color-background-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[x-placement^=right] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
&[x-placement^='right'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
||||||
border-right-color: var(--color-background-default);
|
border-right-color: var(--color-background-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[x-placement^=left] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
&[x-placement^='left'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
||||||
border-left-color: var(--color-background-default);
|
border-left-color: var(--color-background-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[x-placement^=bottom] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
&[x-placement^='bottom'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] {
|
||||||
border-bottom-color: var(--color-background-default);
|
border-bottom-color: var(--color-background-default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
@import './base-styles.scss';
|
@import './base-styles.scss';
|
||||||
@import '../components/component-library/component-library-components.scss';
|
@import '../components/component-library/component-library-components.scss';
|
||||||
@import '../components/app/app-components';
|
@import '../components/app/app-components';
|
||||||
@import '../components/multichain/index.scss';
|
|
||||||
@import '../components/ui/ui-components';
|
@import '../components/ui/ui-components';
|
||||||
|
@import '../components/multichain/multichain-components.scss';
|
||||||
@import '../pages/pages';
|
@import '../pages/pages';
|
||||||
@import './errors.scss';
|
@import './errors.scss';
|
||||||
@import './loading.scss';
|
@import './loading.scss';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user