1- import React , { useMemo } from 'react' ;
1+ import React , { Fragment , useMemo } from 'react' ;
22import { Box , Card , CardContent , Skeleton , useTheme } from '@mui/material' ;
33import CircularProgress from '@mui/material/CircularProgress' ;
44import ListItemButton from '@mui/material/ListItemButton' ;
@@ -14,6 +14,7 @@ import config from 'config';
1414import { useTokens } from 'contexts/TokensContext' ;
1515import type { Balances } from 'utils/wallet/types' ;
1616import { useTokenListWithSearch } from 'hooks/useTokenListWithSearch' ;
17+ import { amount as sdkAmount } from '@wormhole-foundation/sdk' ;
1718
1819type Props = {
1920 tokenList : Array < Token > ;
@@ -102,11 +103,6 @@ const TokenList = (props: Props) => {
102103
103104 // Determine the current state of the token list
104105 const listState = useMemo ( ( ) => {
105- // For source list, require wallet to show balances/tokens from wallet
106- if ( props . isSource && ! props . wallet ?. address && ! props . isConnectingWallet ) {
107- return 'empty' ;
108- }
109-
110106 // Currently fetching initial data
111107 if ( props . isFetching ) {
112108 return 'loading' ;
@@ -130,27 +126,43 @@ const TokenList = (props: Props) => {
130126 const shouldShowLoadingState = listState === 'loading' ;
131127 const shouldShowEmptyMessage = listState === 'empty' ;
132128
129+ // Build sectioned list for source picker when not searching
130+ const isGroupingEnabled = props . isSource && ! props . searchQuery ;
131+
132+ const { itemsForRender, ownedCount } = useMemo ( ( ) => {
133+ if ( ! isGroupingEnabled ) {
134+ return { itemsForRender : sortedTokens , ownedCount : 0 } ;
135+ }
136+
137+ const hasPositiveBalance = ( token : Token ) => {
138+ const bal = props . balances ?. [ token . key ] ?. balance ;
139+ return ! ! ( bal && sdkAmount . units ( bal ) > 0n ) ;
140+ } ;
141+
142+ const nativeToken = sortedTokens . find ( ( token ) => token . isNativeGasToken ) ;
143+ const ownedTokens = sortedTokens . filter ( hasPositiveBalance ) ;
144+ const ownedSet = new Set ( ownedTokens . map ( ( t ) => t . key ) ) ;
145+
146+ const nativePart =
147+ nativeToken && ! ownedSet . has ( nativeToken . key ) ? [ nativeToken ] : [ ] ;
148+
149+ const rest = sortedTokens . filter (
150+ ( t ) =>
151+ ! ownedSet . has ( t . key ) && ( ! nativeToken || t . key !== nativeToken . key ) ,
152+ ) ;
153+
154+ const itemsForRender = [ ...ownedTokens , ...nativePart , ...rest ] ;
155+
156+ return { itemsForRender, ownedCount : ownedTokens . length } ;
157+ } , [ isGroupingEnabled , props . balances , sortedTokens ] ) ;
158+
133159 const searchList = (
134160 < SearchableList < Token >
135161 searchPlaceholder = { placeholder }
136162 sx = { styles . tokenList }
137163 dataTestId = "token-search-list"
138164 searchQuery = { props . searchQuery }
139- listTitle = {
140- shouldShowEmptyMessage ? (
141- emptyMessage
142- ) : (
143- < Box display = "flex" width = "100%" >
144- < Typography
145- style = { { flexGrow : '2' } }
146- fontSize = { 14 }
147- color = { theme . palette . text . secondary }
148- >
149- Tokens on { props . selectedChainConfig . displayName }
150- </ Typography >
151- </ Box >
152- )
153- }
165+ listTitle = { shouldShowEmptyMessage ? emptyMessage : '' }
154166 loading = {
155167 shouldShowLoadingState &&
156168 [ 1 , 2 , 3 ] . map ( ( x ) => (
@@ -161,32 +173,49 @@ const TokenList = (props: Props) => {
161173 </ ListItemButton >
162174 ) )
163175 }
164- items = { sortedTokens }
176+ items = { itemsForRender }
165177 onQueryChange = { ( query ) => {
166178 props . onSearchQueryChange ( query ) ;
167179 } }
168- renderFn = { ( token : Token ) => {
180+ renderFn = { ( token : Token , index : number ) => {
169181 const balance = props . balances ?. [ token . key ] ?. balance ;
170182 const tokenPrice = tokenPrices . get ( token . key ) ;
171183 const price =
172184 balance && tokenPrice !== undefined
173185 ? getUSDFormat ( calculateUSDPriceRaw ( tokenPrice , balance , token ) )
174186 : null ;
175187
188+ const isRestSection = isGroupingEnabled && index >= ownedCount ;
189+
176190 return (
177- < TokenItem
178- key = { token . key }
179- token = { token }
180- chain = { props . selectedChainConfig . sdkName }
181- onClick = { ( ) => {
182- props . onSelectToken ( token ) ;
183- } }
184- isSource = { props . isSource }
185- balance = { balance }
186- price = { price }
187- isSelected = { token . key === props . selectedToken ?. key }
188- isFetchingBalance = { props . isFetchingBalances }
189- />
191+ < Fragment key = { token . key } >
192+ { isGroupingEnabled && index === 0 && ownedCount > 0 && (
193+ < Box sx = { { padding : '4px 16px' } } >
194+ < Typography fontSize = { 14 } color = { theme . palette . text . secondary } >
195+ Your tokens
196+ </ Typography >
197+ </ Box >
198+ ) }
199+ { isGroupingEnabled && index === ownedCount && (
200+ < Box sx = { { padding : '4px 16px' } } >
201+ < Typography fontSize = { 14 } color = { theme . palette . text . secondary } >
202+ All tokens
203+ </ Typography >
204+ </ Box >
205+ ) }
206+ < TokenItem
207+ key = { token . key }
208+ token = { token }
209+ chain = { props . selectedChainConfig . sdkName }
210+ onClick = { ( ) => props . onSelectToken ( token ) }
211+ isSource = { props . isSource }
212+ balance = { balance }
213+ price = { price }
214+ isSelected = { token . key === props . selectedToken ?. key }
215+ isFetchingBalance = { props . isFetchingBalances }
216+ dimmed = { isRestSection }
217+ />
218+ </ Fragment >
190219 ) ;
191220 } }
192221 />
@@ -196,9 +225,6 @@ const TokenList = (props: Props) => {
196225 < Card sx = { styles . card } variant = "elevation" >
197226 < CardContent sx = { styles . tokenListContainer } >
198227 < Box sx = { { display : 'flex' , padding : '0 16px' } } >
199- < Typography width = "100%" sx = { styles . title } >
200- Select a token
201- </ Typography >
202228 { isFetchingToken || props . isFetchingBalances ? (
203229 < CircularProgress
204230 sx = { { alignSelf : 'flex-end' , marginBottom : '12px' } }
0 commit comments