Skip to content

Commit 7236697

Browse files
fix: nft list getting cut off (#1829)
- Closes #1779 - Closes FE-1328 # Summary - Added a general loading state for the entire NFT list for when the account data is still loading - Fixed NFT image collapsible loading state not matching the sizes of NFT images later displayed - Fixed NFT Image list getting cut off # Checklist - [x] I've added error handling for all actions/requests, and verified how this error will show on UI. (or there was no error handling) - [x] I've reviewed all the copy changed/added in this PR, using AI if needed. (or there was no copy changes) - [x] I've included the reference to the issues being closed from Github and/or Linear (or there was no issues) - [x] I've changed the Docs to reflect my changes (or it was not needed) - [x] I've put docs links where it may be helpful (or it was not needed) - [x] I checked the resulting UI both in Light and Dark mode (or no UI changes were made) - [x] I **reviewed** the **entire PR** myself (preferably, on GH UI) --------- Co-authored-by: Luiz Gomes <8636507+LuizAsFight@users.noreply.github.com> Co-authored-by: LuizAsFight <felipebolsonigomes@gmail.com>
1 parent e35cee4 commit 7236697

File tree

9 files changed

+173
-66
lines changed

9 files changed

+173
-66
lines changed

.changeset/rotten-toes-glow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"fuels-wallet": patch
3+
---
4+
5+
fix: nft list getting cut off

packages/app/src/systems/Account/components/BalanceNFTs/BalanceNFTs.tsx

+50-20
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import { cssObj } from '@fuel-ui/css';
2-
import { Accordion, Badge, Box, Copyable, VStack } from '@fuel-ui/react';
2+
import { Accordion, Badge, Box, Copyable } from '@fuel-ui/react';
33
import type { CoinAsset } from '@fuel-wallet/types';
4-
import { useMemo } from 'react';
4+
import { memo, useMemo } from 'react';
5+
import { NFTImageLoading } from '~/systems/Account/components/BalanceNFTs/NFTImageLoading';
6+
import { AssetList } from '~/systems/Asset';
57
import { AssetListEmpty } from '~/systems/Asset/components/AssetList/AssetListEmpty';
68
import { shortAddress } from '~/systems/Core';
79
import { NFTImage } from './NFTImage';
10+
import { NFTListItemLoading } from './NFTListItemLoading';
11+
import { NFTTitleLoading } from './NFTTitleLoading';
812
import {
913
UNKNOWN_COLLECTION_TITLE,
1014
groupNFTsByCollection,
1115
} from './groupNFTsByCollection';
1216

1317
interface BalanceNFTsProps {
1418
balances: CoinAsset[] | undefined;
19+
isLoading?: boolean;
1520
}
1621

17-
export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
22+
const EMPTY_ARRAY: CoinAsset[] = [];
23+
24+
export const BalanceNFTs = ({
25+
balances = EMPTY_ARRAY,
26+
isLoading,
27+
}: BalanceNFTsProps) => {
1828
const { collections, defaultValue } = useMemo(() => {
1929
const collections = groupNFTsByCollection(balances);
2030
const defaultValue = collections
@@ -27,21 +37,31 @@ export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
2737
};
2838
}, [balances]);
2939

30-
if (collections.length === 0) {
31-
return (
32-
<AssetListEmpty
33-
text="You don't have any NFTs"
34-
supportText="To add NFTs, simply send them to your Fuel address."
35-
hideFaucet
36-
/>
37-
);
38-
}
39-
4040
return (
4141
<Box css={styles.root}>
42-
<Accordion type="multiple" defaultValue={defaultValue}>
43-
{collections.map((collection) => {
44-
return (
42+
{isLoading && !collections.length && (
43+
<Box css={styles.titleLoading}>
44+
<NFTTitleLoading />
45+
<Box css={styles.gridLoading}>
46+
<NFTListItemLoading />
47+
<NFTListItemLoading />
48+
<NFTListItemLoading />
49+
<NFTListItemLoading />
50+
<NFTListItemLoading />
51+
<NFTListItemLoading />
52+
</Box>
53+
</Box>
54+
)}
55+
{!isLoading && !collections?.length && (
56+
<AssetListEmpty
57+
text="You don't have any NFTs"
58+
supportText="To add NFTs, simply send them to your Fuel address."
59+
hideFaucet
60+
/>
61+
)}
62+
{!!collections.length && (
63+
<Accordion type="multiple" defaultValue={defaultValue}>
64+
{collections.map((collection) => (
4565
<Accordion.Item key={collection.name} value={collection.name}>
4666
<Accordion.Trigger>
4767
<Badge variant="ghost" color="gray" as="span">
@@ -64,9 +84,9 @@ export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
6484
</Box>
6585
</Accordion.Content>
6686
</Accordion.Item>
67-
);
68-
})}
69-
</Accordion>
87+
))}
88+
</Accordion>
89+
)}
7090
</Box>
7191
);
7292
};
@@ -102,7 +122,6 @@ const styles = {
102122

103123
svg: {
104124
width: '$3',
105-
height: '$3',
106125
},
107126
},
108127
'.fuel_Accordion-content': {
@@ -128,6 +147,17 @@ const styles = {
128147
gridTemplateColumns: 'repeat(3, 1fr)',
129148
gap: '$3',
130149
}),
150+
gridLoading: cssObj({
151+
display: 'grid',
152+
gridTemplateColumns: 'repeat(3, 1fr)',
153+
gap: '$3',
154+
marginTop: '14px',
155+
paddingLeft: '$5',
156+
paddingRight: '$2',
157+
}),
158+
titleLoading: cssObj({
159+
marginTop: '16px',
160+
}),
131161
name: cssObj({
132162
marginTop: '$1',
133163
gap: '$0',
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import { cssObj } from '@fuel-ui/css';
2-
import { Box, ContentLoader, Icon } from '@fuel-ui/react';
3-
import { useEffect, useRef, useState } from 'react';
2+
import { Box, ContentLoader, Icon, Image } from '@fuel-ui/react';
3+
import { memo, useEffect, useRef, useState } from 'react';
4+
import { NFTImageLoading } from '~/systems/Account/components/BalanceNFTs/NFTImageLoading';
45
import { shortAddress } from '~/systems/Core';
56

67
interface NFTImageProps {
78
assetId: string;
89
image: string | undefined;
910
}
1011

11-
export const NFTImage = ({ assetId, image }: NFTImageProps) => {
12+
function Empty() {
13+
return (
14+
<Box css={styles.noImage}>
15+
<Icon icon={Icon.is('FileOff')} />
16+
</Box>
17+
);
18+
}
19+
20+
const _NFTImage = ({ assetId, image }: NFTImageProps) => {
1221
const imgRef = useRef<HTMLImageElement>(null);
1322

1423
const [fallback, setFallback] = useState(false);
@@ -25,31 +34,22 @@ export const NFTImage = ({ assetId, image }: NFTImageProps) => {
2534
}
2635
}, []);
2736

28-
if (image && !fallback) {
29-
return (
30-
<Box css={styles.item}>
31-
{isLoading && (
32-
<ContentLoader width="100%" height="100%" viewBox="0 0 22 22">
33-
<rect x="0" y="0" rx="0" ry="0" width="22" height="22" />
34-
</ContentLoader>
35-
)}
36-
<img
37+
if (!image || !!fallback) return <Empty />;
38+
39+
return (
40+
<Box css={styles.item}>
41+
{image && !fallback && isLoading && <NFTImageLoading />}
42+
{image && !fallback && (
43+
<Image
3744
ref={imgRef}
3845
src={image}
3946
alt={shortAddress(assetId)}
4047
data-loading={isLoading}
48+
style={cssObj({ visibility: isLoading ? 'hidden' : 'visible' })}
4149
onLoad={() => setLoading(false)}
42-
onError={() => {
43-
setFallback(true);
44-
}}
50+
onError={() => setFallback(true)}
4551
/>
46-
</Box>
47-
);
48-
}
49-
50-
return (
51-
<Box css={styles.noImage}>
52-
<Icon icon={Icon.is('FileOff')} />
52+
)}
5353
</Box>
5454
);
5555
};
@@ -59,7 +59,7 @@ const styles = {
5959
aspectRatio: '1 / 1',
6060
borderRadius: '12px',
6161
overflow: 'hidden',
62-
62+
minHeight: '89px',
6363
img: {
6464
width: '100%',
6565
objectFit: 'cover',
@@ -79,3 +79,7 @@ const styles = {
7979
alignItems: 'center',
8080
}),
8181
};
82+
83+
export const NFTImage = memo(_NFTImage, (a, b) => {
84+
return a.assetId === b.assetId && a.image === b.image;
85+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { cssObj } from '@fuel-ui/css';
2+
import { Box, ContentLoader } from '@fuel-ui/react';
3+
4+
export function NFTImageLoading({ size = 89 }: { size?: number }) {
5+
return (
6+
<Box
7+
css={cssObj({
8+
overflow: 'hidden',
9+
borderRadius: '10px',
10+
width: `${size}px`,
11+
height: `${size}px`,
12+
})}
13+
>
14+
<ContentLoader width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
15+
<rect x="0" y="0" rx="0" ry="0" width={size} height={size} />
16+
</ContentLoader>
17+
</Box>
18+
);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { cssObj } from '@fuel-ui/css';
2+
import { Box } from '@fuel-ui/react';
3+
import { NFTImageLoading } from './NFTImageLoading';
4+
import { NFTTitleLoading } from './NFTTitleLoading';
5+
6+
export function NFTListItemLoading() {
7+
return (
8+
<Box
9+
css={cssObj({
10+
display: 'flex',
11+
flexDirection: 'column',
12+
gap: '8px',
13+
})}
14+
>
15+
<NFTImageLoading />
16+
<NFTTitleLoading />
17+
</Box>
18+
);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { cssObj } from '@fuel-ui/css';
2+
import { Box, ContentLoader } from '@fuel-ui/react';
3+
4+
export function NFTTitleLoading({ height = 18 }: { height?: number }) {
5+
return (
6+
<Box
7+
css={cssObj({
8+
overflow: 'hidden',
9+
borderRadius: '6px',
10+
width: '89px',
11+
height: `${height}px`,
12+
})}
13+
>
14+
<ContentLoader
15+
width="89px"
16+
height={height ?? '100%'}
17+
viewBox={`0 0 89 ${height}`}
18+
>
19+
<rect x="0" y="0" rx="0" ry="0" width="89" height="18" />
20+
</ContentLoader>
21+
</Box>
22+
);
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BALANCE_NFTS_TAB_HEIGHT = 244;

packages/app/src/systems/Asset/components/AssetList/AssetListEmpty.tsx

+26-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { cssObj } from '@fuel-ui/css';
2-
import { Button, Card, Heading, Icon, Text } from '@fuel-ui/react';
2+
import { Box, Button, Card, Heading, Icon, Text } from '@fuel-ui/react';
3+
import { BALANCE_NFTS_TAB_HEIGHT } from '~/systems/Account/components/BalanceNFTs/constants';
34
import { useFundWallet } from '~/systems/FundWallet';
45

56
export type AssetListEmptyProps = {
@@ -17,29 +18,34 @@ export function AssetListEmpty({
1718
const showFund = hasFaucet || hasBridge;
1819

1920
return (
20-
<Card css={styles.empty}>
21-
<Card.Body>
22-
{!!text && <Heading as="h5">{text}</Heading>}
23-
{!!supportText && <Text fontSize="sm">{supportText}</Text>}
24-
{showFund && !hideFaucet && (
25-
/**
26-
* TODO: need to add right faucet icon on @fuel-ui
27-
*/
28-
<Button
29-
size="sm"
30-
intent="primary"
31-
leftIcon={hasFaucet ? Icon.is('Wand') : Icon.is('Coins')}
32-
onPress={open}
33-
>
34-
{hasFaucet ? 'Faucet' : 'Bridge to Fuel'}
35-
</Button>
36-
)}
37-
</Card.Body>
38-
</Card>
21+
<Box css={styles.container}>
22+
<Card css={styles.empty}>
23+
<Card.Body>
24+
{!!text && <Heading as="h5">{text}</Heading>}
25+
{!!supportText && <Text fontSize="sm">{supportText}</Text>}
26+
{showFund && !hideFaucet && (
27+
/**
28+
* TODO: need to add right faucet icon on @fuel-ui
29+
*/
30+
<Button
31+
size="sm"
32+
intent="primary"
33+
leftIcon={hasFaucet ? Icon.is('Wand') : Icon.is('Coins')}
34+
onPress={open}
35+
>
36+
{hasFaucet ? 'Faucet' : 'Bridge to Fuel'}
37+
</Button>
38+
)}
39+
</Card.Body>
40+
</Card>
41+
</Box>
3942
);
4043
}
4144

4245
const styles = {
46+
container: cssObj({
47+
minHeight: BALANCE_NFTS_TAB_HEIGHT,
48+
}),
4349
empty: cssObj({
4450
h5: {
4551
margin: 0,

packages/app/src/systems/Home/pages/Home/Home.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useBalanceVisibility } from '~/systems/Core/hooks/useVisibility';
77

88
import { BalanceAssets } from '~/systems/Account/components/BalanceAssets/BalanceAssets';
99
import { BalanceNFTs } from '~/systems/Account/components/BalanceNFTs/BalanceNFTs';
10+
import { BALANCE_NFTS_TAB_HEIGHT } from '~/systems/Account/components/BalanceNFTs/constants';
1011
import { QuickAccountConnect } from '~/systems/Account/components/QuickAccountConnect/QuickAccountConnect';
1112
import { HomeActions } from '../../components';
1213

@@ -52,7 +53,7 @@ export function Home() {
5253
<BalanceAssets balances={account?.balances} isLoading={isLoading} />
5354
</Tabs.Content>
5455
<Tabs.Content value="nft" css={styles.assetsList}>
55-
<BalanceNFTs balances={account?.balances} />
56+
<BalanceNFTs balances={account?.balances} isLoading={isLoading} />
5657
</Tabs.Content>
5758
</Tabs>
5859
</Layout.Content>
@@ -69,8 +70,7 @@ const styles = {
6970
},
7071
}),
7172
assetsList: cssObj({
72-
maxHeight: 200,
73-
paddingBottom: '$4',
73+
height: BALANCE_NFTS_TAB_HEIGHT,
7474
...scrollable(),
7575
overflowY: 'scroll !important',
7676
}),

0 commit comments

Comments
 (0)