mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-22 17:52:17 +01:00
feat: add TopAssets & NetworkIcon components
- move AssetLogo from `components` to `product-components`
- extract `NetworkIcon` from `CoinLogo`
- add `TopAssets` components
- if no `contractAddress` and `coingeckoId`, then render `CoinLogo`, else render `AssetLogo`
This commit is contained in:
@@ -49,6 +49,12 @@ module.exports = {
|
||||
],
|
||||
});
|
||||
|
||||
// Add polyfill for Node.js 'stream' module (required by jws)
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
stream: require.resolve('stream-browserify'),
|
||||
};
|
||||
|
||||
// NOTE: remove the previous loaders from handling the svgs
|
||||
const imageRule = config.module.rules.find(rule => rule?.['test']?.test('.svg'));
|
||||
if (imageRule) {
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"babel-loader": "^10.0.0",
|
||||
"postcss-styled-syntax": "^0.7.1",
|
||||
"storybook": "^9.0.16",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stylelint": "^16.14.1",
|
||||
"stylelint-config-standard": "^38.0.0",
|
||||
"typescript-styled-plugin": "^0.18.3"
|
||||
|
||||
@@ -24,15 +24,26 @@ export const AssetLogo: StoryObj<AssetLogoProps> = {
|
||||
contractAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
shouldTryToFetch: true,
|
||||
placeholder: 'USDC',
|
||||
showNetworkIcon: false,
|
||||
...getFramePropsStory(allowedAssetLogoFrameProps).args,
|
||||
},
|
||||
argTypes: {
|
||||
...getFramePropsStory(allowedAssetLogoFrameProps).argTypes,
|
||||
size: {
|
||||
options: allowedAssetLogoSizes,
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
},
|
||||
...getFramePropsStory(allowedAssetLogoFrameProps).argTypes,
|
||||
showNetworkIcon: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
shouldTryToFetch: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,18 +3,20 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
NetworkSymbol,
|
||||
type NetworkSymbolExtended,
|
||||
getNetwork,
|
||||
getNetworkByCoingeckoId,
|
||||
getNetworkFeatures,
|
||||
isNetworkSymbol,
|
||||
} from '@suite-common/wallet-config';
|
||||
import { getAssetLogoContractAddresses } from '@suite-common/wallet-utils';
|
||||
import { getAssetLogoUrl } from '@trezor/asset-utils';
|
||||
import {
|
||||
ElevationUp,
|
||||
type FrameProps,
|
||||
type FramePropsKeys,
|
||||
type TransientProps,
|
||||
FrameProps,
|
||||
FramePropsKeys,
|
||||
TransientProps,
|
||||
pickAndPrepareFrameProps,
|
||||
useElevation,
|
||||
withFrameProps,
|
||||
@@ -22,6 +24,8 @@ import {
|
||||
import { Elevation, borders, mapElevationToBackground, mapElevationToBorder } from '@trezor/theme';
|
||||
|
||||
import { AssetInitials } from './AssetInitials';
|
||||
import { LegacyNetworkSymbol, isNetworkSymbolWithIcon } from '../../constants/networks';
|
||||
import { NetworkIcon } from '../NetworkIcon/NetworkIcon';
|
||||
|
||||
export const allowedAssetLogoSizes = [20, 24, 32, 40] as const;
|
||||
type AssetLogoSize = (typeof allowedAssetLogoSizes)[number];
|
||||
@@ -38,6 +42,7 @@ export type AssetLogoProps = AllowedFrameProps & {
|
||||
placeholderWithTooltip?: boolean;
|
||||
placeholder: string;
|
||||
'data-testid'?: string;
|
||||
showNetworkIcon?: boolean;
|
||||
};
|
||||
|
||||
type LogoCandidate = { address: string; src: string; srcSet: string };
|
||||
@@ -46,6 +51,7 @@ const Container = styled.div<TransientProps<AllowedFrameProps> & { $size: number
|
||||
${({ $size }) => `
|
||||
width: ${$size}px;
|
||||
height: ${$size}px;
|
||||
position: relative;
|
||||
`}
|
||||
${withFrameProps}
|
||||
`;
|
||||
@@ -58,6 +64,13 @@ const Logo = styled.img<{ $size: number; $elevation: Elevation }>`
|
||||
background-color: ${mapElevationToBackground};
|
||||
`;
|
||||
|
||||
const StyledNetworkIcon = styled(NetworkIcon)`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
line-height: 0;
|
||||
`;
|
||||
|
||||
interface LogoProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
$size: number;
|
||||
}
|
||||
@@ -78,6 +91,19 @@ const makeCacheKey = (coingeckoId: string, addressesKey: string) =>
|
||||
|
||||
const makeAddressKey = (coingeckoId: string, address: string) => `${coingeckoId}::${address}`;
|
||||
|
||||
export function shouldShowNetworkIcon(
|
||||
networkSymbol?: NetworkSymbolExtended,
|
||||
contractAddress?: string | null,
|
||||
) {
|
||||
return (
|
||||
networkSymbol &&
|
||||
isNetworkSymbolWithIcon(networkSymbol) &&
|
||||
isNetworkSymbol(networkSymbol) &&
|
||||
Boolean(contractAddress) &&
|
||||
getNetworkFeatures(networkSymbol).includes('tokens')
|
||||
);
|
||||
}
|
||||
|
||||
export const getCoingeckoIdAndContractAddressIncludesNativeTokens = (
|
||||
coingeckoId: string,
|
||||
contractAddress: string[] | undefined,
|
||||
@@ -106,10 +132,11 @@ export const AssetLogo = ({
|
||||
size,
|
||||
coingeckoId,
|
||||
symbol,
|
||||
contractAddress,
|
||||
contractAddress = null,
|
||||
shouldTryToFetch = true,
|
||||
placeholder,
|
||||
placeholderWithTooltip = true,
|
||||
showNetworkIcon = false,
|
||||
'data-testid': dataTest,
|
||||
...rest
|
||||
}: AssetLogoProps) => {
|
||||
@@ -238,6 +265,12 @@ export const AssetLogo = ({
|
||||
onLoad={handleOnLoad}
|
||||
onError={handleLoadError}
|
||||
/>
|
||||
{showNetworkIcon && (
|
||||
<StyledNetworkIcon
|
||||
networkSymbol={symbol as NetworkSymbol | LegacyNetworkSymbol}
|
||||
size={size * 0.375}
|
||||
/>
|
||||
)}
|
||||
</ElevationUp>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { COIN_LOGO_TYPE, CoinLogoProps } from './CoinLogo';
|
||||
import { COINS } from './coins';
|
||||
import { CoinLogo as CoinLogoComponent } from '../../index';
|
||||
import { COIN_LOGO_TYPE, CoinLogo as CoinLogoComponent, CoinLogoProps } from './CoinLogo';
|
||||
import { COINS } from '../../constants/coins';
|
||||
|
||||
const Center = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -5,17 +5,19 @@ import styled from 'styled-components';
|
||||
|
||||
import { NetworkSymbol, getNetworkOptional } from '@suite-common/wallet-config';
|
||||
import { borders } from '@trezor/theme';
|
||||
import { roundTo } from '@trezor/utils';
|
||||
|
||||
import { COINS, LegacyNetworkSymbol } from './coins';
|
||||
import { NETWORK_ICONS } from './networks';
|
||||
import { COINS, LegacyNetworkSymbol } from '../../constants/coins';
|
||||
import { NETWORK_ICONS } from '../../constants/networks';
|
||||
import { NetworkIcon } from '../NetworkIcon/NetworkIcon';
|
||||
|
||||
export const COIN_LOGO_TYPE = ['token', 'network', 'tokenWithNetwork'] as const;
|
||||
export type CoinLogoType = (typeof COIN_LOGO_TYPE)[number];
|
||||
|
||||
const DEFAULT_SIZE = 32;
|
||||
|
||||
const getSize = (size?: number, border = 0, divisor = 1) =>
|
||||
`${(size ?? DEFAULT_SIZE) / divisor + border * 2}px`;
|
||||
const getSize = (size: number = DEFAULT_SIZE, border = 0, divisor = 1) =>
|
||||
`${roundTo(size / divisor + border * 2, 2)}px`;
|
||||
|
||||
export interface CoinLogoProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||
symbol: NetworkSymbol | LegacyNetworkSymbol;
|
||||
@@ -98,16 +100,7 @@ export const CoinLogo = ({
|
||||
}}
|
||||
loading={() => <span className="loading" />}
|
||||
/>
|
||||
{badge && symbolSrc != null && (
|
||||
<ReactSVG
|
||||
src={badge}
|
||||
beforeInjection={svg => {
|
||||
svg.setAttribute('width', getSize(size, 0, 3));
|
||||
svg.setAttribute('height', getSize(size, 0, 3));
|
||||
}}
|
||||
loading={() => <span className="loading" />}
|
||||
/>
|
||||
)}
|
||||
{badge && <NetworkIcon networkSymbol={symbol} size={roundTo(size / 3, 2)} />}
|
||||
</SvgWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@ import styled from 'styled-components';
|
||||
import { networksCollection } from '@suite-common/wallet-config';
|
||||
import { StoryColumn } from '@trezor/components';
|
||||
|
||||
import { COINS, isCoinSymbol } from './coins';
|
||||
import { NETWORK_ICONS, isNetworkSymbol } from './networks';
|
||||
import { CoinLogo } from '../../index';
|
||||
import { CoinLogo } from './CoinLogo';
|
||||
import { COINS, isCoinSymbol } from '../../constants/coins';
|
||||
import { NETWORK_ICONS, isNetworkSymbolWithIcon } from '../../constants/networks';
|
||||
|
||||
const Heading = styled.h2`
|
||||
margin-bottom: 2px;
|
||||
@@ -67,7 +67,7 @@ export const All: StoryObj = {
|
||||
{Object.keys(NETWORK_ICONS).map(network => (
|
||||
<Icon key={network}>
|
||||
<CoinName>{network}</CoinName>
|
||||
{isNetworkSymbol(network) && (
|
||||
{isNetworkSymbolWithIcon(network) && (
|
||||
<CoinLogo
|
||||
symbol={network}
|
||||
data-testid={`network-${network}`}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
|
||||
// These coins are not supported in Suite, but exist in Trezor Connect
|
||||
export type LegacyNetworkSymbol =
|
||||
| 'eos'
|
||||
| 'nem'
|
||||
| 'xtz'
|
||||
| 'dash'
|
||||
| 'dgb'
|
||||
| 'nmc'
|
||||
| 'vtc'
|
||||
| 'btg'
|
||||
| 'xmr';
|
||||
|
||||
export const COINS: Record<NetworkSymbol | LegacyNetworkSymbol, string> = {
|
||||
ada: require('../../images/coins/ada.svg'),
|
||||
arb: require('../../images/coins/arb.svg'),
|
||||
avax: require('../../images/coins/avax.svg'),
|
||||
base: require('../../images/coins/base.svg'),
|
||||
bch: require('../../images/coins/bch.svg'),
|
||||
bsc: require('../../images/coins/bsc.svg'),
|
||||
btc: require('../../images/coins/btc.svg'),
|
||||
btg: require('../../images/coins/btg.svg'),
|
||||
dash: require('../../images/coins/dash.svg'),
|
||||
dgb: require('../../images/coins/dgb.svg'),
|
||||
doge: require('../../images/coins/doge.svg'),
|
||||
dsol: require('../../images/coins/dsol.svg'),
|
||||
eos: require('../../images/coins/eos.svg'),
|
||||
etc: require('../../images/coins/etc.svg'),
|
||||
eth: require('../../images/coins/eth.svg'),
|
||||
ltc: require('../../images/coins/ltc.svg'),
|
||||
op: require('../../images/coins/op.svg'),
|
||||
pol: require('../../images/coins/pol.svg'),
|
||||
nem: require('../../images/coins/nem.svg'),
|
||||
nmc: require('../../images/coins/nmc.svg'),
|
||||
regtest: require('../../images/coins/btc_test.svg'),
|
||||
sol: require('../../images/coins/sol.svg'),
|
||||
tada: require('../../images/coins/tada.svg'),
|
||||
test: require('../../images/coins/btc_test.svg'),
|
||||
thod: require('../../images/coins/thod.svg'),
|
||||
tsep: require('../../images/coins/tsep.svg'),
|
||||
txlm: require('../../images/coins/txlm.svg'),
|
||||
txrp: require('../../images/coins/txrp.svg'),
|
||||
vtc: require('../../images/coins/vtc.svg'),
|
||||
xlm: require('../../images/coins/xlm.svg'),
|
||||
xmr: require('../../images/coins/xmr.svg'),
|
||||
xrp: require('../../images/coins/xrp.svg'),
|
||||
xtz: require('../../images/coins/xtz.svg'),
|
||||
zec: require('../../images/coins/zec.svg'),
|
||||
};
|
||||
|
||||
export const isCoinSymbol = (
|
||||
coinSymbol: string,
|
||||
): coinSymbol is NetworkSymbol | LegacyNetworkSymbol =>
|
||||
Object.prototype.hasOwnProperty.call(COINS, coinSymbol);
|
||||
@@ -1,45 +0,0 @@
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
|
||||
import { LegacyNetworkSymbol } from './coins';
|
||||
|
||||
export const NETWORK_ICONS: Record<NetworkSymbol | LegacyNetworkSymbol, string> = {
|
||||
ada: require('../../images/networks/ada.svg'),
|
||||
arb: require('../../images/networks/arb.svg'),
|
||||
avax: require('../../images/networks/avax.svg'),
|
||||
base: require('../../images/networks/base.svg'),
|
||||
bch: require('../../images/networks/bch.svg'),
|
||||
bsc: require('../../images/networks/bsc.svg'),
|
||||
btc: require('../../images/networks/btc.svg'),
|
||||
doge: require('../../images/networks/doge.svg'),
|
||||
dsol: require('../../images/networks/dsol.svg'),
|
||||
etc: require('../../images/networks/etc.svg'),
|
||||
eth: require('../../images/networks/eth.svg'),
|
||||
ltc: require('../../images/networks/ltc.svg'),
|
||||
op: require('../../images/networks/op.svg'),
|
||||
pol: require('../../images/networks/pol.svg'),
|
||||
regtest: require('../../images/networks/btc_test.svg'),
|
||||
sol: require('../../images/networks/sol.svg'),
|
||||
tada: require('../../images/networks/tada.svg'),
|
||||
test: require('../../images/networks/btc_test.svg'),
|
||||
thod: require('../../images/networks/thod.svg'),
|
||||
tsep: require('../../images/networks/tsep.svg'),
|
||||
txlm: require('../../images/networks/txlm.svg'),
|
||||
txrp: require('../../images/networks/txrp.svg'),
|
||||
xrp: require('../../images/networks/xrp.svg'),
|
||||
zec: require('../../images/networks/zec.svg'),
|
||||
eos: require('../../images/networks/eos.svg'),
|
||||
nem: require('../../images/networks/nem.svg'),
|
||||
xlm: require('../../images/networks/xlm.svg'),
|
||||
xtz: require('../../images/networks/xtz.svg'),
|
||||
dash: require('../../images/networks/dash.svg'),
|
||||
dgb: require('../../images/networks/dgb.svg'),
|
||||
nmc: require('../../images/networks/nmc.svg'),
|
||||
vtc: require('../../images/networks/vtc.svg'),
|
||||
btg: require('../../images/networks/btg.svg'),
|
||||
xmr: require('../../images/networks/xmr.svg'),
|
||||
};
|
||||
|
||||
export const isNetworkSymbol = (
|
||||
networkSymbol: string,
|
||||
): networkSymbol is NetworkSymbol | LegacyNetworkSymbol =>
|
||||
Object.prototype.hasOwnProperty.call(NETWORK_ICONS, networkSymbol);
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Column, H2, Paragraph, StoryColumn } from '@trezor/components';
|
||||
|
||||
import { NetworkIcon } from './NetworkIcon';
|
||||
import { NETWORK_ICONS, isNetworkSymbolWithIcon } from '../../constants/networks';
|
||||
|
||||
const WrapperIcons = styled.div`
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-gap: 5px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof NetworkIcon> = {
|
||||
title: 'NetworkIcon',
|
||||
component: NetworkIcon,
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const All: StoryObj = {
|
||||
render: () => (
|
||||
<StoryColumn minWidth={700}>
|
||||
<H2 margin={{ bottom: 2 }}>Network Icons</H2>
|
||||
<Paragraph>All available network SVG icons</Paragraph>
|
||||
<WrapperIcons>
|
||||
{Object.keys(NETWORK_ICONS).map(networkSymbol => (
|
||||
<Column
|
||||
key={networkSymbol}
|
||||
minHeight={100}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Paragraph margin={{ bottom: 8 }} variant="tertiary">
|
||||
{networkSymbol}
|
||||
</Paragraph>
|
||||
{isNetworkSymbolWithIcon(networkSymbol) && (
|
||||
<NetworkIcon networkSymbol={networkSymbol} size={32} />
|
||||
)}
|
||||
</Column>
|
||||
))}
|
||||
</WrapperIcons>
|
||||
</StoryColumn>
|
||||
),
|
||||
};
|
||||
|
||||
export const Single: StoryObj<typeof NetworkIcon> = {
|
||||
args: {
|
||||
networkSymbol: 'btc',
|
||||
size: 32,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ReactSVG } from 'react-svg';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
|
||||
import { LegacyNetworkSymbol, NETWORK_ICONS } from '../../constants/networks';
|
||||
|
||||
const StyledReactSVG = styled(ReactSVG)`
|
||||
line-height: 0;
|
||||
` as typeof ReactSVG;
|
||||
|
||||
export interface NetworkIconProps {
|
||||
networkSymbol: NetworkSymbol | LegacyNetworkSymbol;
|
||||
size?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function NetworkIcon({ networkSymbol, size = 32, className }: NetworkIconProps) {
|
||||
const src = NETWORK_ICONS[networkSymbol];
|
||||
|
||||
if (!src) {
|
||||
console.error(`Network icon for ${networkSymbol} not found`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledReactSVG
|
||||
src={NETWORK_ICONS[networkSymbol]}
|
||||
beforeInjection={svg => {
|
||||
svg.setAttribute('width', `${size}px`);
|
||||
svg.setAttribute('height', `${size}px`);
|
||||
}}
|
||||
loading={() => <span className="loading" />}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import { Badge, Column, Row, Text } from '@trezor/components';
|
||||
import { spacings, spacingsPx } from '@trezor/theme';
|
||||
|
||||
import { AssetOptionBaseProps } from './SelectAssetModal';
|
||||
import { isCoinSymbol } from '../../constants/coins';
|
||||
import { AssetLogo } from '../AssetLogo/AssetLogo';
|
||||
import { CoinLogo } from '../CoinLogo/CoinLogo';
|
||||
import { isCoinSymbol } from '../CoinLogo/coins';
|
||||
|
||||
const ClickableContainer = styled.div`
|
||||
cursor: pointer;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Asset, TopAssets } from './TopAssets';
|
||||
|
||||
const meta: Meta<typeof TopAssets> = {
|
||||
title: 'TopAssets',
|
||||
component: TopAssets,
|
||||
};
|
||||
export default meta;
|
||||
|
||||
const popularAssets: Asset[] = [
|
||||
{
|
||||
id: 'btc',
|
||||
symbol: 'btc',
|
||||
contractAddress: null,
|
||||
coingeckoId: 'bitcoin',
|
||||
isNativeToken: true,
|
||||
},
|
||||
{
|
||||
symbol: 'eth',
|
||||
contractAddress: null,
|
||||
coingeckoId: 'ethereum',
|
||||
isNativeToken: true,
|
||||
id: 'eth',
|
||||
},
|
||||
{
|
||||
symbol: 'sol',
|
||||
id: 'sol',
|
||||
contractAddress: 'WCTk5xWdn5SYg56twGj32sUF3W4WFQ48ogezLBuYTBY',
|
||||
coingeckoId: 'solana',
|
||||
isNativeToken: false,
|
||||
},
|
||||
{
|
||||
symbol: 'eth',
|
||||
contractAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
coingeckoId: 'ethereum',
|
||||
isNativeToken: false,
|
||||
id: 'usdc',
|
||||
},
|
||||
{
|
||||
symbol: 'base',
|
||||
id: 'base',
|
||||
contractAddress: null,
|
||||
coingeckoId: 'ethereum',
|
||||
isNativeToken: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const Default: StoryObj<typeof TopAssets> = {
|
||||
args: {
|
||||
assets: popularAssets,
|
||||
onAssetClick: asset => {
|
||||
console.log(asset);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Text } from '@trezor/components';
|
||||
import { borders, mapElevationToBorder, spacings, spacingsPx } from '@trezor/theme';
|
||||
|
||||
import { AssetLogo, AssetLogoProps } from '../AssetLogo/AssetLogo';
|
||||
import { CoinLogo } from '../CoinLogo/CoinLogo';
|
||||
|
||||
const Container = styled.div<{ $itemsCount: number }>`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${({ $itemsCount }) => $itemsCount}, 1fr);
|
||||
border: 1px solid ${({ theme }) => mapElevationToBorder({ $elevation: 1, theme })};
|
||||
border-radius: ${borders.radii.sm};
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Item = styled('button')<{ $isLast: boolean }>`
|
||||
border: unset;
|
||||
background: unset;
|
||||
box-shadow: unset;
|
||||
cursor: pointer;
|
||||
border-right: ${({ $isLast, theme }) =>
|
||||
!$isLast && `1px solid ${mapElevationToBorder({ $elevation: 1, theme })}`};
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: ${spacingsPx.xxs};
|
||||
flex-direction: column;
|
||||
padding: ${spacings.xs * 1.25}px ${spacings.xs * 1.5}px ${spacings.xs * 0.75}px;
|
||||
`;
|
||||
|
||||
export type Asset = {
|
||||
id: string;
|
||||
symbol: string;
|
||||
contractAddress: string | null;
|
||||
coingeckoId: string;
|
||||
isNativeToken: boolean;
|
||||
};
|
||||
export interface TopAssetsProps {
|
||||
assets: Asset[];
|
||||
onAssetClick: (asset: Asset) => void;
|
||||
logoSize?: AssetLogoProps['size'];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TopAssets({ assets, logoSize = 40, onAssetClick, className }: TopAssetsProps) {
|
||||
return (
|
||||
<Container $itemsCount={assets.length} className={className}>
|
||||
{assets.map((asset, index) => {
|
||||
const displaySymbol = asset.symbol.toUpperCase();
|
||||
|
||||
return (
|
||||
<Item
|
||||
key={asset.id}
|
||||
$isLast={index === assets.length - 1}
|
||||
onClick={() => onAssetClick(asset)}
|
||||
>
|
||||
{asset.isNativeToken ? (
|
||||
<CoinLogo
|
||||
size={logoSize}
|
||||
// @ts-expect-error
|
||||
symbol={asset.symbol}
|
||||
type="tokenWithNetwork"
|
||||
/>
|
||||
) : (
|
||||
<AssetLogo
|
||||
size={logoSize}
|
||||
coingeckoId={asset.coingeckoId}
|
||||
symbol={asset.symbol}
|
||||
contractAddress={asset.contractAddress}
|
||||
placeholder={displaySymbol}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Text typographyStyle="hint" variant="default">
|
||||
{displaySymbol}
|
||||
</Text>
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
54
packages/product-components/src/constants/coins.ts
Normal file
54
packages/product-components/src/constants/coins.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
|
||||
// These coins are not supported in Suite, but exist in Trezor Connect
|
||||
export type LegacyNetworkSymbol =
|
||||
| 'eos'
|
||||
| 'nem'
|
||||
| 'xtz'
|
||||
| 'dash'
|
||||
| 'dgb'
|
||||
| 'nmc'
|
||||
| 'vtc'
|
||||
| 'btg'
|
||||
| 'xmr';
|
||||
|
||||
export const COINS: Record<NetworkSymbol | LegacyNetworkSymbol, string> = {
|
||||
ada: require('../images/coins/ada.svg'),
|
||||
arb: require('../images/coins/arb.svg'),
|
||||
avax: require('../images/coins/avax.svg'),
|
||||
base: require('../images/coins/base.svg'),
|
||||
bch: require('../images/coins/bch.svg'),
|
||||
bsc: require('../images/coins/bsc.svg'),
|
||||
btc: require('../images/coins/btc.svg'),
|
||||
btg: require('../images/coins/btg.svg'),
|
||||
dash: require('../images/coins/dash.svg'),
|
||||
dgb: require('../images/coins/dgb.svg'),
|
||||
doge: require('../images/coins/doge.svg'),
|
||||
dsol: require('../images/coins/dsol.svg'),
|
||||
eos: require('../images/coins/eos.svg'),
|
||||
etc: require('../images/coins/etc.svg'),
|
||||
eth: require('../images/coins/eth.svg'),
|
||||
ltc: require('../images/coins/ltc.svg'),
|
||||
op: require('../images/coins/op.svg'),
|
||||
pol: require('../images/coins/pol.svg'),
|
||||
nem: require('../images/coins/nem.svg'),
|
||||
nmc: require('../images/coins/nmc.svg'),
|
||||
regtest: require('../images/coins/btc_test.svg'),
|
||||
sol: require('../images/coins/sol.svg'),
|
||||
tada: require('../images/coins/tada.svg'),
|
||||
test: require('../images/coins/btc_test.svg'),
|
||||
thod: require('../images/coins/thod.svg'),
|
||||
tsep: require('../images/coins/tsep.svg'),
|
||||
txlm: require('../images/coins/txlm.svg'),
|
||||
txrp: require('../images/coins/txrp.svg'),
|
||||
vtc: require('../images/coins/vtc.svg'),
|
||||
xlm: require('../images/coins/xlm.svg'),
|
||||
xmr: require('../images/coins/xmr.svg'),
|
||||
xrp: require('../images/coins/xrp.svg'),
|
||||
xtz: require('../images/coins/xtz.svg'),
|
||||
zec: require('../images/coins/zec.svg'),
|
||||
};
|
||||
|
||||
export const isCoinSymbol = (
|
||||
coinSymbol: string,
|
||||
): coinSymbol is NetworkSymbol | LegacyNetworkSymbol => Object.hasOwn(COINS, coinSymbol);
|
||||
55
packages/product-components/src/constants/networks.ts
Normal file
55
packages/product-components/src/constants/networks.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
|
||||
// These coins are not supported in Suite, but exist in Trezor Connect
|
||||
export type LegacyNetworkSymbol =
|
||||
| 'eos'
|
||||
| 'nem'
|
||||
| 'xtz'
|
||||
| 'dash'
|
||||
| 'dgb'
|
||||
| 'nmc'
|
||||
| 'vtc'
|
||||
| 'btg'
|
||||
| 'xmr';
|
||||
|
||||
export const NETWORK_ICONS = {
|
||||
ada: require('../images/networks/ada.svg'),
|
||||
arb: require('../images/networks/arb.svg'),
|
||||
avax: require('../images/networks/avax.svg'),
|
||||
base: require('../images/networks/base.svg'),
|
||||
bch: require('../images/networks/bch.svg'),
|
||||
bsc: require('../images/networks/bsc.svg'),
|
||||
btc: require('../images/networks/btc.svg'),
|
||||
doge: require('../images/networks/doge.svg'),
|
||||
dsol: require('../images/networks/dsol.svg'),
|
||||
etc: require('../images/networks/etc.svg'),
|
||||
eth: require('../images/networks/eth.svg'),
|
||||
ltc: require('../images/networks/ltc.svg'),
|
||||
op: require('../images/networks/op.svg'),
|
||||
pol: require('../images/networks/pol.svg'),
|
||||
regtest: require('../images/networks/btc_test.svg'),
|
||||
sol: require('../images/networks/sol.svg'),
|
||||
tada: require('../images/networks/tada.svg'),
|
||||
test: require('../images/networks/btc_test.svg'),
|
||||
thod: require('../images/networks/thod.svg'),
|
||||
tsep: require('../images/networks/tsep.svg'),
|
||||
txlm: require('../images/networks/txlm.svg'),
|
||||
txrp: require('../images/networks/txrp.svg'),
|
||||
xrp: require('../images/networks/xrp.svg'),
|
||||
zec: require('../images/networks/zec.svg'),
|
||||
eos: require('../images/networks/eos.svg'),
|
||||
nem: require('../images/networks/nem.svg'),
|
||||
xlm: require('../images/networks/xlm.svg'),
|
||||
xtz: require('../images/networks/xtz.svg'),
|
||||
dash: require('../images/networks/dash.svg'),
|
||||
dgb: require('../images/networks/dgb.svg'),
|
||||
nmc: require('../images/networks/nmc.svg'),
|
||||
vtc: require('../images/networks/vtc.svg'),
|
||||
btg: require('../images/networks/btg.svg'),
|
||||
xmr: require('../images/networks/xmr.svg'),
|
||||
} as const satisfies Record<NetworkSymbol | LegacyNetworkSymbol, string>;
|
||||
|
||||
export const isNetworkSymbolWithIcon = (
|
||||
networkSymbol: string,
|
||||
): networkSymbol is NetworkSymbol | LegacyNetworkSymbol =>
|
||||
Object.hasOwn(NETWORK_ICONS, networkSymbol);
|
||||
@@ -12,7 +12,7 @@ export { RotateDeviceImage } from './components/RotateDeviceImage/RotateDeviceIm
|
||||
export { TrezorLogo } from './components/TrezorLogo/TrezorLogo';
|
||||
export { PasswordStrengthIndicator } from './components/PasswordStrengthIndicator/PasswordStrengthIndicator';
|
||||
export { CoinLogo } from './components/CoinLogo/CoinLogo';
|
||||
export { isCoinSymbol } from './components/CoinLogo/coins';
|
||||
export { isCoinSymbol } from './constants/coins';
|
||||
export { AssetShareIndicator } from './components/AssetShareIndicator/AssetShareIndicator';
|
||||
export * from './components/TokenIconSet/TokenIconSet';
|
||||
export { TokenTabs, type TokenTab } from './components/SelectAssetModal/TokenTabs';
|
||||
@@ -33,4 +33,6 @@ export { type ModelFor, type ColorsFor } from './components/DeviceAnimation/devi
|
||||
export { DeviceWithScene } from './components/DeviceWithScene/DeviceWithScene';
|
||||
export { getModelFrontColor, getLargeModelImagePath } from './utils/getModelFrontColor';
|
||||
export { DataAnalytics } from './components/DataAnalytics';
|
||||
export { AssetLogo, type AssetLogoProps } from './components/AssetLogo/AssetLogo';
|
||||
export * from './components/AssetLogo/AssetLogo';
|
||||
export { isNetworkSymbolWithIcon } from './constants/networks';
|
||||
export * from './components/TopAssets/TopAssets';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
import { selectAccounts } from '@suite-common/wallet-core';
|
||||
import {
|
||||
Card,
|
||||
@@ -14,8 +13,7 @@ import {
|
||||
Table,
|
||||
} from '@trezor/components';
|
||||
import { UiRequestSelectAccount } from '@trezor/connect';
|
||||
import { CoinLogo } from '@trezor/product-components';
|
||||
import { NETWORK_ICONS } from '@trezor/product-components/src/components/CoinLogo/networks';
|
||||
import { CoinLogo, isNetworkSymbolWithIcon } from '@trezor/product-components';
|
||||
import { spacings } from '@trezor/theme';
|
||||
|
||||
import { onReceiveAccount } from 'src/actions/suite/modalActions';
|
||||
@@ -132,10 +130,10 @@ export const SelectAccountModal = ({ data }: SelectAccountModalProps) => {
|
||||
>
|
||||
<Table.Cell>
|
||||
<Row gap={spacings.sm}>
|
||||
{symbol in NETWORK_ICONS && (
|
||||
{isNetworkSymbolWithIcon(symbol) && (
|
||||
<CoinLogo
|
||||
type="network"
|
||||
symbol={symbol as NetworkSymbol}
|
||||
symbol={symbol}
|
||||
size={24}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -11,8 +11,7 @@ import {
|
||||
} from '@suite-common/wallet-types';
|
||||
import { Card, Column, Divider, H4, InfoItem, Row, Text } from '@trezor/components';
|
||||
import { mapPaddingTypeToPadding } from '@trezor/components/src/components/Card/utils';
|
||||
import { AssetLogo, CoinLogo } from '@trezor/product-components';
|
||||
import { isCoinSymbol } from '@trezor/product-components/src/components/CoinLogo/coins';
|
||||
import { AssetLogo, CoinLogo, isCoinSymbol } from '@trezor/product-components';
|
||||
import { spacings } from '@trezor/theme';
|
||||
|
||||
import { BaseCurrencyValue } from 'src/components/suite/BaseCurrencyValue';
|
||||
|
||||
@@ -31,8 +31,7 @@ import {
|
||||
Text,
|
||||
} from '@trezor/components';
|
||||
import { ERRORS } from '@trezor/connect';
|
||||
import { AssetLogo, CoinLogo } from '@trezor/product-components';
|
||||
import { isCoinSymbol } from '@trezor/product-components/src/components/CoinLogo/coins';
|
||||
import { AssetLogo, CoinLogo, isCoinSymbol } from '@trezor/product-components';
|
||||
import { spacings } from '@trezor/theme';
|
||||
import { BigNumber } from '@trezor/utils';
|
||||
|
||||
|
||||
@@ -60,3 +60,4 @@ export * from './getIntegerInRangeFromString';
|
||||
export * from './safeBigIntStringify';
|
||||
export * from './union';
|
||||
export * from './isInt';
|
||||
export * from './number';
|
||||
|
||||
5
packages/utils/src/number.ts
Normal file
5
packages/utils/src/number.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function roundTo(value: number, precision = 2) {
|
||||
const x = 10 ** precision;
|
||||
|
||||
return Math.round(value * x) / x;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DeviceModelInternal } from '@trezor/device-utils';
|
||||
|
||||
import { getExplorerUrls } from './getExplorerUrls';
|
||||
import { Networks } from './types';
|
||||
import { NetworkFeature, Networks } from './types';
|
||||
|
||||
export const networks = {
|
||||
btc: {
|
||||
@@ -680,16 +680,18 @@ export const networks = {
|
||||
},
|
||||
} as const satisfies Networks;
|
||||
|
||||
export type NetworkDisplaySymbol = (typeof networks)[keyof typeof networks]['displaySymbol'];
|
||||
|
||||
type NetworksConfig = typeof networks;
|
||||
|
||||
export type StakingNetworkSymbol = {
|
||||
[S in keyof NetworksConfig]: 'staking' extends NetworksConfig[S]['features'][number]
|
||||
? S
|
||||
export type NetworkDisplaySymbol = NetworksConfig[keyof NetworksConfig]['displaySymbol'];
|
||||
|
||||
export type NetworkWithFeature<TFeature extends NetworkFeature> = {
|
||||
[S in keyof NetworksConfig]: TFeature extends NetworksConfig[S]['features'][number]
|
||||
? NetworksConfig[S]
|
||||
: never;
|
||||
}[keyof NetworksConfig];
|
||||
|
||||
export type StakingNetworkSymbol = NetworkWithFeature<'staking'>['symbol'];
|
||||
|
||||
export type StakingNetworkType = NetworksConfig[StakingNetworkSymbol]['networkType'];
|
||||
|
||||
export const [STAKING_SYMBOLS, STAKING_TYPES] = (
|
||||
|
||||
@@ -75,7 +75,7 @@ export const getNetworkFeatures = (symbol: NetworkSymbol): NetworkFeature[] =>
|
||||
export const getCoingeckoId = (symbol: NetworkSymbol) => networks[symbol].coingeckoId;
|
||||
|
||||
export const isNetworkSymbol = (symbol: NetworkSymbolExtended): symbol is NetworkSymbol =>
|
||||
Object.prototype.hasOwnProperty.call(networks, symbol);
|
||||
Object.hasOwn(networks, symbol);
|
||||
|
||||
/**
|
||||
* Get network object by symbol as a generic `Network` type.
|
||||
|
||||
Reference in New Issue
Block a user