@@ -58,9 +58,13 @@ export function TagFilter({ tags, initialTags = [], locale = 'en' }: TagFilterPr
5858 )
5959 }
6060
61+ const [ visibleCount , setVisibleCount ] = useState < number | null > ( null )
62+ const hasActiveFilter = selectedTags . length > 0 || query . trim ( ) . length > 0
63+
6164 useEffect ( ( ) => {
6265 const cards = document . querySelectorAll < HTMLElement > ( '[data-tags]' )
6366 const normalizedQuery = query . toLowerCase ( ) . trim ( )
67+ let count = 0
6468
6569 cards . forEach ( ( card ) => {
6670 const cardTags : string [ ] = JSON . parse ( card . dataset . tags ?? '[]' )
@@ -72,46 +76,62 @@ export function TagFilter({ tags, initialTags = [], locale = 'en' }: TagFilterPr
7276 ( card . dataset . title ?? '' ) . toLowerCase ( ) . includes ( normalizedQuery ) ||
7377 ( card . dataset . description ?? '' ) . toLowerCase ( ) . includes ( normalizedQuery )
7478
75- card . hidden = ! matchesTags || ! matchesQuery
79+ const visible = matchesTags && matchesQuery
80+ card . hidden = ! visible
81+ if ( visible && card . style . display !== 'none' ) count ++
7682 } )
7783
84+ setVisibleCount ( count )
7885 writeURL ( selectedTags , query )
7986 window . dispatchEvent ( new Event ( 'tags:filter' ) )
8087 } , [ selectedTags , query ] )
8188
8289 return (
83- < div className = "mb-10 space-y-4" >
84- < div className = "relative" >
85- < SearchIcon className = "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
86- < input
87- type = "text"
88- value = { query }
89- onChange = { ( e ) => setQuery ( e . target . value ) }
90- placeholder = { t ( 'tagFilter.placeholder' ) }
91- className = "w-full rounded-md border border-border bg-transparent pl-10 pr-4 py-2 max-w-sm text-sm text-foreground placeholder:text-muted-foreground outline-none transition-colors focus:border-primary/50"
92- />
93- </ div >
90+ < >
91+ < div className = "mb-10 space-y-4" >
92+ < div className = "relative" >
93+ < SearchIcon className = "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
94+ < input
95+ type = "text"
96+ value = { query }
97+ onChange = { ( e ) => setQuery ( e . target . value ) }
98+ placeholder = { t ( 'tagFilter.placeholder' ) }
99+ className = "w-full rounded-md border border-border bg-transparent pl-10 pr-4 py-2 max-w-sm text-sm text-foreground placeholder:text-muted-foreground outline-none transition-colors focus:border-primary/50"
100+ />
101+ </ div >
94102
95- < div className = "flex items-center gap-3 overflow-x-auto scrollbar-hide" >
96- { tags . map ( ( tag ) => {
97- const active = selectedTags . includes ( tag . name )
98- return (
99- < button
100- key = { tag . name }
101- type = "button"
102- onClick = { ( ) => toggleTag ( tag . name ) }
103- className = { `flex items-center gap-1.5 rounded-md border px-4 py-1.5 text-sm shrink-0 transition-colors cursor-pointer border-dashed ${
104- active
105- ? 'border-primary/50 bg-primary/5 text-primary font-medium'
106- : 'border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground'
107- } `}
108- >
109- < TagIcon className = "size-3.5" />
110- { tag . name }
111- </ button >
112- )
113- } ) }
103+ < div className = "flex items-center gap-3 overflow-x-auto scrollbar-hide" >
104+ { tags . map ( ( tag ) => {
105+ const active = selectedTags . includes ( tag . name )
106+ return (
107+ < button
108+ key = { tag . name }
109+ type = "button"
110+ onClick = { ( ) => toggleTag ( tag . name ) }
111+ className = { `flex items-center gap-1.5 rounded-md border px-4 py-1.5 text-sm shrink-0 transition-colors cursor-pointer border-dashed ${
112+ active
113+ ? 'border-primary/50 bg-primary/5 text-primary font-medium'
114+ : 'border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground'
115+ } `}
116+ >
117+ < TagIcon className = "size-3.5" />
118+ { tag . name }
119+ </ button >
120+ )
121+ } ) }
122+ </ div >
114123 </ div >
115- </ div >
124+
125+ { hasActiveFilter && visibleCount === 0 && (
126+ < div className = "flex flex-col items-center justify-center gap-4 rounded-xl border-2 border-dashed border-border py-20" >
127+ < svg className = "size-10 text-muted-foreground/50" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "1.5" strokeLinecap = "round" strokeLinejoin = "round" >
128+ < circle cx = "11" cy = "11" r = "8" />
129+ < path d = "m21 21-4.3-4.3" />
130+ < path d = "M8 11h6" />
131+ </ svg >
132+ < p className = "text-muted-foreground text-lg" > { t ( 'index.noResults' ) } </ p >
133+ </ div >
134+ ) }
135+ </ >
116136 )
117137}
0 commit comments