feat: add graceful degradation and conditional favorites filter

Add progressive enhancement to hide filter panel when JavaScript is
disabled, and conditionally render favorites filter based on login status.

Search Component:
- Added showFilters state (default false)
- Set showFilters to true in onMount when JS is enabled
- Wrapped FilterPanel in {#if showFilters} for graceful degradation
- Filters hidden without JavaScript, visible with JS

FilterPanel:
- Split grid layout into two variants:
  - with-favorites: 5 columns (120px 120px 1fr 160px 90px)
  - without-favorites: 4 columns (120px 120px 1fr 160px)
- Conditionally render FavoritesFilter only when isLoggedIn
- Apply appropriate class based on login status

FavoritesFilter:
- Simplified template (no internal login check)
- Only rendered when user is logged in via FilterPanel

UX:
- Non-JS browsers: Simple search only, filters gracefully hidden
- Not logged in: 4-column layout without favorites filter
- Logged in: 5-column layout with favorites filter
This commit is contained in:
2026-01-02 21:41:17 +01:00
parent 2f71b13de6
commit 626f4b039a
3 changed files with 46 additions and 43 deletions

View File

@@ -10,8 +10,7 @@
} = $props();
const isEnglish = $derived(lang === 'en');
const label = $derived(isEnglish ? 'Favorites Only' : 'Nur Favoriten');
const loginRequiredLabel = $derived(isEnglish ? 'Login required' : 'Anmeldung erforderlich');
const label = $derived(isEnglish ? 'Favorites' : 'Favoriten');
let checked = $state(enabled);
@@ -57,24 +56,13 @@
text-align: left;
}
}
.login-required {
font-size: 0.85rem;
color: var(--nord3);
font-style: italic;
padding: 0.5rem 0;
}
</style>
<div class="filter-section">
<div class="filter-label">{label}</div>
{#if isLoggedIn}
<Toggle
bind:checked={checked}
label=""
on:change={handleChange}
/>
{:else}
<div class="login-required">{loginRequiredLabel}</div>
{/if}
<Toggle
bind:checked={checked}
label=""
on:change={handleChange}
/>
</div>

View File

@@ -79,11 +79,18 @@
.filter-panel {
display: grid;
grid-template-columns: 120px 120px 1fr 160px 140px;
gap: 2rem;
align-items: start;
}
.filter-panel.with-favorites {
grid-template-columns: 120px 120px 1fr 160px 90px;
}
.filter-panel.without-favorites {
grid-template-columns: 120px 120px 1fr 160px;
}
@media (max-width: 968px) {
.toggle-button {
display: flex;
@@ -125,7 +132,7 @@
<span class="arrow" class:open={filtersOpen}>▼</span>
</button>
<div class="filter-panel" class:open={filtersOpen}>
<div class="filter-panel" class:open={filtersOpen} class:with-favorites={isLoggedIn} class:without-favorites={!isLoggedIn}>
<CategoryFilter
categories={availableCategories}
selected={selectedCategory}
@@ -154,11 +161,13 @@
{months}
/>
<FavoritesFilter
enabled={selectedFavoritesOnly}
onToggle={onFavoritesToggle}
{isLoggedIn}
{lang}
/>
{#if isLoggedIn}
<FavoritesFilter
enabled={selectedFavoritesOnly}
onToggle={onFavoritesToggle}
{isLoggedIn}
{lang}
/>
{/if}
</div>
</div>

View File

@@ -27,6 +27,7 @@
});
let searchQuery = $state('');
let showFilters = $state(false);
// Filter data loaded from APIs
let availableTags = $state([]);
@@ -246,6 +247,9 @@
clearButton.style.display = 'flex';
}
// Enable filter panel for JS-enabled browsers
showFilters = true;
// Get initial search value from URL if present
const urlParams = new URLSearchParams(window.location.search);
const urlQuery = urlParams.get('q');
@@ -340,20 +344,22 @@ scale: 0.8 0.8;
</button>
</form>
<FilterPanel
availableCategories={categories}
{availableTags}
{availableIcons}
{selectedCategory}
{selectedTags}
{selectedIcon}
{selectedSeasons}
{selectedFavoritesOnly}
{lang}
{isLoggedIn}
onCategoryChange={handleCategoryChange}
onTagToggle={handleTagToggle}
onIconChange={handleIconChange}
onSeasonChange={handleSeasonChange}
onFavoritesToggle={handleFavoritesToggle}
/>
{#if showFilters}
<FilterPanel
availableCategories={categories}
{availableTags}
{availableIcons}
{selectedCategory}
{selectedTags}
{selectedIcon}
{selectedSeasons}
{selectedFavoritesOnly}
{lang}
{isLoggedIn}
onCategoryChange={handleCategoryChange}
onTagToggle={handleTagToggle}
onIconChange={handleIconChange}
onSeasonChange={handleSeasonChange}
onFavoritesToggle={handleFavoritesToggle}
/>
{/if}