|
| 1 | +import { IconChevronsRight } from '@tabler/icons-react'; |
| 2 | +import { Flex } from 'antd'; |
| 3 | +import Image from 'next/image'; |
| 4 | +import { useState } from 'react'; |
| 5 | +import { twMerge } from 'tailwind-merge'; |
| 6 | +import { Token } from '../hooks/useData'; |
| 7 | +import { Data, Dataset } from '../utils/types'; |
| 8 | +import { Privacy } from './privacy'; |
| 9 | +import { TokenDetails } from './tokenDetails'; |
| 10 | + |
| 11 | +const t: Dataset = { |
| 12 | + tokenLogo: 'Logo du token', |
| 13 | + transfered: 'Investi', |
| 14 | + withdrawn: 'Retiré', |
| 15 | + gains: 'Gains', |
| 16 | + loss: 'Pertes', |
| 17 | +}; |
| 18 | + |
| 19 | +interface TokenListItemProps { |
| 20 | + asset: Token; |
| 21 | + index: number; |
| 22 | + selectedIndex?: number; |
| 23 | + hasLoaded: boolean; |
| 24 | + tokens: Token[]; |
| 25 | + tokenDetails: Data[]; |
| 26 | + total: number; |
| 27 | + onSelectedIndexChange: (index: number | undefined) => void; |
| 28 | +} |
| 29 | + |
| 30 | +interface TokenDetailsButtonProps { |
| 31 | + index: number; |
| 32 | + selectedIndex?: number; |
| 33 | + hasLoaded: boolean; |
| 34 | + isTokenDetailsOpen: boolean; |
| 35 | + tokens: Token[]; |
| 36 | + tokenDetails: Data[]; |
| 37 | + total: number; |
| 38 | + onSelectedIndexChange: (index: number | undefined) => void; |
| 39 | + onTokenDetailsOpenChange: (isOpen: boolean) => void; |
| 40 | +} |
| 41 | + |
| 42 | +function TokenDetailsButton({ |
| 43 | + index, |
| 44 | + selectedIndex, |
| 45 | + hasLoaded, |
| 46 | + isTokenDetailsOpen, |
| 47 | + tokens, |
| 48 | + tokenDetails, |
| 49 | + total, |
| 50 | + onSelectedIndexChange, |
| 51 | + onTokenDetailsOpenChange, |
| 52 | +}: TokenDetailsButtonProps) { |
| 53 | + return ( |
| 54 | + <div className="hidden xs:flex items-center mx-0 xs:mx-2 md:mx-4 w-[50px] h-[50px] justify-center"> |
| 55 | + <IconChevronsRight |
| 56 | + className={twMerge( |
| 57 | + 'h-8 w-8 text-theme-content-strong dark:text-dark-theme-content-strong', |
| 58 | + 'transition-all duration-500 group-hover:animate-pulse', |
| 59 | + index === selectedIndex ? 'opacity-100' : 'opacity-0 group-hover:opacity-100', |
| 60 | + )} |
| 61 | + onClick={e => { |
| 62 | + e.stopPropagation(); |
| 63 | + onSelectedIndexChange(index); |
| 64 | + onTokenDetailsOpenChange(true); |
| 65 | + }} |
| 66 | + /> |
| 67 | + {hasLoaded ? ( |
| 68 | + <TokenDetails |
| 69 | + isOpen={isTokenDetailsOpen} |
| 70 | + onClose={() => onTokenDetailsOpenChange(false)} |
| 71 | + tokens={tokens} |
| 72 | + data={tokenDetails} |
| 73 | + total={total} |
| 74 | + selectedIndex={selectedIndex} |
| 75 | + onSelectedIndexChange={onSelectedIndexChange} |
| 76 | + /> |
| 77 | + ) : null} |
| 78 | + </div> |
| 79 | + ); |
| 80 | +} |
| 81 | + |
| 82 | +export function TokenListLoading() { |
| 83 | + return ( |
| 84 | + <div className="flex w-full animate-pulse py-4"> |
| 85 | + <div className="bg-theme-border rounded-full w-[50px] h-[50px] hidden 2xs:flex items-center mx-0 xs:mx-2 md:mx-4"></div> |
| 86 | + <div className="flex flex-col px-2 md:px-4 flex-1 min-w-0"> |
| 87 | + <div className="bg-theme-border rounded-md w-24 xs:w-36 md:w-44 h-7 mb-1" /> |
| 88 | + <div className="bg-theme-border rounded-md w-12 h-5 mb-1" /> |
| 89 | + </div> |
| 90 | + <div className="hidden sm:flex flex-col w-32"> |
| 91 | + <div className="bg-theme-border rounded-md w-32 h-5 mb-1" /> |
| 92 | + <div className="bg-theme-border rounded-md w-32 h-5 mb-1" /> |
| 93 | + </div> |
| 94 | + <div className="flex flex-col mx-0 md:mx-2 items-end w-32 md:flex-1"> |
| 95 | + <div className="bg-theme-border rounded-md w-24 h-5 mb-1" /> |
| 96 | + <div className="bg-theme-border rounded-md w-16 h-7 mb-1" /> |
| 97 | + </div> |
| 98 | + <div className="bg-theme-border rounded-md hidden xs:flex self-center mx-0 xs:mx-2 md:mx-4 justify-center w-8 h-8"></div> |
| 99 | + </div> |
| 100 | + ); |
| 101 | +} |
| 102 | + |
| 103 | +export function TokenListItem({ |
| 104 | + asset, |
| 105 | + index, |
| 106 | + selectedIndex, |
| 107 | + hasLoaded, |
| 108 | + tokens, |
| 109 | + tokenDetails, |
| 110 | + total, |
| 111 | + onSelectedIndexChange, |
| 112 | +}: TokenListItemProps) { |
| 113 | + const [isTokenDetailsOpen, setIsTokenDetailsOpen] = useState(false); |
| 114 | + |
| 115 | + return ( |
| 116 | + <div |
| 117 | + key={asset.label} |
| 118 | + className={twMerge( |
| 119 | + 'flex w-full border-b-2 last:border-b-0 border-theme-border dark:border-dark-theme-border', |
| 120 | + 'group cursor-pointer select-none touch-none transition-colors duration-200 py-4', |
| 121 | + selectedIndex === index |
| 122 | + ? 'bg-theme-background-subtle dark:bg-dark-theme-background-subtle' |
| 123 | + : 'hover:bg-theme-background-subtle dark:hover:bg-dark-theme-background-subtle [@media(hover:none)]:hover:bg-transparent [@media(hover:none)]:dark:hover:bg-transparent', |
| 124 | + )} |
| 125 | + onClick={() => onSelectedIndexChange(selectedIndex === index ? undefined : index)} |
| 126 | + onContextMenu={e => { |
| 127 | + e.preventDefault(); |
| 128 | + e.stopPropagation(); |
| 129 | + onSelectedIndexChange(index); |
| 130 | + setIsTokenDetailsOpen(true); |
| 131 | + }} |
| 132 | + > |
| 133 | + <Image |
| 134 | + className="rounded-full w-[50px] h-[50px] hidden 2xs:flex items-center mx-0 xs:mx-2 md:mx-4" |
| 135 | + src={asset.image} |
| 136 | + alt={t.tokenLogo} |
| 137 | + width={50} |
| 138 | + height={50} |
| 139 | + /> |
| 140 | + <div className="flex flex-col px-2 md:px-4 flex-1 min-w-0"> |
| 141 | + <div className="text-xl truncate">{asset.label}</div> |
| 142 | + <div>{asset.value ? asset.value.toLocaleCurrency() : ''}</div> |
| 143 | + </div> |
| 144 | + {hasLoaded ? ( |
| 145 | + <div className={twMerge('hidden sm:flex flex-col w-32', asset.movement ? 'opacity-100' : 'opacity-0')}> |
| 146 | + <Flex> |
| 147 | + {asset.movement >= 0 ? t.transfered : t.withdrawn} : |
| 148 | + <Privacy className="font-bold" amount={asset.movement} /> |
| 149 | + </Flex> |
| 150 | + <Flex> |
| 151 | + {asset.profit >= 0 ? t.gains : t.loss} : |
| 152 | + <Privacy |
| 153 | + className={twMerge('font-bold', asset.profit >= 0 ? 'text-ok' : 'text-error')} |
| 154 | + amount={asset.profit} |
| 155 | + /> |
| 156 | + </Flex> |
| 157 | + </div> |
| 158 | + ) : ( |
| 159 | + <div className="hidden sm:flex flex-col w-32 animate-pulse"> |
| 160 | + <div className="bg-theme-border rounded-md w-32 h-5 mb-1" /> |
| 161 | + <div className="bg-theme-border rounded-md w-32 h-5 mb-1" /> |
| 162 | + </div> |
| 163 | + )} |
| 164 | + <div className="flex flex-col mx-0 md:mx-2 items-end w-32 md:flex-1"> |
| 165 | + <Flex className="gap-1 justify-end"> |
| 166 | + <Privacy amount={asset.balance.toDecimalPlace(asset.balance.getPrecision(), 'down')} currencyType="none" /> |
| 167 | + {asset.symbol} |
| 168 | + </Flex> |
| 169 | + <Privacy className="font-bold text-lg text-right" amount={asset.total} /> |
| 170 | + </div> |
| 171 | + <TokenDetailsButton |
| 172 | + index={index} |
| 173 | + selectedIndex={selectedIndex} |
| 174 | + hasLoaded={hasLoaded} |
| 175 | + isTokenDetailsOpen={isTokenDetailsOpen} |
| 176 | + tokens={tokens} |
| 177 | + tokenDetails={tokenDetails} |
| 178 | + total={total} |
| 179 | + onSelectedIndexChange={onSelectedIndexChange} |
| 180 | + onTokenDetailsOpenChange={setIsTokenDetailsOpen} |
| 181 | + /> |
| 182 | + </div> |
| 183 | + ); |
| 184 | +} |
0 commit comments