@lifi/widget 3.29.1 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/esm/components/AmountInput/AmountInputEndAdornment.d.ts +1 -1
  3. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +3 -2
  4. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  5. package/dist/esm/components/AmountInput/PriceFormHelperText.d.ts +1 -1
  6. package/dist/esm/components/AmountInput/PriceFormHelperText.js +3 -2
  7. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  8. package/dist/esm/components/ChainSelect/ChainSelect.js +35 -18
  9. package/dist/esm/components/ChainSelect/ChainSelect.js.map +1 -1
  10. package/dist/esm/components/ChainSelect/useChainSelect.d.ts +1 -1
  11. package/dist/esm/components/ChainSelect/useChainSelect.js +3 -3
  12. package/dist/esm/components/ChainSelect/useChainSelect.js.map +1 -1
  13. package/dist/esm/components/Chains/AllChainsAvatar.d.ts +7 -0
  14. package/dist/esm/components/Chains/AllChainsAvatar.js +77 -0
  15. package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -0
  16. package/dist/esm/components/Chains/ChainList.d.ts +2 -1
  17. package/dist/esm/components/Chains/ChainList.js +2 -2
  18. package/dist/esm/components/Chains/ChainList.js.map +1 -1
  19. package/dist/esm/components/Chains/ChainSearchInput.js +2 -2
  20. package/dist/esm/components/Chains/ChainSearchInput.js.map +1 -1
  21. package/dist/esm/components/Chains/SelectChainContent.js +1 -1
  22. package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
  23. package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
  24. package/dist/esm/components/Chains/VirtualizedChainList.js +42 -10
  25. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  26. package/dist/esm/components/RouteCard/RouteCardEssentials.js +1 -1
  27. package/dist/esm/components/RouteCard/RouteCardEssentials.js.map +1 -1
  28. package/dist/esm/components/TokenList/TokenDetailsSheet.d.ts +1 -5
  29. package/dist/esm/components/TokenList/TokenDetailsSheet.js +4 -2
  30. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  31. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +2 -2
  32. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  33. package/dist/esm/components/TokenList/TokenList.js +11 -53
  34. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  35. package/dist/esm/components/TokenList/TokenListItem.d.ts +1 -1
  36. package/dist/esm/components/TokenList/TokenListItem.js +29 -25
  37. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  38. package/dist/esm/components/TokenList/VirtualizedTokenList.js +56 -37
  39. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  40. package/dist/esm/components/TokenList/types.d.ts +6 -10
  41. package/dist/esm/components/TokenList/useTokenSelect.js +3 -4
  42. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  43. package/dist/esm/components/TransactionDetails.js +2 -2
  44. package/dist/esm/components/TransactionDetails.js.map +1 -1
  45. package/dist/esm/config/version.d.ts +1 -1
  46. package/dist/esm/config/version.js +1 -1
  47. package/dist/esm/hooks/useAccountsBalancesData.d.ts +6 -0
  48. package/dist/esm/hooks/useAccountsBalancesData.js +64 -0
  49. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -0
  50. package/dist/esm/hooks/useFilteredByTokenBalances.d.ts +8 -0
  51. package/dist/esm/hooks/useFilteredByTokenBalances.js +69 -0
  52. package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -0
  53. package/dist/esm/hooks/useListHeight.d.ts +0 -1
  54. package/dist/esm/hooks/useListHeight.js +0 -1
  55. package/dist/esm/hooks/useListHeight.js.map +1 -1
  56. package/dist/esm/hooks/useToken.d.ts +2 -2
  57. package/dist/esm/hooks/useToken.js +13 -11
  58. package/dist/esm/hooks/useToken.js.map +1 -1
  59. package/dist/esm/hooks/useTokenAddressBalance.d.ts +2 -3
  60. package/dist/esm/hooks/useTokenAddressBalance.js +10 -14
  61. package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
  62. package/dist/esm/hooks/useTokenBalances.d.ts +6 -9
  63. package/dist/esm/hooks/useTokenBalances.js +57 -72
  64. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  65. package/dist/esm/hooks/useTokenBalancesQueries.d.ts +11 -0
  66. package/dist/esm/hooks/useTokenBalancesQueries.js +74 -0
  67. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -0
  68. package/dist/esm/hooks/useTokens.d.ts +6 -6
  69. package/dist/esm/hooks/useTokens.js +78 -70
  70. package/dist/esm/hooks/useTokens.js.map +1 -1
  71. package/dist/esm/i18n/bn.json +3 -3
  72. package/dist/esm/i18n/de.json +3 -3
  73. package/dist/esm/i18n/en.json +3 -3
  74. package/dist/esm/i18n/es.json +3 -3
  75. package/dist/esm/i18n/fr.json +3 -3
  76. package/dist/esm/i18n/hi.json +3 -3
  77. package/dist/esm/i18n/id.json +3 -3
  78. package/dist/esm/i18n/it.json +3 -3
  79. package/dist/esm/i18n/ja.json +3 -3
  80. package/dist/esm/i18n/ko.json +3 -3
  81. package/dist/esm/i18n/pl.json +3 -3
  82. package/dist/esm/i18n/pt.json +3 -3
  83. package/dist/esm/i18n/th.json +3 -3
  84. package/dist/esm/i18n/tr.json +3 -3
  85. package/dist/esm/i18n/uk.json +3 -3
  86. package/dist/esm/i18n/vi.json +3 -3
  87. package/dist/esm/i18n/zh.json +3 -3
  88. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js +6 -1
  89. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js.map +1 -1
  90. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -4
  91. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
  92. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js +1 -1
  93. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js.map +1 -1
  94. package/dist/esm/stores/chains/createChainOrderStore.d.ts +3 -2
  95. package/dist/esm/stores/chains/createChainOrderStore.js +13 -8
  96. package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
  97. package/dist/esm/stores/chains/types.d.ts +2 -0
  98. package/dist/esm/stores/chains/useChainOrder.js +5 -1
  99. package/dist/esm/stores/chains/useChainOrder.js.map +1 -1
  100. package/dist/esm/types/token.d.ts +7 -2
  101. package/dist/esm/utils/chainType.d.ts +1 -0
  102. package/dist/esm/utils/chainType.js +2 -0
  103. package/dist/esm/utils/chainType.js.map +1 -1
  104. package/dist/esm/utils/token.d.ts +8 -0
  105. package/dist/esm/utils/token.js +29 -0
  106. package/dist/esm/utils/token.js.map +1 -0
  107. package/dist/esm/utils/tokenList.d.ts +13 -0
  108. package/dist/esm/utils/tokenList.js +106 -0
  109. package/dist/esm/utils/tokenList.js.map +1 -0
  110. package/package.json +7 -7
  111. package/package.json.tmp +6 -6
  112. package/src/components/AmountInput/AmountInputEndAdornment.tsx +3 -2
  113. package/src/components/AmountInput/PriceFormHelperText.tsx +3 -2
  114. package/src/components/ChainSelect/ChainSelect.tsx +112 -40
  115. package/src/components/ChainSelect/useChainSelect.ts +3 -3
  116. package/src/components/Chains/AllChainsAvatar.tsx +113 -0
  117. package/src/components/Chains/ChainList.tsx +3 -0
  118. package/src/components/Chains/ChainSearchInput.tsx +2 -2
  119. package/src/components/Chains/SelectChainContent.tsx +1 -0
  120. package/src/components/Chains/VirtualizedChainList.tsx +80 -12
  121. package/src/components/RouteCard/RouteCardEssentials.tsx +1 -1
  122. package/src/components/TokenList/TokenDetailsSheet.tsx +4 -9
  123. package/src/components/TokenList/TokenDetailsSheetContent.tsx +2 -6
  124. package/src/components/TokenList/TokenList.tsx +57 -129
  125. package/src/components/TokenList/TokenListItem.tsx +191 -166
  126. package/src/components/TokenList/VirtualizedTokenList.tsx +88 -48
  127. package/src/components/TokenList/types.ts +14 -10
  128. package/src/components/TokenList/useTokenSelect.ts +3 -4
  129. package/src/components/TransactionDetails.tsx +2 -2
  130. package/src/config/version.ts +1 -1
  131. package/src/hooks/useAccountsBalancesData.ts +101 -0
  132. package/src/hooks/useFilteredByTokenBalances.ts +101 -0
  133. package/src/hooks/useListHeight.ts +0 -1
  134. package/src/hooks/useToken.ts +26 -14
  135. package/src/hooks/useTokenAddressBalance.ts +14 -20
  136. package/src/hooks/useTokenBalances.ts +81 -80
  137. package/src/hooks/useTokenBalancesQueries.ts +94 -0
  138. package/src/hooks/useTokens.ts +118 -90
  139. package/src/i18n/bn.json +3 -3
  140. package/src/i18n/de.json +3 -3
  141. package/src/i18n/en.json +3 -3
  142. package/src/i18n/es.json +3 -3
  143. package/src/i18n/fr.json +3 -3
  144. package/src/i18n/hi.json +3 -3
  145. package/src/i18n/id.json +3 -3
  146. package/src/i18n/it.json +3 -3
  147. package/src/i18n/ja.json +3 -3
  148. package/src/i18n/ko.json +3 -3
  149. package/src/i18n/pl.json +3 -3
  150. package/src/i18n/pt.json +3 -3
  151. package/src/i18n/th.json +3 -3
  152. package/src/i18n/tr.json +3 -3
  153. package/src/i18n/uk.json +3 -3
  154. package/src/i18n/vi.json +3 -3
  155. package/src/i18n/zh.json +3 -3
  156. package/src/pages/SelectTokenPage/SearchTokenInput.tsx +5 -0
  157. package/src/pages/SelectTokenPage/SelectTokenPage.tsx +7 -13
  158. package/src/pages/TransactionPage/TokenValueBottomSheet.tsx +1 -1
  159. package/src/stores/chains/createChainOrderStore.ts +17 -8
  160. package/src/stores/chains/types.ts +2 -0
  161. package/src/stores/chains/useChainOrder.ts +5 -1
  162. package/src/types/token.ts +11 -2
  163. package/src/utils/chainType.ts +2 -0
  164. package/src/utils/token.ts +65 -0
  165. package/src/utils/tokenList.ts +172 -0
  166. package/dist/esm/components/TokenList/utils.d.ts +0 -2
  167. package/dist/esm/components/TokenList/utils.js +0 -35
  168. package/dist/esm/components/TokenList/utils.js.map +0 -1
  169. package/src/components/TokenList/utils.ts +0 -42
