feat(find): active-filter bar with clear-all (closes #129)

Adds a summary bar that appears at the top of the Find Recipes panel
whenever any filter is active. Shows a count ("3 filters active") and
a Clear all button that resets all Find-tab filters in one tap:
  constraints, allergies, excluded ingredients, shopping mode,
  pantry-match-only, hard day mode, time budgets (active + total),
  max missing, style, category, and all four nutrition limits.

Local input refs (constraintInput, allergyInput, etc.) are also cleared
so the text fields don't show stale uncommitted values after a clear.
This commit is contained in:
pyr0ball 2026-05-11 11:57:10 -07:00
parent 30f5620fd5
commit 92fab94ae0

View file

@ -95,6 +95,14 @@
<div class="card mb-controls"> <div class="card mb-controls">
<h2 class="section-title text-xl mb-md">Find Recipes</h2> <h2 class="section-title text-xl mb-md">Find Recipes</h2>
<!-- Active-filter summary bar -->
<div v-if="activeFilterCount > 0" class="active-filter-bar" role="status">
<span class="text-sm">{{ activeFilterCount }} filter{{ activeFilterCount !== 1 ? 's' : '' }} active</span>
<button class="btn btn-xs btn-secondary" @click="clearAllFindFilters" aria-label="Clear all active filters">
Clear all
</button>
</div>
<!-- Level Selector --> <!-- Level Selector -->
<div class="form-group"> <div class="form-group">
<label class="form-label">How far should we stretch?</label> <label class="form-label">How far should we stretch?</label>
@ -968,6 +976,42 @@ const advancedActive = computed(() =>
!!recipesStore.styleId !!recipesStore.styleId
) )
const activeFilterCount = computed(() => {
let n = 0
if (recipesStore.constraints.length > 0) n++
if (recipesStore.allergies.length > 0) n++
if (recipesStore.excludeIngredients.length > 0) n++
if (recipesStore.shoppingMode) n++
if (recipesStore.pantryMatchOnly) n++
if (recipesStore.hardDayMode) n++
if (recipesStore.maxActiveMin !== null) n++
if (recipesStore.maxTotalMin !== null) n++
if (recipesStore.maxMissing !== null) n++
if (recipesStore.styleId !== null) n++
if (recipesStore.category !== null) n++
n += Object.values(recipesStore.nutritionFilters).filter((v) => v !== null).length
return n
})
function clearAllFindFilters() {
recipesStore.clearConstraints()
recipesStore.clearAllergies()
recipesStore.clearExcludeIngredients()
recipesStore.shoppingMode = false
recipesStore.pantryMatchOnly = false
recipesStore.hardDayMode = false
recipesStore.maxActiveMin = null
recipesStore.maxTotalMin = null
recipesStore.maxMissing = null
recipesStore.styleId = null
recipesStore.category = null
recipesStore.nutritionFilters = { max_calories: null, max_sugar_g: null, max_carbs_g: null, max_sodium_mg: null }
constraintInput.value = ''
allergyInput.value = ''
excludeIngredientInput.value = ''
categoryInput.value = ''
}
// #46 count of active nutrition filters so the summary is informative when collapsed // #46 count of active nutrition filters so the summary is informative when collapsed
const activeNutritionFilterCount = computed(() => const activeNutritionFilterCount = computed(() =>
Object.values(recipesStore.nutritionFilters ?? {}).filter((v) => v !== null).length Object.values(recipesStore.nutritionFilters ?? {}).filter((v) => v !== null).length
@ -1387,6 +1431,18 @@ watch(
} }
/* Filter bar */ /* Filter bar */
.active-filter-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-xs) var(--spacing-sm);
margin-bottom: var(--spacing-sm);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
color: var(--color-text-secondary);
}
.filter-bar { .filter-bar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;