1- import { useCallback , useMemo } from 'react' ;
2- import { useSearchParams } from 'react-router' ;
1+ import { useMemo } from 'react' ;
32
43import categories from '@/data/categories' ;
54import icons from '@/data/icons' ;
@@ -8,82 +7,25 @@ import { IconCard } from '@/design/components/IconCard';
87import { Pagination } from '@/design/components/Pagination' ;
98import { Search } from '@/design/components/Search' ;
109import Header from '@/design/layout/LayoutElements/Header' ;
10+ import { useFilters } from '@/hooks/useFilters' ;
1111import useSearch from '@/hooks/useSearch' ;
1212import { ILibraryIcon } from '@/types' ;
1313
1414import Amicon , { aiFilterXmark , aiXmark } from '@studio384/amicons' ;
1515import clsx from 'clsx' ;
1616
1717export default function Icons ( ) {
18- const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
19-
20- const [ searchCategories , searchQuery , searchPage ] : [ string [ ] , string , number ] = useMemo ( ( ) => {
21- const categories = searchParams . get ( 'category' ) ;
22- const query = searchParams . get ( 'search' ) ;
23- const page = Number ( searchParams . get ( 'page' ) ?? 1 ) ;
24-
25- return [ categories ?. split ( ',' ) . filter ( ( item ) => item !== '' ) ?? [ ] , query ?? '' , page ?? 1 ] ;
26- } , [ searchParams ] ) ;
18+ const filters = useFilters ( ) ;
2719
2820 const searchableList = useMemo ( ( ) => {
29- if ( searchCategories . length >= 1 ) {
30- return icons . filter ( ( icon ) => searchCategories . every ( ( _searchCategory ) => icon . categories . includes ( _searchCategory as never ) ) ) ;
21+ if ( filters . query . categories . length >= 1 ) {
22+ return icons . filter ( ( icon ) => filters . query . categories . every ( ( _searchCategory ) => icon . categories . includes ( _searchCategory as never ) ) ) ;
3123 }
3224
3325 return icons ;
34- } , [ searchCategories ] ) ;
35-
36- const { result } = useSearch ( searchableList , [ 'slug' , 'tags' ] , searchQuery ) ;
37-
38- // c: categories
39- // q: query
40- // p: page
41- const setSearchQuery = useCallback (
42- ( type : 'q' | 'c' | 'p' , value : string | number ) => {
43- let search = searchParams . get ( 'search' ) ;
44- let page = Number ( searchParams . get ( 'page' ) ) ;
45- let category =
46- searchParams
47- . get ( 'category' )
48- ?. split ( ',' )
49- . filter ( ( item ) => item !== '' ) ?? [ ] ;
50-
51- switch ( type ) {
52- case 'c' : {
53- if ( typeof value === 'number' ) return ;
26+ } , [ filters . query . categories ] ) ;
5427
55- if ( category . includes ( value ) ) {
56- category = category . filter ( ( item ) => item !== value ) ;
57- } else {
58- category . push ( value ) ;
59- }
60-
61- page = 1 ; // Always reset page
62- break ;
63- }
64- case 'q' : {
65- if ( typeof value === 'number' ) return ;
66-
67- search = value ;
68- page = 1 ; // Always reset page
69- break ;
70- }
71- case 'p' : {
72- if ( typeof value === 'string' ) return ;
73-
74- page = value ;
75- break ;
76- }
77- }
78-
79- setSearchParams ( {
80- page : ( page || 1 ) . toString ( ) ,
81- search : search ?? '' ,
82- category : category . join ( ',' ) ?? ''
83- } ) ;
84- } ,
85- [ searchParams , setSearchParams ]
86- ) ;
28+ const { result } = useSearch ( searchableList , filters . query . search ) ;
8729
8830 return (
8931 < >
@@ -100,13 +42,13 @@ export default function Icons() {
10042 return (
10143 < button
10244 key = { _category . slug }
103- onClick = { ( ) => setSearchQuery ( 'c' , _category . slug ) }
104- data-selected = { searchCategories . includes ( _category . slug ) || undefined }
45+ onClick = { ( ) => filters . toggleCategory ( _category . slug ) }
46+ data-selected = { filters . query . categories . includes ( _category . slug ) || undefined }
10547 data-noicons = { categoryIcons . length === 0 ? true : undefined }
10648 className = { clsx (
10749 'group grid h-8 grid-cols-[min-content_auto_min-content] items-center gap-2 rounded-sm px-2.5 text-start text-sm hover:cursor-pointer hover:bg-violet-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-violet-500 data-selected:focus-visible:outline-violet-700' ,
10850 {
109- 'bg-violet-500 text-white hover:bg-violet-600' : searchCategories . includes ( _category . slug )
51+ 'bg-violet-500 text-white hover:bg-violet-600' : filters . query . categories . includes ( _category . slug )
11052 }
11153 ) }
11254 >
@@ -123,46 +65,41 @@ export default function Icons() {
12365 < div className = "flex items-baseline gap-2" >
12466 < h2 className = "font-display text-3xl font-medium" > { result . length } icons</ h2 >
12567 < span className = "text-zinc-600" >
126- Page { searchPage } of { Math . ceil ( result . length / 96 ) }
68+ Page { filters . query . page } of { Math . ceil ( result . length / 96 ) }
12769 </ span >
12870 </ div >
12971
13072 < div className = "flex gap-1" >
131- < Search placeholder = "Search" value = { searchQuery } onValueChange = { ( value ) => setSearchQuery ( 'q' , value ) } />
73+ < Search placeholder = "Search" value = { filters . searchValue } onValueChange = { ( value ) => filters . setSearch ( value ) } />
13274 < Button
13375 icon
13476 variant = "secondary"
135- disabled = { searchQuery === '' && searchCategories . length === 0 }
136- onClick = { ( ) => {
137- setSearchParams ( {
138- search : '' ,
139- category : ''
140- } ) ;
141- } }
77+ disabled = { filters . searchValue === '' && filters . query . categories . length === 0 }
78+ onClick = { ( ) => filters . resetQuery ( ) }
14279 >
14380 < Amicon icon = { aiFilterXmark } />
14481 </ Button >
14582 </ div >
14683 </ div >
147- { ( searchQuery || searchCategories . length >= 1 ) && (
84+ { ( filters . query . search || filters . query . categories . length >= 1 ) && (
14885 < div className = "flex gap-1" >
149- { searchQuery && (
86+ { filters . query . search && (
15087 < div className = "font-display flex items-center gap-1 rounded-full bg-zinc-100 py-1 ps-2.5 pe-1 text-sm" >
151- "{ searchQuery } "
88+ "{ filters . query . search } "
15289 < button
15390 className = "text-md flex size-6 cursor-pointer items-center justify-center rounded-full bg-transparent hover:bg-zinc-300"
154- onClick = { ( ) => setSearchQuery ( 'q' , '' ) }
91+ onClick = { ( ) => filters . setSearch ( '' ) }
15592 >
15693 < Amicon icon = { aiXmark } /> < span className = "sr-only" > Delete category</ span >
15794 </ button >
15895 </ div >
15996 ) }
160- { searchCategories . map ( ( category ) => (
97+ { filters . query . categories . map ( ( category ) => (
16198 < div key = { category } className = "font-display flex items-center gap-1 rounded-full bg-zinc-100 py-1 ps-2.5 pe-1 text-sm" >
16299 { category }
163100 < button
164101 className = "text-md flex size-6 cursor-pointer items-center justify-center rounded-full bg-transparent hover:bg-zinc-300"
165- onClick = { ( ) => setSearchQuery ( 'c' , category ) }
102+ onClick = { ( ) => filters . toggleCategory ( category ) }
166103 >
167104 < Amicon icon = { aiXmark } /> < span className = "sr-only" > Delete category</ span >
168105 </ button >
@@ -171,12 +108,12 @@ export default function Icons() {
171108 </ div >
172109 ) }
173110 < div className = "grid grid-cols-[repeat(auto-fill,minmax(min(9rem,100%),1fr))] gap-2" >
174- { result . slice ( ( searchPage - 1 ) * 96 , searchPage * 96 ) . map ( ( icon : ILibraryIcon ) => (
111+ { result . slice ( ( filters . query . page - 1 ) * 96 , filters . query . page * 96 ) . map ( ( icon : ILibraryIcon ) => (
175112 < IconCard key = { icon . slug } icon = { icon } />
176113 ) ) }
177114 </ div >
178115
179- { result . length > 0 && < Pagination count = { Math . ceil ( result . length / 96 ) } page = { searchPage } onChange = { ( _ , page ) => setSearchQuery ( 'p' , page ) } /> }
116+ { result . length > 0 && < Pagination count = { Math . ceil ( result . length / 96 ) } page = { filters . query . page } onChange = { ( _ , page ) => filters . setPage ( page ) } /> }
180117 </ div >
181118 </ div >
182119 </ div >
0 commit comments