add filter badges to search-box

This commit is contained in:
2026-02-01 14:26:44 +01:00
parent 683b3a51f7
commit 2e51ea37cc
4 changed files with 207 additions and 3 deletions

View File

@@ -146,9 +146,96 @@ class SearchBox extends HTMLElement {
</button>
</div>
</form>
${this.renderFilterBadges()}
`
}
hasActiveFilters() {
return this.searchQuery || this.selectedCategory || this.useCurrentLocation
}
getActiveFilterCount() {
let count = 0
if (this.searchQuery) count++
if (this.selectedCategory) count++
if (this.useCurrentLocation) count++
return count
}
renderFilterBadges() {
if (!this.hasActiveFilters()) return ''
const badges = []
// Query badge
if (this.searchQuery) {
badges.push(/* html */`
<button type="button" class="filter-badge" data-filter="query">
<span class="filter-badge-text">"${this.escapeHtml(this.searchQuery)}"</span>
<svg class="filter-badge-close" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`)
}
// Category badge
if (this.selectedCategory) {
const categoryLabel = this.selectedSubcategory
? `${t(`categories.${this.selectedCategory}`)} ${t(`subcategories.${this.selectedSubcategory}`)}`
: t(`categories.${this.selectedCategory}`)
badges.push(/* html */`
<button type="button" class="filter-badge" data-filter="category">
<span class="filter-badge-text">${categoryLabel}</span>
<svg class="filter-badge-close" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`)
}
// Location badge (only when using current location with radius)
if (this.useCurrentLocation) {
const radiusText = t('search.radiusAround', { radius: this.selectedRadius })
badges.push(/* html */`
<button type="button" class="filter-badge" data-filter="location">
<span class="filter-badge-text">📍 ${radiusText}</span>
<svg class="filter-badge-close" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`)
}
// Only show "clear all" if more than one filter is active
const showClearAll = this.getActiveFilterCount() > 1
return /* html */`
<div class="filter-badges">
${badges.join('')}
${showClearAll ? `
<button type="button" class="filter-badge filter-badge-clear" data-filter="all">
<span class="filter-badge-text">${t('search.clearAll')}</span>
<svg class="filter-badge-close" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
` : ''}
</div>
`
}
escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
renderFilters() {
// Track which category accordion is expanded
this._expandedCategory = this._expandedCategory || ''
@@ -378,16 +465,71 @@ class SearchBox extends HTMLElement {
const handleRadiusChange = (e) => {
this.selectedRadius = parseInt(e.target.value)
this.emitFilterChange()
this.updateFilterBadges()
}
radiusSelect?.addEventListener('change', handleRadiusChange)
radiusSelectMobile?.addEventListener('change', handleRadiusChange)
// Filter badge click handlers
this.querySelectorAll('.filter-badge').forEach(badge => {
badge.addEventListener('click', (e) => {
e.preventDefault()
const filterType = badge.dataset.filter
this.removeFilter(filterType)
})
})
// Adjust select width to selected option (desktop only)
this.adjustSelectWidth(countrySelect)
this.adjustSelectWidth(radiusSelect)
}
updateFilterBadges() {
const badgesContainer = this.querySelector('.filter-badges')
if (badgesContainer) {
badgesContainer.outerHTML = this.renderFilterBadges()
// Re-attach event listeners for new badges
this.querySelectorAll('.filter-badge').forEach(badge => {
badge.addEventListener('click', (e) => {
e.preventDefault()
const filterType = badge.dataset.filter
this.removeFilter(filterType)
})
})
}
}
removeFilter(filterType) {
switch (filterType) {
case 'query':
this.searchQuery = ''
break
case 'category':
this.selectedCategory = ''
this.selectedSubcategory = ''
break
case 'location':
this.useCurrentLocation = false
this.currentLat = null
this.currentLng = null
break
case 'all':
this.searchQuery = ''
this.selectedCategory = ''
this.selectedSubcategory = ''
this.useCurrentLocation = false
this.currentLat = null
this.currentLng = null
break
}
this.saveFiltersToStorage()
this.emitFilterChange()
this.render()
this.setupEventListeners()
}
adjustSelectWidth(select) {
if (!select) return
@@ -960,6 +1102,62 @@ style.textContent = /* css */`
background: var(--color-primary-light);
color: var(--color-primary);
}
/* Filter Badges */
search-box .filter-badges {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
margin-top: var(--space-sm);
justify-content: center;
}
search-box .filter-badge {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
color: var(--color-text);
cursor: pointer;
transition: all var(--transition-fast);
}
search-box .filter-badge:hover {
background: var(--color-bg-tertiary);
border-color: var(--color-text-muted);
}
search-box .filter-badge-text {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
search-box .filter-badge-close {
flex-shrink: 0;
color: var(--color-text-muted);
transition: color var(--transition-fast);
}
search-box .filter-badge:hover .filter-badge-close {
color: var(--color-text);
}
search-box .filter-badge-clear {
background: transparent;
border-style: dashed;
color: var(--color-text-muted);
}
search-box .filter-badge-clear:hover {
background: var(--color-bg-secondary);
color: var(--color-text);
}
`
document.head.appendChild(style)

View File

@@ -52,7 +52,9 @@
"enterQuery": "Gib einen Suchbegriff ein, um Anzeigen zu finden.",
"noResults": "Keine Ergebnisse gefunden. Versuche einen anderen Suchbegriff.",
"resultsCount": "{{count}} Ergebnisse gefunden",
"allIn": "Alles in"
"allIn": "Alles in",
"clearAll": "Alle löschen",
"radiusAround": "{{radius}} km Umkreis"
},
"subcategories": {
"phones": "Handy & Telefon",

View File

@@ -52,7 +52,9 @@
"enterQuery": "Enter a search term to find listings.",
"noResults": "No results found. Try a different search term.",
"resultsCount": "{{count}} results found",
"allIn": "All in"
"allIn": "All in",
"clearAll": "Clear all",
"radiusAround": "{{radius}} km radius"
},
"subcategories": {
"phones": "Phones & Tablets",

View File

@@ -52,7 +52,9 @@
"enterQuery": "Entrez un terme de recherche pour trouver des annonces.",
"noResults": "Aucun résultat trouvé. Essayez un autre terme de recherche.",
"resultsCount": "{{count}} résultats trouvés",
"allIn": "Tout dans"
"allIn": "Tout dans",
"clearAll": "Tout effacer",
"radiusAround": "{{radius}} km autour"
},
"subcategories": {
"phones": "Téléphones & Tablettes",