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:
Jiří Čermák
2025-11-25 15:06:03 +01:00
committed by Jiří Čermák
parent 0c91b86a58
commit 9409fbbeb2
25 changed files with 438 additions and 145 deletions

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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',
},
},
},
};

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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}`}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,
},
};

View File

@@ -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}
/>
);
}

View File

@@ -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;

View File

@@ -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);
},
},
};

View File

@@ -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>
);
}

View 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);

View 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);

View File

@@ -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';

View File

@@ -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}
/>
)}

View File

@@ -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';

View File

@@ -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';

View File

@@ -60,3 +60,4 @@ export * from './getIntegerInRangeFromString';
export * from './safeBigIntStringify';
export * from './union';
export * from './isInt';
export * from './number';

View File

@@ -0,0 +1,5 @@
export function roundTo(value: number, precision = 2) {
const x = 10 ** precision;
return Math.round(value * x) / x;
}

View File

@@ -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] = (

View File

@@ -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.

View File

@@ -13706,6 +13706,7 @@ __metadata:
react-svg: "npm:16.3.0"
react-use: "npm:^17.6.0"
storybook: "npm:^9.0.16"
stream-browserify: "npm:^3.0.0"
styled-components: "npm:^6.1.19"
stylelint: "npm:^16.14.1"
stylelint-config-standard: "npm:^38.0.0"