Skip to content

Commit

Permalink
fix: nft list getting cut off (#1829)
Browse files Browse the repository at this point in the history
- 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>
  • Loading branch information
3 people authored Feb 12, 2025
1 parent e35cee4 commit 7236697
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-toes-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

fix: nft list getting cut off
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { cssObj } from '@fuel-ui/css';
import { Accordion, Badge, Box, Copyable, VStack } from '@fuel-ui/react';
import { Accordion, Badge, Box, Copyable } from '@fuel-ui/react';
import type { CoinAsset } from '@fuel-wallet/types';
import { useMemo } from 'react';
import { memo, useMemo } from 'react';
import { NFTImageLoading } from '~/systems/Account/components/BalanceNFTs/NFTImageLoading';
import { AssetList } from '~/systems/Asset';
import { AssetListEmpty } from '~/systems/Asset/components/AssetList/AssetListEmpty';
import { shortAddress } from '~/systems/Core';
import { NFTImage } from './NFTImage';
import { NFTListItemLoading } from './NFTListItemLoading';
import { NFTTitleLoading } from './NFTTitleLoading';
import {
UNKNOWN_COLLECTION_TITLE,
groupNFTsByCollection,
} from './groupNFTsByCollection';

interface BalanceNFTsProps {
balances: CoinAsset[] | undefined;
isLoading?: boolean;
}

export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
const EMPTY_ARRAY: CoinAsset[] = [];

export const BalanceNFTs = ({
balances = EMPTY_ARRAY,
isLoading,
}: BalanceNFTsProps) => {
const { collections, defaultValue } = useMemo(() => {
const collections = groupNFTsByCollection(balances);
const defaultValue = collections
Expand All @@ -27,21 +37,31 @@ export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
};
}, [balances]);

if (collections.length === 0) {
return (
<AssetListEmpty
text="You don't have any NFTs"
supportText="To add NFTs, simply send them to your Fuel address."
hideFaucet
/>
);
}

return (
<Box css={styles.root}>
<Accordion type="multiple" defaultValue={defaultValue}>
{collections.map((collection) => {
return (
{isLoading && !collections.length && (
<Box css={styles.titleLoading}>
<NFTTitleLoading />
<Box css={styles.gridLoading}>
<NFTListItemLoading />
<NFTListItemLoading />
<NFTListItemLoading />
<NFTListItemLoading />
<NFTListItemLoading />
<NFTListItemLoading />
</Box>
</Box>
)}
{!isLoading && !collections?.length && (
<AssetListEmpty
text="You don't have any NFTs"
supportText="To add NFTs, simply send them to your Fuel address."
hideFaucet
/>
)}
{!!collections.length && (
<Accordion type="multiple" defaultValue={defaultValue}>
{collections.map((collection) => (
<Accordion.Item key={collection.name} value={collection.name}>
<Accordion.Trigger>
<Badge variant="ghost" color="gray" as="span">
Expand All @@ -64,9 +84,9 @@ export const BalanceNFTs = ({ balances = [] }: BalanceNFTsProps) => {
</Box>
</Accordion.Content>
</Accordion.Item>
);
})}
</Accordion>
))}
</Accordion>
)}
</Box>
);
};
Expand Down Expand Up @@ -102,7 +122,6 @@ const styles = {

svg: {
width: '$3',
height: '$3',
},
},
'.fuel_Accordion-content': {
Expand All @@ -128,6 +147,17 @@ const styles = {
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '$3',
}),
gridLoading: cssObj({
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '$3',
marginTop: '14px',
paddingLeft: '$5',
paddingRight: '$2',
}),
titleLoading: cssObj({
marginTop: '16px',
}),
name: cssObj({
marginTop: '$1',
gap: '$0',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { cssObj } from '@fuel-ui/css';
import { Box, ContentLoader, Icon } from '@fuel-ui/react';
import { useEffect, useRef, useState } from 'react';
import { Box, ContentLoader, Icon, Image } from '@fuel-ui/react';
import { memo, useEffect, useRef, useState } from 'react';
import { NFTImageLoading } from '~/systems/Account/components/BalanceNFTs/NFTImageLoading';
import { shortAddress } from '~/systems/Core';

interface NFTImageProps {
assetId: string;
image: string | undefined;
}

export const NFTImage = ({ assetId, image }: NFTImageProps) => {
function Empty() {
return (
<Box css={styles.noImage}>
<Icon icon={Icon.is('FileOff')} />
</Box>
);
}

const _NFTImage = ({ assetId, image }: NFTImageProps) => {
const imgRef = useRef<HTMLImageElement>(null);

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

if (image && !fallback) {
return (
<Box css={styles.item}>
{isLoading && (
<ContentLoader width="100%" height="100%" viewBox="0 0 22 22">
<rect x="0" y="0" rx="0" ry="0" width="22" height="22" />
</ContentLoader>
)}
<img
if (!image || !!fallback) return <Empty />;

return (
<Box css={styles.item}>
{image && !fallback && isLoading && <NFTImageLoading />}
{image && !fallback && (
<Image
ref={imgRef}
src={image}
alt={shortAddress(assetId)}
data-loading={isLoading}
style={cssObj({ visibility: isLoading ? 'hidden' : 'visible' })}
onLoad={() => setLoading(false)}
onError={() => {
setFallback(true);
}}
onError={() => setFallback(true)}
/>
</Box>
);
}

return (
<Box css={styles.noImage}>
<Icon icon={Icon.is('FileOff')} />
)}
</Box>
);
};
Expand All @@ -59,7 +59,7 @@ const styles = {
aspectRatio: '1 / 1',
borderRadius: '12px',
overflow: 'hidden',

minHeight: '89px',
img: {
width: '100%',
objectFit: 'cover',
Expand All @@ -79,3 +79,7 @@ const styles = {
alignItems: 'center',
}),
};

export const NFTImage = memo(_NFTImage, (a, b) => {
return a.assetId === b.assetId && a.image === b.image;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cssObj } from '@fuel-ui/css';
import { Box, ContentLoader } from '@fuel-ui/react';

export function NFTImageLoading({ size = 89 }: { size?: number }) {
return (
<Box
css={cssObj({
overflow: 'hidden',
borderRadius: '10px',
width: `${size}px`,
height: `${size}px`,
})}
>
<ContentLoader width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
<rect x="0" y="0" rx="0" ry="0" width={size} height={size} />
</ContentLoader>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cssObj } from '@fuel-ui/css';
import { Box } from '@fuel-ui/react';
import { NFTImageLoading } from './NFTImageLoading';
import { NFTTitleLoading } from './NFTTitleLoading';

export function NFTListItemLoading() {
return (
<Box
css={cssObj({
display: 'flex',
flexDirection: 'column',
gap: '8px',
})}
>
<NFTImageLoading />
<NFTTitleLoading />
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cssObj } from '@fuel-ui/css';
import { Box, ContentLoader } from '@fuel-ui/react';

export function NFTTitleLoading({ height = 18 }: { height?: number }) {
return (
<Box
css={cssObj({
overflow: 'hidden',
borderRadius: '6px',
width: '89px',
height: `${height}px`,
})}
>
<ContentLoader
width="89px"
height={height ?? '100%'}
viewBox={`0 0 89 ${height}`}
>
<rect x="0" y="0" rx="0" ry="0" width="89" height="18" />
</ContentLoader>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BALANCE_NFTS_TAB_HEIGHT = 244;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cssObj } from '@fuel-ui/css';
import { Button, Card, Heading, Icon, Text } from '@fuel-ui/react';
import { Box, Button, Card, Heading, Icon, Text } from '@fuel-ui/react';
import { BALANCE_NFTS_TAB_HEIGHT } from '~/systems/Account/components/BalanceNFTs/constants';
import { useFundWallet } from '~/systems/FundWallet';

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

return (
<Card css={styles.empty}>
<Card.Body>
{!!text && <Heading as="h5">{text}</Heading>}
{!!supportText && <Text fontSize="sm">{supportText}</Text>}
{showFund && !hideFaucet && (
/**
* TODO: need to add right faucet icon on @fuel-ui
*/
<Button
size="sm"
intent="primary"
leftIcon={hasFaucet ? Icon.is('Wand') : Icon.is('Coins')}
onPress={open}
>
{hasFaucet ? 'Faucet' : 'Bridge to Fuel'}
</Button>
)}
</Card.Body>
</Card>
<Box css={styles.container}>
<Card css={styles.empty}>
<Card.Body>
{!!text && <Heading as="h5">{text}</Heading>}
{!!supportText && <Text fontSize="sm">{supportText}</Text>}
{showFund && !hideFaucet && (
/**
* TODO: need to add right faucet icon on @fuel-ui
*/
<Button
size="sm"
intent="primary"
leftIcon={hasFaucet ? Icon.is('Wand') : Icon.is('Coins')}
onPress={open}
>
{hasFaucet ? 'Faucet' : 'Bridge to Fuel'}
</Button>
)}
</Card.Body>
</Card>
</Box>
);
}

const styles = {
container: cssObj({
minHeight: BALANCE_NFTS_TAB_HEIGHT,
}),
empty: cssObj({
h5: {
margin: 0,
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/systems/Home/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useBalanceVisibility } from '~/systems/Core/hooks/useVisibility';

import { BalanceAssets } from '~/systems/Account/components/BalanceAssets/BalanceAssets';
import { BalanceNFTs } from '~/systems/Account/components/BalanceNFTs/BalanceNFTs';
import { BALANCE_NFTS_TAB_HEIGHT } from '~/systems/Account/components/BalanceNFTs/constants';
import { QuickAccountConnect } from '~/systems/Account/components/QuickAccountConnect/QuickAccountConnect';
import { HomeActions } from '../../components';

Expand Down Expand Up @@ -52,7 +53,7 @@ export function Home() {
<BalanceAssets balances={account?.balances} isLoading={isLoading} />
</Tabs.Content>
<Tabs.Content value="nft" css={styles.assetsList}>
<BalanceNFTs balances={account?.balances} />
<BalanceNFTs balances={account?.balances} isLoading={isLoading} />
</Tabs.Content>
</Tabs>
</Layout.Content>
Expand All @@ -69,8 +70,7 @@ const styles = {
},
}),
assetsList: cssObj({
maxHeight: 200,
paddingBottom: '$4',
height: BALANCE_NFTS_TAB_HEIGHT,
...scrollable(),
overflowY: 'scroll !important',
}),
Expand Down

0 comments on commit 7236697

Please sign in to comment.