Skip to content

Commit 11b799f

Browse files
committed
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`
1 parent c02dcaa commit 11b799f

File tree

25 files changed

+436
-145
lines changed

25 files changed

+436
-145
lines changed

packages/components/.storybook/main.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ module.exports = {
4949
],
5050
});
5151

52+
// Add polyfill for Node.js 'stream' module (required by jws)
53+
config.resolve.fallback = {
54+
...config.resolve.fallback,
55+
stream: require.resolve('stream-browserify'),
56+
};
57+
5258
// NOTE: remove the previous loaders from handling the svgs
5359
const imageRule = config.module.rules.find(rule => rule?.['test']?.test('.svg'));
5460
if (imageRule) {

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"babel-loader": "^10.0.0",
5959
"postcss-styled-syntax": "^0.7.1",
6060
"storybook": "^9.0.16",
61+
"stream-browserify": "^3.0.0",
6162
"stylelint": "^16.14.1",
6263
"stylelint-config-standard": "^38.0.0",
6364
"typescript-styled-plugin": "^0.18.3"

packages/product-components/src/components/AssetLogo/AssetLogo.stories.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,26 @@ export const AssetLogo: StoryObj<AssetLogoProps> = {
2424
contractAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
2525
shouldTryToFetch: true,
2626
placeholder: 'USDC',
27+
showNetworkIcon: false,
2728
...getFramePropsStory(allowedAssetLogoFrameProps).args,
2829
},
2930
argTypes: {
31+
...getFramePropsStory(allowedAssetLogoFrameProps).argTypes,
3032
size: {
3133
options: allowedAssetLogoSizes,
3234
control: {
3335
type: 'select',
3436
},
3537
},
36-
...getFramePropsStory(allowedAssetLogoFrameProps).argTypes,
38+
showNetworkIcon: {
39+
control: {
40+
type: 'boolean',
41+
},
42+
},
43+
shouldTryToFetch: {
44+
control: {
45+
type: 'boolean',
46+
},
47+
},
3748
},
3849
};

packages/product-components/src/components/AssetLogo/AssetLogo.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@ import { useEffect, useMemo, useState } from 'react';
33
import styled from 'styled-components';
44

55
import {
6+
NetworkSymbol,
67
type NetworkSymbolExtended,
78
getNetwork,
89
getNetworkByCoingeckoId,
10+
getNetworkFeatures,
911
isNetworkSymbol,
1012
} from '@suite-common/wallet-config';
1113
import { getAssetLogoContractAddresses } from '@suite-common/wallet-utils';
1214
import { getAssetLogoUrl } from '@trezor/asset-utils';
1315
import {
1416
ElevationUp,
15-
type FrameProps,
16-
type FramePropsKeys,
17-
type TransientProps,
17+
FrameProps,
18+
FramePropsKeys,
19+
TransientProps,
1820
pickAndPrepareFrameProps,
1921
useElevation,
2022
withFrameProps,
2123
} from '@trezor/components';
2224
import { Elevation, borders, mapElevationToBackground, mapElevationToBorder } from '@trezor/theme';
2325

2426
import { AssetInitials } from './AssetInitials';
27+
import { LegacyNetworkSymbol, isNetworkSymbolWithIcon } from '../../constants/networks';
28+
import { NetworkIcon } from '../NetworkIcon/NetworkIcon';
2529

2630
export const allowedAssetLogoSizes = [20, 24, 32, 40] as const;
2731
type AssetLogoSize = (typeof allowedAssetLogoSizes)[number];
@@ -38,6 +42,7 @@ export type AssetLogoProps = AllowedFrameProps & {
3842
placeholderWithTooltip?: boolean;
3943
placeholder: string;
4044
'data-testid'?: string;
45+
showNetworkIcon?: boolean;
4146
};
4247

4348
type LogoCandidate = { address: string; src: string; srcSet: string };
@@ -46,6 +51,7 @@ const Container = styled.div<TransientProps<AllowedFrameProps> & { $size: number
4651
${({ $size }) => `
4752
width: ${$size}px;
4853
height: ${$size}px;
54+
position: relative;
4955
`}
5056
${withFrameProps}
5157
`;
@@ -58,6 +64,13 @@ const Logo = styled.img<{ $size: number; $elevation: Elevation }>`
5864
background-color: ${mapElevationToBackground};
5965
`;
6066

