refactor: always show sort and collapsible price filter toolbar on home page
This commit is contained in:
@@ -25,6 +25,7 @@ class PageHome extends HTMLElement {
|
||||
this.sort = 'newest'
|
||||
this.minPrice = null
|
||||
this.maxPrice = null
|
||||
this.priceFilterOpen = false
|
||||
|
||||
// Geo state
|
||||
this.userLat = null
|
||||
@@ -155,6 +156,11 @@ class PageHome extends HTMLElement {
|
||||
return false
|
||||
})
|
||||
|
||||
// Toggle price filter
|
||||
this.querySelector('#toggle-price-filter')?.addEventListener('click', () => {
|
||||
this.togglePriceFilter()
|
||||
})
|
||||
|
||||
// Sort change
|
||||
this.querySelector('#sort-select')?.addEventListener('change', (e) => {
|
||||
this.sort = e.target.value
|
||||
@@ -190,6 +196,7 @@ class PageHome extends HTMLElement {
|
||||
this.sort = 'newest'
|
||||
this.minPrice = null
|
||||
this.maxPrice = null
|
||||
this.priceFilterOpen = false
|
||||
this.updateUrl()
|
||||
this.render()
|
||||
this.setupEventListeners()
|
||||
@@ -384,18 +391,17 @@ class PageHome extends HTMLElement {
|
||||
|
||||
render() {
|
||||
const showFilters = this.hasActiveFilters()
|
||||
const hasPriceFilter = this.minPrice || this.maxPrice
|
||||
|
||||
this.innerHTML = /* html */`
|
||||
<section class="search-section">
|
||||
<search-box no-navigate></search-box>
|
||||
</section>
|
||||
|
||||
${showFilters ? this.renderFilterBar() : ''}
|
||||
|
||||
<section class="listings-section">
|
||||
<div class="listings-header">
|
||||
${showFilters ? `<h2 class="listings-title">${this.getListingsTitle()}</h2>` : ''}
|
||||
${!showFilters ? `
|
||||
<div class="listings-toolbar">
|
||||
<div class="sort-inline">
|
||||
<select id="sort-select">
|
||||
${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>
|
||||
</select>
|
||||
</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 id="listings-container" class="listings-grid">
|
||||
${this.renderListings()}
|
||||
@@ -423,39 +457,10 @@ class PageHome extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
renderFilterBar() {
|
||||
return /* html */`
|
||||
<section class="filter-bar">
|
||||
<div class="filter-row">
|
||||
<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>
|
||||
`
|
||||
togglePriceFilter() {
|
||||
this.priceFilterOpen = !this.priceFilterOpen
|
||||
const panel = this.querySelector('#price-filter-panel')
|
||||
if (panel) panel.classList.toggle('open', this.priceFilterOpen)
|
||||
}
|
||||
|
||||
renderListings() {
|
||||
@@ -526,57 +531,14 @@ style.textContent = /* css */`
|
||||
padding: var(--space-xl) 0 var(--space-lg);
|
||||
}
|
||||
|
||||
/* Filter Bar */
|
||||
page-home .filter-bar {
|
||||
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 {
|
||||
/* Toolbar */
|
||||
page-home .listings-toolbar {
|
||||
display: flex;
|
||||
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 {
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
@@ -586,29 +548,79 @@ style.textContent = /* css */`
|
||||
font-size: var(--font-size-sm);
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
|
||||
page-home .btn-sm {
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
|
||||
page-home .btn-ghost {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
page-home .btn-ghost:hover {
|
||||
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) {
|
||||
page-home .filter-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
page-home .listings-toolbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
page-home .sort-inline {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
page-home .sort-inline select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
page-home .price-inputs {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -622,10 +634,6 @@ style.textContent = /* css */`
|
||||
page-home .price-inputs .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
page-home .sort-filter select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Listings Section */
|
||||
|
||||
Reference in New Issue
Block a user