feat: toast undo button and hover/focus pause

This commit is contained in:
Your Name
2026-02-20 14:38:34 +02:00
parent 24b3caf0da
commit 85b39e41f6

View File

@@ -1,17 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
import { Check, AlertCircle, Info } from 'lucide-vue-next' import { Check, AlertCircle, Info, X, Undo2 } from 'lucide-vue-next'
const toastStore = useToastStore() const toastStore = useToastStore()
</script> </script>
<template> <template>
<div class="fixed top-4 left-1/2 -translate-x-1/2 z-[100] flex flex-col gap-2 pointer-events-none" style="margin-left: 24px;"> <div
role="region"
aria-label="Notifications"
aria-live="polite"
aria-atomic="false"
class="fixed top-4 left-1/2 -translate-x-1/2 z-[100] flex flex-col gap-2 pointer-events-none"
style="margin-left: 24px;"
>
<div <div
v-for="toast in toastStore.toasts" v-for="toast in toastStore.toasts"
:key="toast.id" :key="toast.id"
@click="toastStore.removeToast(toast.id)" :role="toast.type === 'error' ? 'alert' : 'status'"
class="w-80 flex items-center gap-3 px-4 py-3 bg-bg-surface border border-border-subtle rounded-lg shadow-lg cursor-pointer pointer-events-auto border-l-[3px]" tabindex="0"
@keydown.escape="toastStore.removeToast(toast.id)"
@mouseenter="toastStore.pauseToast(toast.id)"
@mouseleave="toastStore.resumeToast(toast.id)"
@focusin="toastStore.pauseToast(toast.id)"
@focusout="toastStore.resumeToast(toast.id)"
class="w-80 flex items-center gap-3 px-4 py-3 bg-bg-surface border border-border-subtle rounded-lg shadow-lg pointer-events-auto border-l-[3px]"
:class="[ :class="[
toast.exiting ? 'animate-toast-exit' : 'animate-toast-enter', toast.exiting ? 'animate-toast-exit' : 'animate-toast-enter',
toast.type === 'success' ? 'border-l-status-running' : '', toast.type === 'success' ? 'border-l-status-running' : '',
@@ -19,10 +32,26 @@ const toastStore = useToastStore()
toast.type === 'info' ? 'border-l-accent' : '' toast.type === 'info' ? 'border-l-accent' : ''
]" ]"
> >
<Check v-if="toast.type === 'success'" class="w-4 h-4 text-status-running shrink-0" :stroke-width="2" /> <Check v-if="toast.type === 'success'" class="w-4 h-4 text-status-running shrink-0" aria-hidden="true" :stroke-width="2" />
<AlertCircle v-if="toast.type === 'error'" class="w-4 h-4 text-status-error shrink-0" :stroke-width="2" /> <AlertCircle v-if="toast.type === 'error'" class="w-4 h-4 text-status-error shrink-0" aria-hidden="true" :stroke-width="2" />
<Info v-if="toast.type === 'info'" class="w-4 h-4 text-accent shrink-0" :stroke-width="2" /> <Info v-if="toast.type === 'info'" class="w-4 h-4 text-accent shrink-0" aria-hidden="true" :stroke-width="2" />
<span class="text-sm text-text-primary">{{ toast.message }}</span> <span class="sr-only">{{ toast.type === 'success' ? 'Success:' : toast.type === 'error' ? 'Error:' : 'Info:' }}</span>
<span class="text-sm text-text-primary flex-1">{{ toast.message }}</span>
<button
v-if="toast.onUndo"
aria-label="Undo"
@click.stop="toastStore.undoToast(toast.id)"
class="shrink-0 p-1 text-accent hover:text-accent/80 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent rounded"
>
<Undo2 class="w-3.5 h-3.5" aria-hidden="true" :stroke-width="2" />
</button>
<button
aria-label="Dismiss"
@click.stop="toastStore.removeToast(toast.id)"
class="shrink-0 p-1 text-text-tertiary hover:text-text-primary transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent rounded"
>
<X class="w-3.5 h-3.5" aria-hidden="true" :stroke-width="2" />
</button>
</div> </div>
</div> </div>
</template> </template>