Files
explore/ui/multichain/address/portfolio/MultichainAddressPortfolioCards.tsx

115 lines
3.6 KiB
TypeScript

import BigNumber from 'bignumber.js';
import { clamp } from 'es-toolkit';
import React from 'react';
import type * as multichain from '@luxfi/multichain-aggregator-types';
import type { ClusterChainConfig } from 'types/multichain';
import multichainConfig from 'configs/multichain';
import useIsMobile from 'lib/hooks/useIsMobile';
import { CollapsibleList } from '@luxfi/ui/collapsible';
import type { LinkProps } from 'toolkit/next/link';
import IconSvg from 'ui/shared/IconSvg';
import MultichainAddressPortfolioCard from './MultichainAddressPortfolioCard';
const TRIGGER_PROPS: LinkProps = {
variant: 'secondary',
className: 'text-xs lg:text-sm py-1 lg:py-[2px]',
};
interface Props {
isLoading: boolean;
selectedChainId: string | null;
onChange: (chainId: string) => void;
chainValues: multichain.AddressPortfolio['chain_values'];
totalValue?: string;
}
const MultichainAddressPortfolioCards = ({ isLoading, selectedChainId, onChange, chainValues, totalValue }: Props) => {
const [ initialActiveIndex, setInitialActiveIndex ] = React.useState<number | undefined>(undefined);
const chains = multichainConfig()?.chains;
const isMobile = useIsMobile();
const items = React.useMemo(() => {
const totalValueBn = BigNumber(totalValue ?? '0');
const result: Array<{ chain: ClusterChainConfig; value: BigNumber; share: number | undefined }> = chainValues ?
Object.entries(chainValues)
.map(([ chainId, value ]) => {
const chain = chains?.find((chain) => chain.id === chainId);
if (!chain) {
return null;
}
const valueBn = BigNumber(value);
return {
chain,
value: valueBn,
share: totalValueBn.gt(0) ? valueBn.div(totalValueBn).toNumber() : undefined,
};
})
.filter(Boolean)
.sort((a, b) => b.value.minus(a.value).toNumber()) :
[];
return result;
}, [ chainValues, chains, totalValue ]);
const cutLength = clamp(items.filter(({ share }) => share).length, 0, isMobile ? 6 : 10) || items.length;
React.useEffect(() => {
if (!isLoading && selectedChainId !== null) {
const activeIndex = items.findIndex((item) => item.chain?.id === selectedChainId);
setInitialActiveIndex((prev) => prev === undefined ? activeIndex : prev);
}
}, [ isLoading, items, selectedChainId ]);
const renderItem = React.useCallback((item: typeof items[number]) => {
return (
<MultichainAddressPortfolioCard
key={ item.chain.id }
chain={ item.chain }
value={ item.value }
share={ item.share }
isLoading={ isLoading }
isSelected={ Boolean(item.chain) && selectedChainId === item.chain?.id }
noneIsSelected={ selectedChainId === null }
totalNum={ items.length }
onClick={ items.length > 1 ? onChange : undefined }
/>
);
}, [ isLoading, selectedChainId, onChange, items ]);
if (items.length === 0) {
return null;
}
const text: [React.ReactNode, React.ReactNode] = [
<>
<IconSvg name="plus" className="w-2 h-2 lg:w-2.5 lg:h-2.5 mr-0.5"/>
<span>show more</span>
</>,
<>
<IconSvg name="minus" className="w-2 h-2 lg:w-2.5 lg:h-2.5 mr-0.5"/>
<span>show less</span>
</>,
];
return (
<CollapsibleList
items={ items }
renderItem={ renderItem }
cutLength={ cutLength }
text={ text }
triggerProps={ TRIGGER_PROPS }
defaultExpanded={ initialActiveIndex !== undefined ? initialActiveIndex > cutLength : undefined }
className="flex-row items-center gap-2 flex-wrap mt-2"
/>
);
};
export default React.memo(MultichainAddressPortfolioCards);