@@ -1,151 +1,79 @@
1
- import type { BaseToken } from '@lifi/sdk'
2
- import { useAccount } from '@lifi/wallet-management'
3
1
  import { Box } from '@mui/material'
4
- import { type FC, useEffect } from 'react'
5
- import { useChain } from '../../hooks/useChain.js'
2
+ import { type FC, memo, useEffect } from 'react'
6
3
  import { useDebouncedWatch } from '../../hooks/useDebouncedWatch.js'
7
4
  import { useTokenBalances } from '../../hooks/useTokenBalances.js'
8
- import { useTokenSearch } from '../../hooks/useTokenSearch.js'
9
5
  import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
10
- import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
6
+ import { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'
11
7
  import { FormKeyHelper } from '../../stores/form/types.js'
12
8
  import { useFieldValues } from '../../stores/form/useFieldValues.js'
13
9
  import { WidgetEvent } from '../../types/events.js'
14
- import type { TokenAmount } from '../../types/token.js'
15
- import { getConfigItemSets, isFormItemAllowed } from '../../utils/item.js'
16
10
  import { TokenNotFound } from './TokenNotFound.js'
17
11
  import type { TokenListProps } from './types.js'
18
12
  import { useTokenSelect } from './useTokenSelect.js'
19
- import { filteredTokensComparator } from './utils.js'
20
13
  import { VirtualizedTokenList } from './VirtualizedTokenList.js'
21
14
 
22
- export const TokenList: FC<TokenListProps> = ({
23
- formType,
24
- parentRef,
25
- height,
26
- onClick,
27
- }) => {
28
- const emitter = useWidgetEvents()
29
- const [selectedChainId, selectedTokenAddress] = useFieldValues(
30
- FormKeyHelper.getChainKey(formType),
31
- FormKeyHelper.getTokenKey(formType)
32
- )
33
- const [tokenSearchFilter]: string[] = useDebouncedWatch(
34
- 320,
35
- 'tokenSearchFilter'
36
- )
15
+ export const TokenList: FC<TokenListProps> = memo(
16
+ ({ formType, parentRef, height, onClick }) => {
17
+ const emitter = useWidgetEvents()
37
18
 
38
- const { tokens: configTokens } = useWidgetConfig()
39
-
40
- const { chain: selectedChain, isLoading: isSelectedChainLoading } =
41
- useChain(selectedChainId)
42
- const { account } = useAccount({
43
- chainType: selectedChain?.chainType,
44
- })
45
-
46
- const {
47
- tokens: chainTokens,
48
- tokensWithBalance,
49
- isLoading: isTokensLoading,
50
- isBalanceLoading,
51
- featuredTokens,
52
- popularTokens,
53
- } = useTokenBalances(selectedChainId)
54
-
55
- let filteredTokens = (tokensWithBalance ?? chainTokens ?? []) as TokenAmount[]
56
- const normalizedSearchFilter = tokenSearchFilter?.replaceAll('$', '')
57
- const searchFilter = normalizedSearchFilter?.toUpperCase() ?? ''
58
-
59
- const filteredConfigTokens = getConfigItemSets(
60
- configTokens,
61
- (tokens: BaseToken[]) =>
62
- new Set(
63
- tokens
64
- .filter((t) => t.chainId === selectedChainId)
65
- .map((t) => t.address)
66
- ),
67
- formType
68
- )
69
-
70
- // Get the appropriate allow/deny lists based on formType
71
- filteredTokens = filteredTokens.filter(
72
- (token) =>
73
- token.chainId === selectedChainId &&
74
- isFormItemAllowed(token, filteredConfigTokens, formType, (t) => t.address)
75
- )
19
+ const [selectedChainId, selectedTokenAddress] = useFieldValues(
20
+ FormKeyHelper.getChainKey(formType),
21
+ FormKeyHelper.getTokenKey(formType)
22
+ )
76
23
 
77
- filteredTokens = tokenSearchFilter
78
- ? filteredTokens
79
- .filter(
80
- (token) =>
81
- token.name?.toUpperCase().includes(searchFilter) ||
82
- token.symbol
83
- .toUpperCase()
84
- // Replace ₮ with T for USD₮0
85
- .replaceAll('₮', 'T')
86
- .includes(searchFilter) ||
87
- token.address.toUpperCase().includes(searchFilter)
88
- )
89
- .sort(filteredTokensComparator(searchFilter))
90
- : filteredTokens
24
+ const isAllNetworks = useChainOrderStore((state) => state.isAllNetworks)
91
25
 
92
- const tokenSearchEnabled =
93
- !isTokensLoading &&
94
- !filteredTokens.length &&
95
- !!tokenSearchFilter &&
96
- !!selectedChainId
26
+ const [tokenSearchFilter]: string[] = useDebouncedWatch(
27
+ 320,
28
+ 'tokenSearchFilter'
29
+ )
97
30
 
98
- const { token: searchedToken, isLoading: isSearchedTokenLoading } =
99
- useTokenSearch(
31
+ const {
32
+ tokens,
33
+ withCategories,
34
+ isTokensLoading,
35
+ isBalanceLoading,
36
+ isSearchLoading,
37
+ } = useTokenBalances(
100
38
  selectedChainId,
101
- normalizedSearchFilter,
102
- tokenSearchEnabled,
103
- formType
39
+ formType,
40
+ isAllNetworks,
41
+ tokenSearchFilter
104
42
  )
105
43
 
106
- const isLoading =
107
- isTokensLoading ||
108
- isSelectedChainLoading ||
109
- (tokenSearchEnabled && isSearchedTokenLoading)
44
+ const handleTokenClick = useTokenSelect(formType, onClick)
110
45
 
111
- const tokens = filteredTokens.length
112
- ? filteredTokens
113
- : searchedToken
114
- ? [searchedToken]
115
- : filteredTokens
46
+ const showCategories =
47
+ withCategories && !tokenSearchFilter && !isAllNetworks
116
48
 
117
- const handleTokenClick = useTokenSelect(formType, onClick)
118
- const showCategories =
119
- Boolean(featuredTokens?.length || popularTokens?.length) &&
120
- !tokenSearchFilter
49
+ useEffect(() => {
50
+ const normalizedSearchFilter = tokenSearchFilter?.replaceAll('$', '')
51
+ if (normalizedSearchFilter) {
52
+ emitter.emit(WidgetEvent.TokenSearch, {
53
+ value: normalizedSearchFilter,
54
+ tokens,
55
+ })
56
+ }
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [tokenSearchFilter, tokens, emitter])
121
59
 
122
- // biome-ignore lint/correctness/useExhaustiveDependencies: Should fire only when search filter changes
123
- useEffect(() => {
124
- if (normalizedSearchFilter) {
125
- emitter.emit(WidgetEvent.TokenSearch, {
126
- value: normalizedSearchFilter,
127
- tokens,
128
- })
129
- }
130
- }, [normalizedSearchFilter, emitter])
131
-
132
- return (
133
- <Box ref={parentRef} style={{ height, overflow: 'auto' }}>
134
- {!tokens.length && !isLoading ? (
135
- <TokenNotFound formType={formType} />
136
- ) : null}
137
- <VirtualizedTokenList
138
- account={account}
139
- tokens={tokens}
140
- scrollElementRef={parentRef}
141
- chainId={selectedChainId}
142
- chain={selectedChain}
143
- isLoading={isLoading}
144
- isBalanceLoading={isBalanceLoading}
145
- showCategories={showCategories}
146
- onClick={handleTokenClick}
147
- selectedTokenAddress={selectedTokenAddress}
148
- />
149
- </Box>
150
- )
151
- }
60
+ return (
61
+ <Box ref={parentRef} style={{ height, overflow: 'auto' }}>
62
+ {!tokens.length && !isTokensLoading && !isSearchLoading ? (
63
+ <TokenNotFound formType={formType} />
64
+ ) : null}
65
+ <VirtualizedTokenList
66
+ tokens={tokens}
67
+ scrollElementRef={parentRef}
68
+ chainId={selectedChainId}
69
+ isLoading={isTokensLoading || isSearchLoading}
70
+ isBalanceLoading={isBalanceLoading}
71
+ showCategories={showCategories}
72
+ onClick={handleTokenClick}
73
+ selectedTokenAddress={selectedTokenAddress}
74
+ isAllNetworks={isAllNetworks}
75
+ />
76
+ </Box>
77
+ )
78
+ }
79
+ )
@@ -1,3 +1,4 @@
1
+ import type { StaticToken } from '@lifi/sdk'
1
2
  import { ChainType } from '@lifi/sdk'
2
3
  import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
3
4
  import {
@@ -5,16 +6,18 @@ import {
5
6
  Box,
6
7
  ListItemAvatar,
7
8
  ListItemText,
9
+ listItemSecondaryActionClasses,
8
10
  Skeleton,
9
11
  Slide,
10
12
  Typography,
11
13
  } from '@mui/material'
12
14
  import type { MouseEventHandler } from 'react'
13
- import { memo, useRef, useState } from 'react'
15
+ import { memo, useMemo, useRef, useState } from 'react'
14
16
  import { useTranslation } from 'react-i18next'
15
17
  import { useLongPress } from '../../hooks/useLongPress.js'
16
18
  import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
17
19
  import { shortenAddress } from '../../utils/wallet.js'
20
+ import { TokenAvatar } from '../Avatar/TokenAvatar.js'
18
21
  import { ListItemButton } from '../ListItem/ListItemButton.js'
19
22
  import { IconButton, ListItem } from './TokenList.style.js'
20
23
  import type {
@@ -30,17 +33,12 @@ export const TokenListItem: React.FC<TokenListItemProps> = memo(
30
33
  start,
31
34
  token,
32
35
  chain,
33
- accountAddress,
34
36
  isBalanceLoading,
35
37
  startAdornment,
36
38
  endAdornment,
37
39
  selected,
38
40
  onShowTokenDetails,
39
41
  }) => {
40
- const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
41
- e.stopPropagation()
42
- onClick?.(token.address, chain?.id)
43
- }
44
42
  return (
45
43
  <ListItem
46
44
  style={{
@@ -52,10 +50,9 @@ export const TokenListItem: React.FC<TokenListItemProps> = memo(
52
50
  {startAdornment}
53
51
  <TokenListItemButton
54
52
  token={token}
55
- chain={chain}
56
- accountAddress={accountAddress}
57
53
  isBalanceLoading={isBalanceLoading}
58
- onClick={handleClick}
54
+ onClick={onClick}
55
+ chain={chain}
59
56
  selected={selected}
60
57
  onShowTokenDetails={onShowTokenDetails}
61
58
  />
@@ -65,33 +62,40 @@ export const TokenListItem: React.FC<TokenListItemProps> = memo(
65
62
  }
66
63
  )
67
64
 
68
- export const TokenListItemAvatar: React.FC<TokenListItemAvatarProps> = ({
69
- token,
70
- }) => {
71
- const [isImageLoading, setIsImageLoading] = useState(true)
72
- return (
73
- <Avatar
74
- src={token.logoURI}
75
- alt={token.symbol}
76
- sx={(theme) =>
77
- isImageLoading ? { bgcolor: theme.vars.palette.grey[300] } : null
78
- }
79
- onLoad={() => setIsImageLoading(false)}
80
- >
81
- {token.symbol?.[0]}
82
- </Avatar>
83
- )
84
- }
65
+ export const TokenListItemAvatar = memo(
66
+ ({ token }: TokenListItemAvatarProps) => {
67
+ const [isImageLoading, setIsImageLoading] = useState(true)
68
+
69
+ return (
70
+ <Avatar
71
+ src={token.logoURI}
72
+ alt={token.symbol}
73
+ sx={(theme) =>
74
+ isImageLoading ? { bgcolor: theme.vars.palette.grey[300] } : null
75
+ }
76
+ onLoad={() => setIsImageLoading(false)}
77
+ >
78
+ {token.symbol?.[0]}
79
+ </Avatar>
80
+ )
81
+ }
82
+ )
85
83
 
86
84
  interface OpenTokenDetailsButtonProps {
87
85
  tokenAddress: string | undefined
88
86
  withoutContractAddress: boolean
89
- onClick: (tokenAddress: string, withoutContractAddress: boolean) => void
87
+ chainId: number
88
+ onClick: (
89
+ tokenAddress: string,
90
+ withoutContractAddress: boolean,
91
+ chainId: number
92
+ ) => void
90
93
  }
91
94
 
92
95
  const OpenTokenDetailsButton = ({
93
96
  tokenAddress,
94
97
  withoutContractAddress,
98
+ chainId,
95
99
  onClick,
96
100
  }: OpenTokenDetailsButtonProps) => {
97
101
  if (!tokenAddress) {
@@ -103,7 +107,7 @@ const OpenTokenDetailsButton = ({
103
107
  onClick={(e) => {
104
108
  e.stopPropagation()
105
109
  e.currentTarget.blur() // Remove focus to prevent accessibility issues when opening drawer
106
- onClick(tokenAddress, withoutContractAddress)
110
+ onClick(tokenAddress, withoutContractAddress, chainId)
107
111
  }}
108
112
  >
109
113
  <InfoOutlinedIcon />
@@ -111,92 +115,159 @@ const OpenTokenDetailsButton = ({
111
115
  )
112
116
  }
113
117
 
114
- export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
115
- onClick,
116
- token,
117
- chain,
118
- accountAddress,
119
- isBalanceLoading,
120
- selected,
121
- onShowTokenDetails,
122
- }) => {
123
- const { t } = useTranslation()
124
- const container = useRef(null)
125
- const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)
126
- const [showAddress, setShowAddress] = useState(false)
118
+ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
119
+ ({
120
+ onClick,
121
+ token,
122
+ chain,
123
+ isBalanceLoading,
124
+ selected,
125
+ onShowTokenDetails,
126
+ }) => {
127
+ const { t } = useTranslation()
128
+ const container = useRef(null)
129
+ const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)
130
+ const [showAddress, setShowAddress] = useState(false)
127
131
 
128
- const withoutContractAddress = chain?.chainType === ChainType.UTXO
132
+ const withoutContractAddress = chain?.chainType === ChainType.UTXO
129
133
 
130
- const onMouseEnter = () => {
131
- timeoutId.current = setTimeout(() => {
132
- if (token.address) {
133
- setShowAddress(true)
134
+ const onMouseEnter = () => {
135
+ timeoutId.current = setTimeout(() => {
136
+ if (token.address) {
137
+ setShowAddress(true)
138
+ }
139
+ }, 350)
140
+ }
141
+
142
+ const onMouseLeave = () => {
143
+ clearTimeout(timeoutId.current)
144
+ if (showAddress) {
145
+ setShowAddress(false)
134
146
  }
135
- }, 350)
136
- }
147
+ }
137
148
 
138
- const onMouseLeave = () => {
139
- clearTimeout(timeoutId.current)
140
- if (showAddress) {
141
- setShowAddress(false)
149
+ const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
150
+ e.stopPropagation()
151
+ onClick?.(token.address, token.chainId)
142
152
  }
143
- }
144
153
 
145
- const tokenAmount = formatTokenAmount(token.amount, token.decimals)
146
- const tokenPrice = formatTokenPrice(
147
- token.amount,
148
- token.priceUSD,
149
- token.decimals
150
- )
154
+ const tokenAmount = useMemo(
155
+ () => formatTokenAmount(token.amount, token.decimals),
156
+ [token.amount, token.decimals]
157
+ )
151
158
 
152
- const longPressEvents = useLongPress(() =>
153
- onShowTokenDetails(token.address, withoutContractAddress)
154
- )
159
+ const tokenPrice = useMemo(
160
+ () => formatTokenPrice(token.amount, token.priceUSD, token.decimals),
161
+ [token.amount, token.priceUSD, token.decimals]
162
+ )
155
163
 
156
- return (
157
- <ListItemButton
158
- onClick={onClick}
159
- onMouseEnter={onMouseEnter}
160
- onMouseLeave={onMouseLeave}
161
- {...longPressEvents}
162
- dense
163
- selected={selected}
164
- sx={{
165
- height: 60,
166
- marginBottom: '4px',
167
- }}
168
- >
169
- <ListItemAvatar>
170
- <TokenListItemAvatar token={token} />
171
- </ListItemAvatar>
172
- <ListItemText
173
- primary={token.symbol}
174
- slotProps={{
175
- secondary: {
176
- component: 'div',
177
- },
164
+ const longPressEvents = useLongPress(() =>
165
+ onShowTokenDetails(token.address, withoutContractAddress, token.chainId)
166
+ )
167
+
168
+ return (
169
+ <ListItemButton
170
+ onClick={handleClick}
171
+ onMouseEnter={onMouseEnter}
172
+ onMouseLeave={onMouseLeave}
173
+ {...longPressEvents}
174
+ dense
175
+ selected={selected}
176
+ sx={{
177
+ height: 60,
178
+ marginBottom: '4px',
178
179
  }}
179
- secondary={
180
- withoutContractAddress ? (
181
- <Box
182
- ref={container}
183
- sx={{
184
- height: 20,
185
- display: 'flex',
186
- }}
187
- >
180
+ >
181
+ <ListItemAvatar>
182
+ {chain ? (
183
+ <TokenAvatar
184
+ token={token as StaticToken}
185
+ chain={chain}
186
+ tokenAvatarSize={40}
187
+ chainAvatarSize={16}
188
+ />
189
+ ) : (
190
+ <TokenListItemAvatar token={token} />
191
+ )}
192
+ </ListItemAvatar>
193
+ <ListItemText
194
+ primary={token.symbol}
195
+ slotProps={{
196
+ secondary: {
197
+ component: 'div',
198
+ },
199
+ }}
200
+ secondary={
201
+ withoutContractAddress ? (
188
202
  <Box
203
+ ref={container}
189
204
  sx={{
190
- pt: 0.25,
205
+ height: 20,
206
+ display: 'flex',
191
207
  }}
192
208
  >
193
- {token.name}
209
+ <Box
210
+ sx={{
211
+ pt: 0.25,
212
+ }}
213
+ >
214
+ {token.name}
215
+ </Box>
216
+ <Box
217
+ sx={{
218
+ position: 'relative',
219
+ }}
220
+ >
221
+ <Slide
222
+ direction="up"
223
+ in={showAddress}
224
+ container={container.current}
225
+ style={{
226
+ position: 'absolute',
227
+ }}
228
+ appear={false}
229
+ mountOnEnter
230
+ >
231
+ <Box
232
+ sx={{
233
+ display: 'flex',
234
+ }}
235
+ >
236
+ <OpenTokenDetailsButton
237
+ tokenAddress={token.address}
238
+ withoutContractAddress={withoutContractAddress}
239
+ chainId={token.chainId}
240
+ onClick={onShowTokenDetails}
241
+ />
242
+ </Box>
243
+ </Slide>
244
+ </Box>
194
245
  </Box>
246
+ ) : (
195
247
  <Box
248
+ ref={container}
196
249
  sx={{
197
250
  position: 'relative',
251
+ height: 20,
198
252
  }}
199
253
  >
254
+ <Slide
255
+ direction="down"
256
+ in={!showAddress}
257
+ container={container.current}
258
+ style={{
259
+ position: 'absolute',
260
+ }}
261
+ appear={false}
262
+ >
263
+ <Box
264
+ sx={{
265
+ pt: 0.25,
266
+ }}
267
+ >
268
+ {token.name}
269
+ </Box>
270
+ </Slide>
200
271
  <Slide
201
272
  direction="up"
202
273
  in={showAddress}
@@ -212,77 +283,28 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
212
283
  display: 'flex',
213
284
  }}
214
285
  >
286
+ <Box
287
+ sx={{
288
+ display: 'flex',
289
+ alignItems: 'center',
290
+ pt: 0.125,
291
+ }}
292
+ >
293
+ {shortenAddress(token.address)}
294
+ </Box>
215
295
  <OpenTokenDetailsButton
216
296
  tokenAddress={token.address}
217
297
  withoutContractAddress={withoutContractAddress}
298
+ chainId={token.chainId}
218
299
  onClick={onShowTokenDetails}
219
300
  />
220
301
  </Box>
221
302
  </Slide>
222
303
  </Box>
223
- </Box>
224
- ) : (
225
- <Box
226
- ref={container}
227
- sx={{
228
- position: 'relative',
229
- height: 20,
230
- }}
231
- >
232
- <Slide
233
- direction="down"
234
- in={!showAddress}
235
- container={container.current}
236
- style={{
237
- position: 'absolute',
238
- }}
239
- appear={false}
240
- >
241
- <Box
242
- sx={{
243
- pt: 0.25,
244
- }}
245
- >
246
- {token.name}
247
- </Box>
248
- </Slide>
249
- <Slide
250
- direction="up"
251
- in={showAddress}
252
- container={container.current}
253
- style={{
254
- position: 'absolute',
255
- }}
256
- appear={false}
257
- mountOnEnter
258
- >
259
- <Box
260
- sx={{
261
- display: 'flex',
262
- }}
263
- >
264
- <Box
265
- sx={{
266
- display: 'flex',
267
- alignItems: 'center',
268
- pt: 0.125,
269
- }}
270
- >
271
- {shortenAddress(token.address)}
272
- </Box>
273
- <OpenTokenDetailsButton
274
- tokenAddress={token.address}
275
- withoutContractAddress={withoutContractAddress}
276
- onClick={onShowTokenDetails}
277
- />
278
- </Box>
279
- </Slide>
280
- </Box>
281
- )
282
- }
283
- />
284
- {accountAddress ? (
285
- isBalanceLoading ? (
304
+ )
305
+ }
306
+ />
307
+ {isBalanceLoading ? (
286
308
  <TokenAmountSkeleton />
287
309
  ) : (
288
310
  <Box sx={{ textAlign: 'right' }}>
@@ -299,7 +321,7 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
299
321
  })}
300
322
  </Typography>
301
323
  ) : null}
302
- {tokenPrice ? (
324
+ {token.amount && token.priceUSD ? (
303
325
  <Typography
304
326
  data-price={token.priceUSD}
305
327
  sx={{
@@ -314,23 +336,26 @@ export const TokenListItemButton: React.FC<TokenListItemButtonProps> = ({
314
336
  </Typography>
315
337
  ) : null}
316
338
  </Box>
317
- )
318
- ) : null}
319
- </ListItemButton>
320
- )
321
- }
339
+ )}
340
+ </ListItemButton>
341
+ )
342
+ }
343
+ )
322
344
 
323
345
  export const TokenListItemSkeleton = () => {
324
346
  return (
325
347
  <ListItem
326
348
  secondaryAction={<TokenAmountSkeleton />}
327
349
  disablePadding
328
- sx={{
350
+ sx={(theme) => ({
329
351
  position: 'relative',
330
352
  flexDirection: 'row',
331
353
  alignItems: 'center',
332
354
  padding: 0,
333
- }}
355
+ [`& .${listItemSecondaryActionClasses.root}`]: {
356
+ right: theme.spacing(1.5),
357
+ },
358
+ })}
334
359
  >
335
360
  <ListItemAvatar>
336
361
  <Skeleton