@@ -39,6 +39,25 @@ const getRandomPastelColor = () => {
39
39
return pastelColors [ randomIndex ] ;
40
40
} ;
41
41
42
+ // Function to update URL query parameters
43
+ const updateUrlWithTag = ( tag : string | null ) => {
44
+ const url = new URL ( window . location . href ) ;
45
+
46
+ if ( tag ) {
47
+ url . searchParams . set ( 'tag' , tag ) ;
48
+ } else {
49
+ url . searchParams . delete ( 'tag' ) ;
50
+ }
51
+
52
+ window . history . pushState ( { } , '' , url ) ;
53
+ } ;
54
+
55
+ // Function to get tag from URL query parameters
56
+ const getTagFromUrl = ( ) : string | null => {
57
+ const params = new URLSearchParams ( window . location . search ) ;
58
+ return params . get ( 'tag' ) ;
59
+ } ;
60
+
42
61
function App ( ) {
43
62
const [ tools , setTools ] = useState < Tool [ ] > ( [ ] ) ;
44
63
const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
@@ -84,6 +103,12 @@ function App() {
84
103
. map ( entry => entry [ 0 ] ) ;
85
104
86
105
setPopularTags ( popular ) ;
106
+
107
+ // Check for tag in URL
108
+ const urlTag = getTagFromUrl ( ) ;
109
+ if ( urlTag && Array . from ( tags ) . includes ( urlTag ) ) {
110
+ setSelectedTag ( urlTag ) ;
111
+ }
87
112
} catch ( error ) {
88
113
console . error ( 'Error loading data:' , error ) ;
89
114
}
@@ -167,15 +192,18 @@ function App() {
167
192
setCurrentPage ( 1 ) ; // Reset to first page on new search
168
193
} ;
169
194
170
- // Handle tag selection
195
+ // Handle tag selection with URL update
171
196
const handleTagSelect = ( tag : string ) => {
172
- setSelectedTag ( selectedTag === tag ? null : tag ) ;
197
+ const newTag = selectedTag === tag ? null : tag ;
198
+ setSelectedTag ( newTag ) ;
199
+ updateUrlWithTag ( newTag ) ;
173
200
setCurrentPage ( 1 ) ; // Reset to first page on tag change
174
201
} ;
175
202
176
- // Clear all filters
203
+ // Clear all filters with URL update
177
204
const clearFilters = ( ) => {
178
205
setSelectedTag ( null ) ;
206
+ updateUrlWithTag ( null ) ;
179
207
setSearchTerm ( '' ) ;
180
208
setCurrentPage ( 1 ) ;
181
209
} ;
@@ -186,6 +214,19 @@ function App() {
186
214
const indexOfFirstItem = indexOfLastItem - itemsPerPage ;
187
215
const currentTools = filteredTools . slice ( indexOfFirstItem , indexOfLastItem ) ;
188
216
217
+ // Handle URL changes from browser back/forward buttons
218
+ useEffect ( ( ) => {
219
+ const handlePopState = ( ) => {
220
+ const urlTag = getTagFromUrl ( ) ;
221
+ setSelectedTag ( urlTag ) ;
222
+ } ;
223
+
224
+ window . addEventListener ( 'popstate' , handlePopState ) ;
225
+ return ( ) => {
226
+ window . removeEventListener ( 'popstate' , handlePopState ) ;
227
+ } ;
228
+ } , [ ] ) ;
229
+
189
230
// Handle page change
190
231
const handlePageChange = ( pageNumber : number ) => {
191
232
setCurrentPage ( pageNumber ) ;
@@ -206,7 +247,7 @@ function App() {
206
247
heroVisible = { heroVisible }
207
248
/>
208
249
209
- < div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-12 flex-grow" >
250
+ < div className = "max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-12 flex-grow" >
210
251
< header className = "text-center mb-12 pb-6 border-b" id = "about" >
211
252
{ /**@ts -ignore */ }
212
253
< h1 ref = { heroRef } className = "text-4xl font-bold text-primary mb-3" > Awesome Android Tooling</ h1 >
@@ -215,16 +256,19 @@ function App() {
215
256
</ p >
216
257
</ header >
217
258
218
- < div className = "mb-8" >
259
+ { /* Container with fixed width to prevent layout shifts */ }
260
+ < div className = "w-full mb-8" >
219
261
{ /* Enhanced SearchBar with integrated filters */ }
220
- < SearchBar
221
- value = { searchTerm }
222
- onChange = { handleSearchChange }
223
- onTagSelect = { handleTagSelect }
224
- selectedTag = { selectedTag }
225
- allTags = { allTags }
226
- popularTags = { popularTags }
227
- />
262
+ < div className = "w-full" >
263
+ < SearchBar
264
+ value = { searchTerm }
265
+ onChange = { handleSearchChange }
266
+ onTagSelect = { handleTagSelect }
267
+ selectedTag = { selectedTag }
268
+ allTags = { allTags }
269
+ popularTags = { popularTags }
270
+ />
271
+ </ div >
228
272
229
273
{ /* Selected and popular tags display below search bar */ }
230
274
< div className = "mt-3 flex flex-wrap gap-2" >
@@ -279,65 +323,81 @@ function App() {
279
323
) }
280
324
</ div >
281
325
282
- < div className = "pt-4 tools-section" >
283
- { filteredTools . length > 0 ? (
284
- < >
285
- < MasonryGrid >
286
- { currentTools . map ( ( tool ) => (
287
- < Card
288
- key = { tool . name }
289
- className = { cardColors [ tool . name ] || '' }
290
- >
291
- < CardHeader >
292
- < CardTitle > { tool . name } </ CardTitle >
293
- < CardDescription > { tool . description } </ CardDescription >
294
- </ CardHeader >
295
- < CardContent >
296
- < div className = "flex flex-wrap gap-2" >
297
- { tool . tags . map ( tag => (
298
- < span
299
- key = { tag }
300
- className = { `px-2 py-1 rounded text-xs cursor-pointer ${
301
- selectedTag === tag
302
- ? 'bg-primary/20 text-primary font-medium'
303
- : 'bg-muted text-muted-foreground hover:bg-muted/80'
304
- } `}
305
- onClick = { ( ) => handleTagSelect ( tag ) }
306
- >
307
- { tag }
308
- </ span >
309
- ) ) }
310
- </ div >
311
- </ CardContent >
312
- < CardFooter >
313
- < a
314
- href = { tool . link }
315
- target = "_blank"
316
- rel = "noopener noreferrer"
317
- className = "text-primary hover:text-primary/90 hover:underline font-medium inline-flex items-center"
318
- >
319
- More Details →
320
- </ a >
321
- </ CardFooter >
322
- </ Card >
323
- ) ) }
324
- </ MasonryGrid >
325
-
326
- < Pagination
327
- currentPage = { currentPage }
328
- totalPages = { totalPages }
329
- onPageChange = { handlePageChange }
330
- />
331
-
332
- < div className = "text-center mt-4 text-sm text-muted-foreground" >
333
- Showing { indexOfFirstItem + 1 } -{ Math . min ( indexOfLastItem , filteredTools . length ) } of { filteredTools . length } tools
326
+ < div className = "pt-4 tools-section w-full" >
327
+ < div className = "w-full grid grid-cols-1" >
328
+ { filteredTools . length > 0 ? (
329
+ < div className = "w-full" >
330
+ < MasonryGrid >
331
+ { currentTools . map ( ( tool ) => (
332
+ < Card
333
+ key = { tool . name }
334
+ className = { cardColors [ tool . name ] || '' }
335
+ >
336
+ < CardHeader >
337
+ < CardTitle > { tool . name } </ CardTitle >
338
+ < CardDescription > { tool . description } </ CardDescription >
339
+ </ CardHeader >
340
+ < CardContent >
341
+ < div className = "flex flex-wrap gap-2" >
342
+ { tool . tags . map ( tag => (
343
+ < span
344
+ key = { tag }
345
+ className = { `px-2 py-1 rounded text-xs cursor-pointer ${
346
+ selectedTag === tag
347
+ ? 'bg-primary/20 text-primary font-medium'
348
+ : 'bg-muted text-muted-foreground hover:bg-muted/80'
349
+ } `}
350
+ onClick = { ( ) => handleTagSelect ( tag ) }
351
+ >
352
+ { tag }
353
+ </ span >
354
+ ) ) }
355
+ </ div >
356
+ </ CardContent >
357
+ < CardFooter >
358
+ < a
359
+ href = { tool . link }
360
+ target = "_blank"
361
+ rel = "noopener noreferrer"
362
+ className = "text-primary hover:text-primary/90 hover:underline font-medium inline-flex items-center"
363
+ >
364
+ More Details →
365
+ </ a >
366
+ </ CardFooter >
367
+ </ Card >
368
+ ) ) }
369
+ </ MasonryGrid >
370
+
371
+ < Pagination
372
+ currentPage = { currentPage }
373
+ totalPages = { totalPages }
374
+ onPageChange = { handlePageChange }
375
+ />
376
+
377
+ < div className = "text-center mt-4 text-sm text-muted-foreground" >
378
+ Showing { indexOfFirstItem + 1 } -{ Math . min ( indexOfLastItem , filteredTools . length ) } of { filteredTools . length } tools
379
+ </ div >
334
380
</ div >
335
- </ >
336
- ) : (
337
- < div className = "text-center py-12" >
338
- < p className = "text-muted-foreground" > No tools found matching your search criteria.</ p >
339
- </ div >
340
- ) }
381
+ ) : (
382
+ < div className = "w-full min-h-[400px] flex items-center justify-center" >
383
+ < div className = "text-center max-w-md mx-auto p-8 border border-border rounded-lg bg-card shadow-sm" >
384
+ < svg xmlns = "http://www.w3.org/2000/svg" className = "h-12 w-12 mx-auto mb-4 text-muted-foreground/60" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
385
+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 1.5 } d = "M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
386
+ </ svg >
387
+ < h3 className = "text-lg font-medium mb-2" > No results found</ h3 >
388
+ < p className = "text-muted-foreground" > No tools found matching your search criteria. Try adjusting your search or filters.</ p >
389
+ { searchTerm || selectedTag ? (
390
+ < button
391
+ onClick = { clearFilters }
392
+ className = "mt-4 px-4 py-2 rounded-md text-sm bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
393
+ >
394
+ Clear all filters
395
+ </ button >
396
+ ) : null }
397
+ </ div >
398
+ </ div >
399
+ ) }
400
+ </ div >
341
401
</ div >
342
402
</ div >
343
403
0 commit comments