feat: toast undo button and hover/focus pause
This commit is contained in:
@@ -1,17 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
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()
|
||||
</script>
|
||||
|
||||
<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
|
||||
v-for="toast in toastStore.toasts"
|
||||
:key="toast.id"
|
||||
@click="toastStore.removeToast(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 cursor-pointer pointer-events-auto border-l-[3px]"
|
||||
:role="toast.type === 'error' ? 'alert' : 'status'"
|
||||
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="[
|
||||
toast.exiting ? 'animate-toast-exit' : 'animate-toast-enter',
|
||||
toast.type === 'success' ? 'border-l-status-running' : '',
|
||||
@@ -19,10 +32,26 @@ const toastStore = useToastStore()
|
||||
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" />
|
||||
<AlertCircle v-if="toast.type === 'error'" class="w-4 h-4 text-status-error shrink-0" :stroke-width="2" />
|
||||
<Info v-if="toast.type === 'info'" class="w-4 h-4 text-accent shrink-0" :stroke-width="2" />
|
||||
<span class="text-sm text-text-primary">{{ toast.message }}</span>
|
||||
<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" aria-hidden="true" :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="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>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user