@@ -3,9 +3,8 @@ import { useErrors } from '@/hooks/useErrors';
33import { nftUri } from '@/lib/nftUri' ;
44import { isValidAddress } from '@/lib/utils' ;
55import { t } from '@lingui/core/macro' ;
6- import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
7- import { Input } from '../ui/input' ;
8- import { DropdownSelector } from './DropdownSelector' ;
6+ import { useCallback , useEffect , useMemo , useState } from 'react' ;
7+ import { SearchableSelect } from './SearchableSelect' ;
98
109export interface NftSelectorProps {
1110 value : string | null ;
@@ -29,17 +28,9 @@ export function NftSelector({
2928 { } ,
3029 ) ;
3130 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
32- const inputRef = useRef < HTMLInputElement > ( null ) ;
3331
3432 const pageSize = 8 ;
3533
36- // Restore focus after NFT list updates
37- useEffect ( ( ) => {
38- if ( searchTerm && inputRef . current ) {
39- inputRef . current . focus ( ) ;
40- }
41- } , [ nfts , searchTerm ] ) ;
42-
4334 const isValidNftId = useMemo ( ( ) => {
4435 return isValidAddress ( searchTerm , 'nft' ) ;
4536 } , [ searchTerm ] ) ;
@@ -129,58 +120,88 @@ export function NftSelector({
129120
130121 const defaultNftImage = nftUri ( null , null ) ;
131122
132- return (
133- < DropdownSelector
134- loadedItems = { pageNftIds }
135- page = { page }
136- setPage = { setPage }
137- value = { value || undefined }
138- setValue = { ( nftId ) => {
123+ const handleSelect = useCallback (
124+ ( nftId : string | null ) => {
125+ if ( nftId ) {
139126 onChange ( nftId ) ;
140- // Only clear search term if it's not a valid NFT ID (i.e., user clicked on an item from the list)
141- if ( ! isValidAddress ( searchTerm , 'nft' ) ) {
142- setSearchTerm ( '' ) ;
143- }
144- } }
145- isDisabled = { ( nft ) => disabled . includes ( nft ) }
146- className = { className }
147- manualInput = {
148- < Input
149- ref = { inputRef }
150- placeholder = { t `Search by name or enter NFT ID` }
151- value = { searchTerm }
152- onChange = { ( e ) => {
153- const newValue = e . target . value ;
154- setSearchTerm ( newValue ) ;
155-
156- if ( isValidAddress ( newValue , 'nft' ) ) {
157- onChange ( newValue ) ;
158- }
159- } }
160- />
161127 }
162- renderItem = { ( nftId ) => (
163- < div className = 'flex items-center gap-2 w-full' >
164- < img
165- src = { nftThumbnails [ nftId ] ?? defaultNftImage }
166- className = 'w-10 h-10 rounded object-cover'
167- alt = ''
168- aria-hidden = 'true'
169- loading = 'lazy'
170- />
171- < div className = 'flex flex-col truncate' >
172- < span className = 'flex-grow truncate' role = 'text' >
173- { nfts [ nftId ] ?. name ?? 'Unknown NFT' }
174- </ span >
175- < span
176- className = 'text-xs text-muted-foreground truncate'
177- aria-label = 'NFT ID'
178- >
179- { nftId }
180- </ span >
181- </ div >
128+ } ,
129+ [ onChange ] ,
130+ ) ;
131+
132+ const handleManualInput = useCallback (
133+ ( nftId : string ) => {
134+ onChange ( nftId ) ;
135+ } ,
136+ [ onChange ] ,
137+ ) ;
138+
139+ const handleSearchChange = useCallback (
140+ ( search : string ) => {
141+ setSearchTerm ( search ) ;
142+ // Reset to first page when search changes
143+ if ( page !== 0 ) {
144+ setPage ( 0 ) ;
145+ }
146+ } ,
147+ [ page ] ,
148+ ) ;
149+
150+ // Get the NFT records for the current page
151+ const nftItems = useMemo ( ( ) => {
152+ return pageNftIds . map ( ( id ) => nfts [ id ] ) . filter ( Boolean ) as NftRecord [ ] ;
153+ } , [ pageNftIds , nfts ] ) ;
154+
155+ const renderNft = useCallback (
156+ ( nft : NftRecord ) => (
157+ < div className = 'flex items-center gap-2 min-w-0' >
158+ < img
159+ src = { nftThumbnails [ nft . launcher_id ] ?? defaultNftImage }
160+ className = 'w-10 h-10 rounded object-cover flex-shrink-0'
161+ alt = ''
162+ aria-hidden = 'true'
163+ loading = 'lazy'
164+ />
165+ < div className = 'flex flex-col min-w-0' >
166+ < span className = 'truncate' role = 'text' >
167+ { nft . name ?? 'Unknown NFT' }
168+ </ span >
169+ < span
170+ className = 'text-xs text-muted-foreground truncate'
171+ aria-label = 'NFT ID'
172+ >
173+ { nft . launcher_id }
174+ </ span >
182175 </ div >
183- ) }
176+ </ div >
177+ ) ,
178+ [ nftThumbnails , defaultNftImage ] ,
179+ ) ;
180+
181+ const validateNftId = useCallback ( ( value : string ) => {
182+ return isValidAddress ( value , 'nft' ) ;
183+ } , [ ] ) ;
184+
185+ return (
186+ < SearchableSelect
187+ value = { value || undefined }
188+ onSelect = { handleSelect }
189+ items = { nftItems }
190+ getItemId = { ( nft ) => nft . launcher_id }
191+ renderItem = { renderNft }
192+ onSearchChange = { handleSearchChange }
193+ shouldFilter = { false }
194+ validateManualInput = { validateNftId }
195+ onManualInput = { handleManualInput }
196+ page = { page }
197+ onPageChange = { setPage }
198+ pageSize = { pageSize }
199+ hasMorePages = { pageNftIds . length >= pageSize }
200+ disabled = { disabled }
201+ className = { className }
202+ placeholder = { t `Select NFT` }
203+ searchPlaceholder = { t `Search by name or enter NFT ID` }
204+ emptyMessage = { t `No NFTs found.` }
184205 />
185206 ) ;
186207}
0 commit comments