Skip to content

Commit 0aaabaf

Browse files
committed
feat: add empty state on search article
1 parent c9085e7 commit 0aaabaf

3 files changed

Lines changed: 56 additions & 33 deletions

File tree

apps/blog/src/components/TagFilter.tsx

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

apps/blog/src/i18n/ui.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const ui = {
77
'index.heading.prefix': 'Latest',
88
'index.heading.highlight': 'articles',
99
'index.empty': 'No articles yet.',
10+
'index.noResults': 'No articles match your search.',
1011

1112
// RSS
1213
'rss.title': 'Explainer Blog',
@@ -51,6 +52,7 @@ export const ui = {
5152
'index.heading.prefix': 'Derniers',
5253
'index.heading.highlight': 'articles',
5354
'index.empty': 'Aucun article pour le moment.',
55+
'index.noResults': 'Aucun article ne correspond à votre recherche.',
5456

5557
// RSS
5658
'rss.title': 'Blog Explainer',

apps/blog/src/pages/index.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ const t = useTranslations(locale)
4848
</div>
4949
)}
5050

51-
<!-- Empty state -->
51+
<!-- Empty state (no posts at all) -->
5252
{posts.length === 0 && (
5353
<div class="text-center py-20">
5454
<p class="text-muted-foreground text-lg">{t('index.empty')}</p>
5555
</div>
5656
)}
57+
5758
</Base>
5859

5960
<script define:vars={{ supportedLocales: locales, fallbackLocale: locale }}>

0 commit comments

Comments
 (0)