@@ -18,13 +18,58 @@ import { cn, formatPercentage, formatPrice } from '@/lib/utils';
1818import useSWR from 'swr' ;
1919import { useDebounce , useKey } from 'react-use' ;
2020
21+ const TRENDING_LIMIT = 8 ;
22+ const SEARCH_LIMIT = 10 ;
23+
24+ const SearchItem = ( { coin, onSelect, isActiveName } : SearchItemProps ) => {
25+ const isSearchCoin =
26+ typeof coin . data ?. price_change_percentage_24h === 'number' ;
27+
28+ const change = isSearchCoin
29+ ? ( coin as SearchCoin ) . data ?. price_change_percentage_24h ?? 0
30+ : ( coin as TrendingCoin [ 'item' ] ) . data . price_change_percentage_24h ?. usd ?? 0 ;
31+
32+ const price = isSearchCoin ? coin . data ?. price : coin . data . price ;
33+
34+ return (
35+ < CommandItem
36+ value = { coin . id }
37+ onSelect = { ( ) => onSelect ( coin . id ) }
38+ className = 'search-item'
39+ >
40+ < div className = 'coin-info' >
41+ < Image src = { coin . thumb } alt = { coin . name } width = { 32 } height = { 32 } />
42+
43+ < div >
44+ < p className = { cn ( 'font-bold' , isActiveName && 'text-white' ) } >
45+ { coin . name }
46+ </ p >
47+ < p className = 'coin-symbol' > { coin . symbol } </ p >
48+ </ div >
49+ </ div >
50+
51+ { ! price && < span className = 'coin-price' > { formatPrice ( price ) } </ span > }
52+
53+ < p
54+ className = { cn ( 'coin-change' , {
55+ 'text-green-500' : change > 0 ,
56+ 'text-red-500' : change < 0 ,
57+ } ) }
58+ >
59+ { formatPercentage ( change ) }
60+ </ p >
61+ </ CommandItem >
62+ ) ;
63+ } ;
64+
2165export const SearchModal = ( {
2266 initialTrendingCoins = [ ] ,
2367} : {
2468 initialTrendingCoins : TrendingCoin [ ] ;
2569} ) => {
2670 const router = useRouter ( ) ;
2771 const [ open , setOpen ] = useState ( false ) ;
72+
2873 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
2974 const [ debouncedQuery , setDebouncedQuery ] = useState ( '' ) ;
3075
@@ -36,10 +81,9 @@ export const SearchModal = ({
3681 [ searchQuery ]
3782 ) ;
3883
39- const {
40- data : searchResults = [ ] ,
41- isValidating : isSearching ,
42- } = useSWR < SearchCoin [ ] > (
84+ const { data : searchResults = [ ] , isValidating : isSearching } = useSWR <
85+ SearchCoin [ ]
86+ > (
4387 debouncedQuery ? [ 'coin-search' , debouncedQuery ] : null ,
4488 ( [ , query ] ) => searchCoins ( query as string ) ,
4589 {
@@ -48,7 +92,8 @@ export const SearchModal = ({
4892 ) ;
4993
5094 useKey (
51- ( event ) => event . key ?. toLowerCase ( ) === 'k' && ( event . metaKey || event . ctrlKey ) ,
95+ ( event ) =>
96+ event . key ?. toLowerCase ( ) === 'k' && ( event . metaKey || event . ctrlKey ) ,
5297 ( event ) => {
5398 event . preventDefault ( ) ;
5499 setOpen ( ( prev ) => ! prev ) ;
@@ -65,155 +110,86 @@ export const SearchModal = ({
65110 } ;
66111
67112 const hasQuery = debouncedQuery . length > 0 ;
68- const trendingCoins = initialTrendingCoins ;
113+ const trendingCoins = initialTrendingCoins . slice ( 0 , TRENDING_LIMIT ) ;
69114 const showTrending = ! hasQuery && trendingCoins . length > 0 ;
70115
116+ const isSearchEmpty = ! isSearching && ! hasQuery && ! showTrending ;
117+ const isTrendingListVisible = ! isSearching && showTrending ;
118+
119+ const isNoResults = ! isSearching && hasQuery && searchResults . length === 0 ;
120+ const isResultsVisible = ! isSearching && hasQuery && searchResults . length > 0 ;
121+
71122 return (
72- < >
73- < Button
74- variant = 'ghost'
75- onClick = { ( ) => setOpen ( true ) }
76- className = 'search-button'
77- >
123+ < div id = 'search-modal' >
124+ < Button variant = 'ghost' onClick = { ( ) => setOpen ( true ) } className = 'trigger' >
78125 < SearchIcon size = { 18 } />
79126 Search
80- < kbd className = 'search- kbd' >
127+ < kbd className = 'kbd' >
81128 < span className = 'text-xs' > ⌘</ span > K
82129 </ kbd >
83130 </ Button >
84131
85- { /* Dialog */ }
86132 < CommandDialog
87133 open = { open }
88134 onOpenChange = { setOpen }
89- className = 'search-dialog'
135+ className = 'dialog'
136+ data-search-modal
90137 >
91- < div className = 'bg-dark-500 ' >
138+ < div className = 'cmd-input ' >
92139 < CommandInput
93- className = 'placeholder:text-purple-100'
94140 placeholder = 'Search for a token by name or symbol...'
95141 value = { searchQuery }
96142 onValueChange = { setSearchQuery }
97143 />
98144 </ div >
99145
100- < CommandList className = 'custom-scrollbar search-list ' >
101- { isSearching && < div className = 'search- empty' > Searching...</ div > }
146+ < CommandList className = 'list custom-scrollbar' >
147+ { isSearching && < div className = 'empty' > Searching...</ div > }
102148
103- { ! isSearching && ! hasQuery && ! showTrending && (
104- < div className = 'search- empty' > Type to search for coins...</ div >
149+ { isSearchEmpty && (
150+ < div className = 'empty' > Type to search for coins...</ div >
105151 ) }
106152
107- { ! isSearching && showTrending && (
153+ { isTrendingListVisible && (
108154 < CommandGroup
109155 heading = {
110- < div className = 'search- heading' >
156+ < div className = 'heading' >
111157 < TrendingUp size = { 16 } />
112158 Trending Coins
113159 </ div >
114160 }
115- className = 'bg-dark-500 '
161+ className = 'group '
116162 >
117- { trendingCoins . slice ( 0 , 8 ) . map ( ( trendingCoin ) => {
118- const coin = trendingCoin . item ;
119-
120- return (
121- < CommandItem
122- key = { coin . id }
123- value = { coin . id }
124- onSelect = { ( ) => handleSelect ( coin . id ) }
125- className = 'search-item'
126- >
127- < div className = 'search-coin-info' >
128- < Image
129- src = { coin . thumb }
130- alt = { coin . name }
131- width = { 32 }
132- height = { 32 }
133- className = 'search-coin-image'
134- />
135- < div className = 'flex flex-col' >
136- < p className = 'font-bold' > { coin . name } </ p >
137- < p className = 'search-coin-symbol' > { coin . symbol } </ p >
138- </ div >
139- </ div >
140-
141- < span className = 'search-coin-price' >
142- { formatPrice ( coin . data . price ) }
143- </ span >
144-
145- < p
146- className = { cn ( 'search-coin-change' , {
147- 'text-green-500' :
148- coin . data . price_change_percentage_24h . usd > 0 ,
149- 'text-red-500' :
150- coin . data . price_change_percentage_24h . usd < 0 ,
151- } ) }
152- >
153- { formatPercentage (
154- coin . data . price_change_percentage_24h . usd
155- ) }
156- </ p >
157- </ CommandItem >
158- ) ;
159- } ) }
163+ { trendingCoins . map ( ( { item } ) => (
164+ < SearchItem
165+ key = { item . id }
166+ coin = { item }
167+ onSelect = { handleSelect }
168+ isActiveName = { false }
169+ />
170+ ) ) }
160171 </ CommandGroup >
161172 ) }
162173
163- { ! isSearching && hasQuery && searchResults . length === 0 && (
164- < CommandEmpty > No coins found.</ CommandEmpty >
165- ) }
174+ { isNoResults && < CommandEmpty > No coins found.</ CommandEmpty > }
166175
167- { ! isSearching && hasQuery && searchResults . length > 0 && (
176+ { isResultsVisible && (
168177 < CommandGroup
169- heading = { < p className = 'search- heading' > Search Results</ p > }
170- className = 'search- group'
178+ heading = { < p className = 'heading' > Search Results</ p > }
179+ className = 'group'
171180 >
172- { searchResults . slice ( 0 , 10 ) . map ( ( coin ) => {
173- return (
174- < CommandItem
175- key = { coin . id }
176- value = { coin . id }
177- onSelect = { ( ) => handleSelect ( coin . id ) }
178- className = 'search-item'
179- >
180- < div className = 'search-coin-info' >
181- < Image
182- src = { coin . thumb }
183- alt = { coin . name }
184- width = { 32 }
185- height = { 32 }
186- className = 'search-coin-image'
187- />
188- < div className = 'flex flex-col' >
189- < p className = 'font-bold text-white' > { coin . name } </ p >
190- < p className = 'search-coin-symbol' > { coin . symbol } </ p >
191- </ div >
192- </ div >
193-
194- { coin . data ?. price && (
195- < span className = 'search-coin-price' >
196- { formatPrice ( coin . data . price ) }
197- </ span >
198- ) }
199-
200- < p
201- className = { cn ( 'search-coin-change' , {
202- 'text-green-500' :
203- coin . data ?. price_change_percentage_24h > 0 ,
204- 'text-red-500' :
205- coin . data ?. price_change_percentage_24h < 0 ,
206- } ) }
207- >
208- { formatPercentage ( coin . data ?. price_change_percentage_24h ) }
209- </ p >
210- </ CommandItem >
211- ) ;
212- } ) }
181+ { searchResults . slice ( 0 , SEARCH_LIMIT ) . map ( ( coin ) => (
182+ < SearchItem
183+ key = { coin . id }
184+ coin = { coin }
185+ onSelect = { handleSelect }
186+ isActiveName
187+ />
188+ ) ) }
213189 </ CommandGroup >
214190 ) }
215191 </ CommandList >
216192 </ CommandDialog >
217- </ >
193+ </ div >
218194 ) ;
219195} ;
0 commit comments