fix(a11y): accessibility and ND-design audit fixes (#42-#48, #54, #80) #85

Merged
pyr0ball merged 9 commits from fix/a11y-audit into main 2026-04-15 10:21:14 -07:00
2 changed files with 52 additions and 1 deletions
Showing only changes of commit 5c135d0860 - Show all commits

View file

@ -601,11 +601,22 @@
@close="browserSelectedRecipe = null"
@cooked="browserSelectedRecipe = null"
/>
<!-- Undo toast for "I cooked this" dismiss -->
<div
v-if="lastCookedRecipe"
class="undo-toast"
role="status"
aria-live="polite"
>
Dismissed from suggestions.
<button class="btn-link" @click="undoCooked">Undo</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { useRecipesStore } from '../stores/recipes'
import { useInventoryStore } from '../stores/inventory'
import { useSavedRecipesStore } from '../stores/savedRecipes'
@ -763,11 +774,28 @@ function openRecipe(recipe: RecipeSuggestion) {
selectedRecipe.value = recipe
}
const lastCookedRecipe = ref<{ id: number; title: string } | null>(null)
let undoTimer: ReturnType<typeof setTimeout> | null = null
function onCooked(recipe: RecipeSuggestion) {
recipesStore.logCook(recipe.id, recipe.title)
recipesStore.dismiss(recipe.id)
lastCookedRecipe.value = { id: recipe.id, title: recipe.title }
if (undoTimer) clearTimeout(undoTimer)
undoTimer = setTimeout(() => {
lastCookedRecipe.value = null
}, 12000)
}
function undoCooked() {
if (!lastCookedRecipe.value) return
recipesStore.undismiss(lastCookedRecipe.value.id)
if (undoTimer) clearTimeout(undoTimer)
lastCookedRecipe.value = null
}
onUnmounted(() => { if (undoTimer) clearTimeout(undoTimer) })
const levels = [
{ value: 1, label: 'Use What I Have', description: 'Finds recipes you can make right now using exactly what\'s in your pantry.' },
{ value: 2, label: 'Allow Swaps', description: 'Same as above, plus recipes where one or two ingredients can be substituted.' },
@ -1490,4 +1518,21 @@ details[open] .collapsible-summary::before {
grid-template-columns: 1fr;
}
}
.undo-toast {
position: fixed;
bottom: var(--spacing-lg);
left: 50%;
transform: translateX(-50%);
background: var(--color-bg-elevated);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--spacing-sm) var(--spacing-md);
box-shadow: var(--shadow-md);
font-size: var(--font-size-sm);
z-index: 100;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
</style>

View file

@ -238,6 +238,11 @@ export const useRecipesStore = defineStore('recipes', () => {
}
}
function undismiss(id: number) {
dismissedIds.value = new Set([...dismissedIds.value].filter((d) => d !== id))
saveDismissed(dismissedIds.value)
}
function clearDismissed() {
dismissedIds.value = new Set()
localStorage.removeItem(DISMISSED_KEY)
@ -318,6 +323,7 @@ export const useRecipesStore = defineStore('recipes', () => {
suggest,
loadMore,
dismiss,
undismiss,
clearDismissed,
clearResult,
}