67+
const StyledNetworkIcon = styled(NetworkIcon)`
68+
position: absolute;
69+
bottom: 0;
70+
right: 0;
71+
line-height: 0;
72+
`;
73+
6174
interface LogoProps extends React.ImgHTMLAttributes<HTMLImageElement> {
6275
$size: number;
6376
}
@@ -78,6 +91,19 @@ const makeCacheKey = (coingeckoId: string, addressesKey: string) =>
7891

7992
const makeAddressKey = (coingeckoId: string, address: string) => `${coingeckoId}::${address}`;
8093

94+
export function shouldShowNetworkIcon(
95+
networkSymbol?: NetworkSymbolExtended,
96+
contractAddress?: string | null,
97+
) {
98+
return (
99+
networkSymbol &&
100+
isNetworkSymbolWithIcon(networkSymbol) &&
101+
isNetworkSymbol(networkSymbol) &&
102+
Boolean(contractAddress) &&
103+
getNetworkFeatures(networkSymbol).includes('tokens')
104+
);
105+
}
106+
81107
export const getCoingeckoIdAndContractAddressIncludesNativeTokens = (
82108
coingeckoId: string,
83109
contractAddress: string[] | undefined,
@@ -106,10 +132,11 @@ export const AssetLogo = ({
106132
size,
107133
coingeckoId,
108134
symbol,
109-
contractAddress,
135+
contractAddress = null,
110136
shouldTryToFetch = true,
111137
placeholder,
112138
placeholderWithTooltip = true,
139+
showNetworkIcon = false,
113140
'data-testid': dataTest,
114141
...rest
115142
}: AssetLogoProps) => {
@@ -238,6 +265,12 @@ export const AssetLogo = ({
238265
onLoad={handleOnLoad}
239266
onError={handleLoadError}
240267
/>
268+
{showNetworkIcon && (
269+
<StyledNetworkIcon
270+
networkSymbol={symbol as NetworkSymbol | LegacyNetworkSymbol}
271+
size={size * 0.375}
272+
/>
273+
)}
241274
</ElevationUp>
242275
)}
243276
</Container>

packages/product-components/src/components/CoinLogo/CoinLogo.stories.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Meta, StoryObj } from '@storybook/react';
22
import styled from 'styled-components';
33

4-
import { COIN_LOGO_TYPE, CoinLogoProps } from './CoinLogo';
5-
import { COINS } from './coins';
6-
import { CoinLogo as CoinLogoComponent } from '../../index';
4+
import { COIN_LOGO_TYPE, CoinLogo as CoinLogoComponent, CoinLogoProps } from './CoinLogo';
5+
import { COINS } from '../../constants/coins';
76

87
const Center = styled.div`
98
display: flex;

packages/product-components/src/components/CoinLogo/CoinLogo.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import styled from 'styled-components';
55

66
import { NetworkSymbol, getNetworkOptional } from '@suite-common/wallet-config';
77
import { borders } from '@trezor/theme';
8+
import { roundTo } from '@trezor/utils';
89

9-
import { COINS, LegacyNetworkSymbol } from './coins';
10-
import { NETWORK_ICONS } from './networks';
10+
import { COINS, LegacyNetworkSymbol } from '../../constants/coins';
11+
import { NETWORK_ICONS } from '../../constants/networks';
12+
import { NetworkIcon } from '../NetworkIcon/NetworkIcon';
1113

1214
export const COIN_LOGO_TYPE = ['token', 'network', 'tokenWithNetwork'] as const;
1315
export type CoinLogoType = (typeof COIN_LOGO_TYPE)[number];
1416

1517
const DEFAULT_SIZE = 32;
1618

17-
const getSize = (size?: number, border = 0, divisor = 1) =>
18-
`${(size ?? DEFAULT_SIZE) / divisor + border * 2}px`;
19+
const getSize = (size: number = DEFAULT_SIZE, border = 0, divisor = 1) =>
20+
`${roundTo(size / divisor + border * 2, 2)}px`;
1921

2022
export interface CoinLogoProps extends ImgHTMLAttributes<HTMLImageElement> {
2123
symbol: NetworkSymbol | LegacyNetworkSymbol;
@@ -98,16 +100,7 @@ export const CoinLogo = ({
98100
}}
99101
loading={() => <span className="loading" />}
100102
/>
101-
{badge && symbolSrc != null && (
102-
<ReactSVG
103-
src={badge}
104-
beforeInjection={svg => {
105-
svg.setAttribute('width', getSize(size, 0, 3));
106-
svg.setAttribute('height', getSize(size, 0, 3));
107-
}}
108-
loading={() => <span className="loading" />}
109-
/>
110-
)}
103+
{badge && <NetworkIcon networkSymbol={symbol} size={roundTo(size / 3, 2)} />}
111104
</SvgWrapper>
112105
);
113106
};

packages/product-components/src/components/CoinLogo/coinLogos.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import styled from 'styled-components';
44
import { networksCollection } from '@suite-common/wallet-config';
55
import { StoryColumn } from '@trezor/components';
66

7-
import { COINS, isCoinSymbol } from './coins';
8-
import { NETWORK_ICONS, isNetworkSymbol } from './networks';
9-
import { CoinLogo } from '../../index';
7+
import { CoinLogo } from './CoinLogo';
8+
import { COINS, isCoinSymbol } from '../../constants/coins';
9+
import { NETWORK_ICONS, isNetworkSymbolWithIcon } from '../../constants/networks';
1010

1111
const Heading = styled.h2`
1212
margin-bottom: 2px;
@@ -67,7 +67,7 @@ export const All: StoryObj = {
6767
{Object.keys(NETWORK_ICONS).map(network => (
6868
<Icon key={network}>
6969
<CoinName>{network}</CoinName>
70-
{isNetworkSymbol(network) && (
70+
{isNetworkSymbolWithIcon(network) && (
7171
<CoinLogo
7272
symbol={network}
7373
data-testid={`network-${network}`}

packages/product-components/src/components/CoinLogo/coins.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

packages/product-components/src/components/CoinLogo/networks.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import styled from 'styled-components';
3+
4+
import { Column, H2, Paragraph, StoryColumn } from '@trezor/components';
5+
6+
import { NetworkIcon } from './NetworkIcon';
7+
import { NETWORK_ICONS, isNetworkSymbolWithIcon } from '../../constants/networks';
8+
9+
const WrapperIcons = styled.div`
10+
display: grid;
11+
width: 100%;
12+
grid-gap: 5px;
13+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
14+
`;
15+
16+
const meta: Meta<typeof NetworkIcon> = {
17+
title: 'NetworkIcon',
18+
component: NetworkIcon,
19+
};
20+
export default meta;
21+
22+
export const All: StoryObj = {
23+
render: () => (
24+
<StoryColumn minWidth={700}>
25+
<H2 margin={{ bottom: 2 }}>Network Icons</H2>
26+
<Paragraph>All available network SVG icons</Paragraph>
27+
<WrapperIcons>
28+
{Object.keys(NETWORK_ICONS).map(networkSymbol => (
29+
<Column
30+
key={networkSymbol}
31+
minHeight={100}
32+
justifyContent="center"
33+
alignItems="center"
34+
>
35+
<Paragraph margin={{ bottom: 8 }} variant="tertiary">
36+
{networkSymbol}
37+
</Paragraph>
38+
{isNetworkSymbolWithIcon(networkSymbol) && (
39+
<NetworkIcon networkSymbol={networkSymbol} size={32} />
40+
)}
41+
</Column>
42+
))}
43+
</WrapperIcons>
44+
</StoryColumn>
45+
),
46+
};
47+
48+
export const Single: StoryObj<typeof NetworkIcon> = {
49+
args: {
50+
networkSymbol: 'btc',
51+
size: 32,
52+
},
53+
};

0 commit comments

Comments
 (0)