feat(ux/nd): collapse Find tab into level picker + two filter sections with active indicators

This commit is contained in:
pyr0ball 2026-04-08 22:51:43 -07:00
parent 8c1443d156
commit 4bb93b78d1

View file

@ -66,6 +66,14 @@
</label>
</div>
<!-- Dietary Preferences (collapsible) -->
<details class="collapsible form-group">
<summary class="collapsible-summary filter-summary">
Dietary preferences
<span v-if="dietaryActive" class="filter-active-dot" aria-label="filters active"></span>
</summary>
<div class="collapsible-body">
<!-- Dietary Constraints Tags -->
<div class="form-group">
<label class="form-label">Dietary Constraints</label>
@ -125,7 +133,7 @@
</p>
</div>
<!-- Shopping Mode -->
<!-- Shopping Mode (temporary home moves to Shopping tab in #71) -->
<div class="form-group">
<label class="flex-start gap-sm shopping-toggle">
<input type="checkbox" v-model="recipesStore.shoppingMode" />
@ -136,7 +144,7 @@
</p>
</div>
<!-- Max Missing hidden in shopping mode (it's lifted automatically) -->
<!-- Max Missing hidden in shopping mode -->
<div v-if="!recipesStore.shoppingMode" class="form-group">
<label class="form-label">Max Missing Ingredients (optional)</label>
<input
@ -149,66 +157,54 @@
@input="onMaxMissingInput"
/>
</div>
</div>
</details>
<!-- Nutrition Filters -->
<!-- Advanced Filters (collapsible) -->
<details class="collapsible form-group">
<summary class="form-label collapsible-summary nutrition-summary">
Nutrition Filters <span class="text-muted text-xs">(per recipe, optional)</span>
<summary class="collapsible-summary filter-summary">
Advanced filters
<span v-if="advancedActive" class="filter-active-dot" aria-label="filters active"></span>
</summary>
<div class="collapsible-body">
<!-- Nutrition Filters -->
<div class="form-group">
<label class="form-label">Nutrition limits <span class="text-muted text-xs">(per recipe, optional)</span></label>
<div class="nutrition-filters-grid mt-xs">
<div class="form-group">
<label class="form-label">Max Calories</label>
<input
type="number"
class="form-input"
min="0"
placeholder="e.g. 600"
<input type="number" class="form-input" min="0" placeholder="e.g. 600"
:value="recipesStore.nutritionFilters.max_calories ?? ''"
@input="onNutritionInput('max_calories', $event)"
/>
@input="onNutritionInput('max_calories', $event)" />
</div>
<div class="form-group">
<label class="form-label">Max Sugar (g)</label>
<input
type="number"
class="form-input"
min="0"
placeholder="e.g. 10"
<input type="number" class="form-input" min="0" placeholder="e.g. 10"
:value="recipesStore.nutritionFilters.max_sugar_g ?? ''"
@input="onNutritionInput('max_sugar_g', $event)"
/>
@input="onNutritionInput('max_sugar_g', $event)" />
</div>
<div class="form-group">
<label class="form-label">Max Carbs (g)</label>
<input
type="number"
class="form-input"
min="0"
placeholder="e.g. 50"
<input type="number" class="form-input" min="0" placeholder="e.g. 50"
:value="recipesStore.nutritionFilters.max_carbs_g ?? ''"
@input="onNutritionInput('max_carbs_g', $event)"
/>
@input="onNutritionInput('max_carbs_g', $event)" />
</div>
<div class="form-group">
<label class="form-label">Max Sodium (mg)</label>
<input
type="number"
class="form-input"
min="0"
placeholder="e.g. 800"
<input type="number" class="form-input" min="0" placeholder="e.g. 800"
:value="recipesStore.nutritionFilters.max_sodium_mg ?? ''"
@input="onNutritionInput('max_sodium_mg', $event)"
/>
@input="onNutritionInput('max_sodium_mg', $event)" />
</div>
</div>
<p class="text-xs text-muted mt-xs">
Recipes without nutrition data always appear. Filters apply to food.com and estimated values.
Recipes without nutrition data always appear.
</p>
</details>
</div>
<!-- Cuisine Style (Level 3+ only) -->
<div v-if="recipesStore.level >= 3" class="form-group">
<label class="form-label">Cuisine Style <span class="text-muted text-xs">(Level 3+)</span></label>
<label class="form-label">Cuisine Style</label>
<div class="flex flex-wrap gap-xs">
<button
v-for="style in cuisineStyles"
@ -230,6 +226,8 @@
@keydown.enter="recipesStore.category = categoryInput.trim() || null"
/>
</div>
</div>
</details>
<!-- Suggest Button -->
<div class="suggest-row">
@ -657,6 +655,20 @@ const levels = [
{ value: 4, label: 'Surprise Me 🎲', description: 'Fully AI-generated — open-ended and occasionally unexpected. Requires paid tier.' },
]
const dietaryActive = computed(() =>
recipesStore.constraints.length > 0 ||
recipesStore.allergies.length > 0 ||
recipesStore.hardDayMode ||
recipesStore.shoppingMode
)
const advancedActive = computed(() =>
Object.values(recipesStore.nutritionFilters).some((v) => v !== null) ||
recipesStore.maxMissing !== null ||
!!recipesStore.category ||
!!recipesStore.styleId
)
const activeLevel = computed(() => levels.find(l => l.value === recipesStore.level))
const cuisineStyles = [
@ -1052,6 +1064,30 @@ details[open] .collapsible-summary::before {
content: '▼ ';
}
.filter-summary {
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-weight: 500;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
}
.filter-active-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--color-primary);
flex-shrink: 0;
}
.collapsible-body {
padding-top: var(--spacing-sm);
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.swap-row {
padding: var(--spacing-xs) 0;
border-bottom: 1px solid var(--color-border);