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,49 @@ 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 ( ( tok ) => hasPositiveBalance ( tok ) ) ;
144+ const ownedSet = new Set ( ownedTokens . map ( ( token ) => token . key ) ) ;
145+
146+ const items : Token [ ] = [ ] ;
147+ // Owned tokens first, preserving order from sortedTokens
148+ items . push ( ...ownedTokens ) ;
149+
150+ // Include native gas token next if not owned and exists
151+ if ( nativeToken && ! ownedSet . has ( nativeToken . key ) ) {
152+ items . push ( nativeToken ) ;
153+ }
154+
155+ // Then the rest
156+ for ( const token of sortedTokens ) {
157+ if ( ownedSet . has ( token . key ) ) continue ;
158+ if ( nativeToken && token . key === nativeToken . key ) continue ;
159+ items . push ( token ) ;
160+ }
161+
162+ return { itemsForRender : items , ownedCount : ownedTokens . length } ;
163+ } , [ isGroupingEnabled , props . balances , sortedTokens ] ) ;
164+
133165 const searchList = (
134166 < SearchableList < Token >
135167 searchPlaceholder = { placeholder }
136168 sx = { styles . tokenList }
137169 dataTestId = "token-search-list"
138170 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- }
171+ listTitle = { shouldShowEmptyMessage ? emptyMessage : '' }
154172 loading = {
155173 shouldShowLoadingState &&
156174 [ 1 , 2 , 3 ] . map ( ( x ) => (
@@ -161,32 +179,51 @@ const TokenList = (props: Props) => {
161179 </ ListItemButton >
162180 ) )
163181 }
164- items = { sortedTokens }
182+ items = { itemsForRender }
165183 onQueryChange = { ( query ) => {
166184 props . onSearchQueryChange ( query ) ;
167185 } }
168- renderFn = { ( token : Token ) => {
186+ renderFn = { ( token : Token , index : number ) => {
169187 const balance = props . balances ?. [ token . key ] ?. balance ;
170188 const tokenPrice = tokenPrices . get ( token . key ) ;
171189 const price =
172190 balance && tokenPrice !== undefined
173191 ? getUSDFormat ( calculateUSDPriceRaw ( tokenPrice , balance , token ) )
174192 : null ;
175193
194+ const isRestSection = isGroupingEnabled && index >= ownedCount ;
195+
176196 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- />
197+ < Fragment key = { token . key } >
198+ { isGroupingEnabled && index === 0 && ownedCount > 0 && (
199+ < Box sx = { { padding : '4px 16px' } } >
200+ < Typography fontSize = { 14 } color = { theme . palette . text . secondary } >
201+ Your tokens
202+ </ Typography >
203+ </ Box >
204+ ) }
205+ { isGroupingEnabled && index === ownedCount && (
206+ < Box sx = { { padding : '4px 16px' } } >
207+ < Typography fontSize = { 14 } color = { theme . palette . text . secondary } >
208+ All tokens
209+ </ Typography >
210+ </ Box >
211+ ) }
212+ < TokenItem
213+ key = { token . key }
214+ token = { token }
215+ chain = { props . selectedChainConfig . sdkName }
216+ onClick = { ( ) => {
217+ props . onSelectToken ( token ) ;
218+ } }
219+ isSource = { props . isSource }
220+ balance = { balance }
221+ price = { price }
222+ isSelected = { token . key === props . selectedToken ?. key }
223+ isFetchingBalance = { props . isFetchingBalances }
224+ dimmed = { isRestSection }
225+ />
226+ </ Fragment >
190227 ) ;
191228 } }
192229 />
@@ -196,9 +233,6 @@ const TokenList = (props: Props) => {
196233 < Card sx = { styles . card } variant = "elevation" >
197234 < CardContent sx = { styles . tokenListContainer } >
198235 < Box sx = { { display : 'flex' , padding : '0 16px' } } >
199- < Typography width = "100%" sx = { styles . title } >
200- Select a token
201- </ Typography >
202236 { isFetchingToken || props . isFetchingBalances ? (
203237 < CircularProgress
204238 sx = { { alignSelf : 'flex-end' , marginBottom : '12px' } }
0 commit comments