mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:41:09 +02:00
feat(zitare): add category filter chips to search results
Show clickable category pills below the search input when results are available. Each chip shows the match count for that category. Also respects display settings for category/source visibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6107d572a1
commit
b7d1d2ec9a
3 changed files with 81 additions and 11 deletions
|
|
@ -110,7 +110,9 @@
|
|||
"createList": "als Liste erstellen",
|
||||
"createListDescription": "Neue Liste mit diesem Namen erstellen",
|
||||
"minChars": "Bitte gib mindestens 2 Zeichen ein",
|
||||
"hint": "Suche nach Zitaten, Autoren oder Themen"
|
||||
"hint": "Suche nach Zitaten, Autoren oder Themen",
|
||||
"allCategories": "Alle",
|
||||
"filterByCategory": "Nach Kategorie filtern"
|
||||
},
|
||||
"settings": {
|
||||
"quoteLanguage": "Zitat-Sprache",
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@
|
|||
"createList": "create as list",
|
||||
"createListDescription": "Create a new list with this name",
|
||||
"minChars": "Please enter at least 2 characters",
|
||||
"hint": "Search for quotes, authors, or topics"
|
||||
"hint": "Search for quotes, authors, or topics",
|
||||
"allCategories": "All",
|
||||
"filterByCategory": "Filter by category"
|
||||
},
|
||||
"settings": {
|
||||
"quoteLanguage": "Quote language",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,55 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { searchQuotes, type Quote } from '@zitare/content';
|
||||
import { searchQuotes, getAllCategories, type Quote, type Category } from '@zitare/content';
|
||||
import { ZitareEvents } from '@manacore/shared-utils/analytics';
|
||||
import { quotesStore } from '$lib/stores/quotes.svelte';
|
||||
import { zitareSettings } from '$lib/stores/settings.svelte';
|
||||
import QuoteCard from '$lib/components/QuoteCard.svelte';
|
||||
|
||||
let searchTerm = $state('');
|
||||
let lastTrackedTerm = $state('');
|
||||
let selectedCategory = $state<Category | null>(null);
|
||||
|
||||
// Search results
|
||||
let results = $derived<Quote[]>(
|
||||
searchTerm.length >= 2 ? searchQuotes(searchTerm, quotesStore.language) : []
|
||||
);
|
||||
const categories = getAllCategories();
|
||||
|
||||
// Track search when results change (debounced by derived reactivity)
|
||||
// Category i18n key mapping
|
||||
const categoryKeys: Record<Category, string> = {
|
||||
weisheit: 'categories.wisdom',
|
||||
motivation: 'categories.motivation',
|
||||
liebe: 'categories.love',
|
||||
leben: 'categories.life',
|
||||
erfolg: 'categories.success',
|
||||
glueck: 'categories.happiness',
|
||||
freundschaft: 'categories.friendship',
|
||||
mut: 'categories.courage',
|
||||
hoffnung: 'categories.hope',
|
||||
natur: 'categories.nature',
|
||||
};
|
||||
|
||||
// Search results with optional category filter
|
||||
let results = $derived<Quote[]>(() => {
|
||||
if (searchTerm.length < 2) return [];
|
||||
const searchResults = searchQuotes(searchTerm, quotesStore.language);
|
||||
if (!selectedCategory) return searchResults;
|
||||
return searchResults.filter((q) => q.category === selectedCategory);
|
||||
});
|
||||
|
||||
// Track search when results change
|
||||
$effect(() => {
|
||||
if (searchTerm.length >= 2 && searchTerm !== lastTrackedTerm) {
|
||||
lastTrackedTerm = searchTerm;
|
||||
ZitareEvents.searchPerformed(results.length);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleCategory(cat: Category) {
|
||||
selectedCategory = selectedCategory === cat ? null : cat;
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
searchTerm = '';
|
||||
selectedCategory = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -30,7 +60,7 @@
|
|||
<h1 class="text-3xl font-bold text-foreground mb-6">{$_('search.title')}</h1>
|
||||
|
||||
<!-- Search Input -->
|
||||
<div class="relative mb-8">
|
||||
<div class="relative mb-4">
|
||||
<svg
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-foreground-muted"
|
||||
fill="none"
|
||||
|
|
@ -52,7 +82,7 @@
|
|||
/>
|
||||
{#if searchTerm}
|
||||
<button
|
||||
onclick={() => (searchTerm = '')}
|
||||
onclick={clearSearch}
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 p-1 text-foreground-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
|
|
@ -67,6 +97,38 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Category Filter Chips -->
|
||||
{#if searchTerm.length >= 2 && results.length > 0}
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
<button
|
||||
onclick={() => (selectedCategory = null)}
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition-colors {selectedCategory ===
|
||||
null
|
||||
? 'bg-primary text-white'
|
||||
: 'bg-surface-elevated text-foreground-secondary hover:text-foreground border border-border'}"
|
||||
>
|
||||
{$_('search.allCategories')}
|
||||
</button>
|
||||
{#each categories as cat}
|
||||
{@const matchCount = searchQuotes(searchTerm, quotesStore.language).filter(
|
||||
(q) => q.category === cat.name
|
||||
).length}
|
||||
{#if matchCount > 0}
|
||||
<button
|
||||
onclick={() => toggleCategory(cat.name)}
|
||||
class="px-3 py-1.5 rounded-full text-sm font-medium transition-colors {selectedCategory ===
|
||||
cat.name
|
||||
? 'bg-primary text-white'
|
||||
: 'bg-surface-elevated text-foreground-secondary hover:text-foreground border border-border'}"
|
||||
>
|
||||
{$_(categoryKeys[cat.name])}
|
||||
<span class="ml-1 opacity-70">{matchCount}</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Results -->
|
||||
{#if searchTerm.length >= 2}
|
||||
{#if results.length === 0}
|
||||
|
|
@ -93,7 +155,11 @@
|
|||
</p>
|
||||
<div class="space-y-6">
|
||||
{#each results as quote (quote.id)}
|
||||
<QuoteCard {quote} showCategory showSource />
|
||||
<QuoteCard
|
||||
{quote}
|
||||
showCategory={zitareSettings.showCategory}
|
||||
showSource={zitareSettings.showSource}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue