refactor: always show sort and collapsible price filter toolbar on home page

This commit is contained in:
2026-02-07 15:55:33 +01:00
parent 4ee13d51ef
commit b77011ec8a

View File

@@ -25,6 +25,7 @@ class PageHome extends HTMLElement {
this.sort = 'newest' this.sort = 'newest'
this.minPrice = null this.minPrice = null
this.maxPrice = null this.maxPrice = null
this.priceFilterOpen = false
// Geo state // Geo state
this.userLat = null this.userLat = null
@@ -155,6 +156,11 @@ class PageHome extends HTMLElement {
return false return false
}) })
// Toggle price filter
this.querySelector('#toggle-price-filter')?.addEventListener('click', () => {
this.togglePriceFilter()
})
// Sort change // Sort change
this.querySelector('#sort-select')?.addEventListener('change', (e) => { this.querySelector('#sort-select')?.addEventListener('change', (e) => {
this.sort = e.target.value this.sort = e.target.value
@@ -190,6 +196,7 @@ class PageHome extends HTMLElement {
this.sort = 'newest' this.sort = 'newest'
this.minPrice = null this.minPrice = null
this.maxPrice = null this.maxPrice = null
this.priceFilterOpen = false
this.updateUrl() this.updateUrl()
this.render() this.render()
this.setupEventListeners() this.setupEventListeners()
@@ -384,18 +391,17 @@ class PageHome extends HTMLElement {
render() { render() {
const showFilters = this.hasActiveFilters() const showFilters = this.hasActiveFilters()
const hasPriceFilter = this.minPrice || this.maxPrice
this.innerHTML = /* html */` this.innerHTML = /* html */`
<section class="search-section"> <section class="search-section">
<search-box no-navigate></search-box> <search-box no-navigate></search-box>
</section> </section>
${showFilters ? this.renderFilterBar() : ''}
<section class="listings-section"> <section class="listings-section">
<div class="listings-header"> <div class="listings-header">
${showFilters ? `<h2 class="listings-title">${this.getListingsTitle()}</h2>` : ''} ${showFilters ? `<h2 class="listings-title">${this.getListingsTitle()}</h2>` : ''}
${!showFilters ? ` <div class="listings-toolbar">
<div class="sort-inline"> <div class="sort-inline">
<select id="sort-select"> <select id="sort-select">
${this.hasUserLocation() ? `<option value="distance" ${this.sort === 'distance' ? 'selected' : ''}>${t('search.sortDistance')}</option>` : ''} ${this.hasUserLocation() ? `<option value="distance" ${this.sort === 'distance' ? 'selected' : ''}>${t('search.sortDistance')}</option>` : ''}
@@ -405,7 +411,35 @@ class PageHome extends HTMLElement {
<option value="price_desc" ${this.sort === 'price_desc' ? 'selected' : ''}>${t('search.sortPriceDesc')}</option> <option value="price_desc" ${this.sort === 'price_desc' ? 'selected' : ''}>${t('search.sortPriceDesc')}</option>
</select> </select>
</div> </div>
` : ''} <button class="btn btn-sm btn-outline" id="toggle-price-filter">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="4" y1="21" x2="4" y2="14"></line>
<line x1="4" y1="10" x2="4" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12" y2="3"></line>
<line x1="20" y1="21" x2="20" y2="16"></line>
<line x1="20" y1="12" x2="20" y2="3"></line>
<line x1="1" y1="14" x2="7" y2="14"></line>
<line x1="9" y1="8" x2="15" y2="8"></line>
<line x1="17" y1="16" x2="23" y2="16"></line>
</svg>
<span>${t('search.priceRange')}</span>
${hasPriceFilter ? '<span class="filter-active-dot"></span>' : ''}
</button>
${showFilters ? `
<button class="btn btn-sm btn-ghost" id="clear-filters">${t('search.clearAll')}</button>
` : ''}
</div>
</div>
<div class="price-filter-panel ${this.priceFilterOpen ? 'open' : ''}" id="price-filter-panel">
<div class="price-inputs">
<input type="number" id="min-price" placeholder="${t('search.min')}"
value="${this.minPrice || ''}" min="0" step="1">
<span class="price-separator"></span>
<input type="number" id="max-price" placeholder="${t('search.max')}"
value="${this.maxPrice || ''}" min="0" step="1">
<button class="btn btn-sm btn-primary" id="apply-price">${t('search.apply')}</button>
</div>
</div> </div>
<div id="listings-container" class="listings-grid"> <div id="listings-container" class="listings-grid">
${this.renderListings()} ${this.renderListings()}
@@ -423,39 +457,10 @@ class PageHome extends HTMLElement {
} }
} }
renderFilterBar() { togglePriceFilter() {
return /* html */` this.priceFilterOpen = !this.priceFilterOpen
<section class="filter-bar"> const panel = this.querySelector('#price-filter-panel')
<div class="filter-row"> if (panel) panel.classList.toggle('open', this.priceFilterOpen)
<div class="price-filter">
<label>${t('search.priceRange')}</label>
<div class="price-inputs">
<input type="number" id="min-price" placeholder="${t('search.min')}"
value="${this.minPrice || ''}" min="0" step="1">
<span class="price-separator"></span>
<input type="number" id="max-price" placeholder="${t('search.max')}"
value="${this.maxPrice || ''}" min="0" step="1">
<button class="btn btn-sm btn-outline" id="apply-price">${t('search.apply')}</button>
</div>
</div>
<div class="sort-filter">
<label for="sort-select">${t('search.sortBy')}</label>
<select id="sort-select">
${this.hasUserLocation() ? `<option value="distance" ${this.sort === 'distance' ? 'selected' : ''}>${t('search.sortDistance')}</option>` : ''}
<option value="newest" ${this.sort === 'newest' ? 'selected' : ''}>${t('search.sortNewest')}</option>
<option value="oldest" ${this.sort === 'oldest' ? 'selected' : ''}>${t('search.sortOldest')}</option>
<option value="price_asc" ${this.sort === 'price_asc' ? 'selected' : ''}>${t('search.sortPriceAsc')}</option>
<option value="price_desc" ${this.sort === 'price_desc' ? 'selected' : ''}>${t('search.sortPriceDesc')}</option>
</select>
</div>
<button class="btn btn-sm btn-ghost" id="clear-filters">
${t('search.clearAll')}
</button>
</div>
</section>
`
} }
renderListings() { renderListings() {
@@ -526,57 +531,14 @@ style.textContent = /* css */`
padding: var(--space-xl) 0 var(--space-lg); padding: var(--space-xl) 0 var(--space-lg);
} }
/* Filter Bar */ /* Toolbar */
page-home .filter-bar { page-home .listings-toolbar {
margin-bottom: var(--space-lg);
padding: var(--space-md);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
}
page-home .filter-row {
display: flex;
flex-wrap: wrap;
gap: var(--space-lg);
align-items: flex-end;
}
page-home .price-filter,
page-home .sort-filter {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
page-home .price-filter label,
page-home .sort-filter label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
}
page-home .price-inputs {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-xs); gap: var(--space-sm);
flex-wrap: wrap;
} }
page-home .price-inputs input {
width: 100px;
padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
}
page-home .price-separator {
color: var(--color-text-muted);
}
page-home .sort-filter select,
page-home .sort-inline select { page-home .sort-inline select {
padding: var(--space-xs) var(--space-sm); padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
@@ -586,29 +548,79 @@ style.textContent = /* css */`
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
min-width: 150px; min-width: 150px;
} }
page-home .btn-sm { page-home .btn-sm {
padding: var(--space-xs) var(--space-sm); padding: var(--space-xs) var(--space-sm);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
} }
page-home .btn-ghost { page-home .btn-ghost {
background: transparent; background: transparent;
border: none; border: none;
color: var(--color-text-muted); color: var(--color-text-muted);
cursor: pointer; cursor: pointer;
} }
page-home .btn-ghost:hover { page-home .btn-ghost:hover {
color: var(--color-text); color: var(--color-text);
} }
page-home .filter-active-dot {
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background: var(--color-accent);
display: inline-block;
}
/* Price Filter Panel */
page-home .price-filter-panel {
display: none;
padding: var(--space-md);
margin-bottom: var(--space-md);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
}
page-home .price-filter-panel.open {
display: block;
}
page-home .price-inputs {
display: flex;
align-items: center;
gap: var(--space-md);
}
page-home .price-inputs input {
width: 100px;
padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
}
page-home .price-separator {
color: var(--color-text-muted);
}
@media (max-width: 768px) { @media (max-width: 768px) {
page-home .filter-row { page-home .listings-toolbar {
flex-direction: column; width: 100%;
align-items: stretch;
} }
page-home .sort-inline {
flex: 1;
}
page-home .sort-inline select {
width: 100%;
min-width: 0;
}
page-home .price-inputs { page-home .price-inputs {
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -622,10 +634,6 @@ style.textContent = /* css */`
page-home .price-inputs .btn { page-home .price-inputs .btn {
width: 100%; width: 100%;
} }
page-home .sort-filter select {
width: 100%;
}
} }
/* Listings Section */ /* Listings Section */