feat: toast auto-dismiss with undo and pause support
This commit is contained in:
@@ -6,28 +6,69 @@ export interface Toast {
|
||||
message: string
|
||||
type: 'success' | 'error' | 'info'
|
||||
exiting?: boolean
|
||||
duration: number
|
||||
onUndo?: () => void
|
||||
paused: boolean
|
||||
timerId: ReturnType<typeof setTimeout> | null
|
||||
remaining: number
|
||||
}
|
||||
|
||||
const DEFAULT_DURATIONS: Record<Toast['type'], number> = {
|
||||
success: 4000,
|
||||
error: 8000,
|
||||
info: 6000,
|
||||
}
|
||||
|
||||
export interface ToastOptions {
|
||||
onUndo?: () => void
|
||||
}
|
||||
|
||||
export const useToastStore = defineStore('toast', () => {
|
||||
const toasts = ref<Toast[]>([])
|
||||
let nextId = 0
|
||||
const persistentNotifications = ref(false)
|
||||
|
||||
function addToast(message: string, type: Toast['type'] = 'info') {
|
||||
const id = nextId++
|
||||
toasts.value.push({ id, message, type })
|
||||
|
||||
// Max 3 visible
|
||||
if (toasts.value.length > 3) {
|
||||
toasts.value.shift()
|
||||
function startTimer(toast: Toast) {
|
||||
if (persistentNotifications.value || toast.paused || toast.remaining <= 0) return
|
||||
toast.timerId = setTimeout(() => {
|
||||
removeToast(toast.id)
|
||||
}, toast.remaining)
|
||||
}
|
||||
|
||||
// Auto-dismiss after 3s
|
||||
setTimeout(() => removeToast(id), 3000)
|
||||
function addToast(
|
||||
message: string,
|
||||
type: Toast['type'] = 'info',
|
||||
options?: ToastOptions
|
||||
) {
|
||||
const id = nextId++
|
||||
const duration = DEFAULT_DURATIONS[type]
|
||||
const toast: Toast = {
|
||||
id,
|
||||
message,
|
||||
type,
|
||||
duration,
|
||||
onUndo: options?.onUndo,
|
||||
paused: false,
|
||||
timerId: null,
|
||||
remaining: duration,
|
||||
}
|
||||
toasts.value.push(toast)
|
||||
startTimer(toast)
|
||||
|
||||
// Cap at 5, oldest gets exiting state
|
||||
if (toasts.value.length > 5) {
|
||||
const oldest = toasts.value.find(t => !t.exiting)
|
||||
if (oldest) removeToast(oldest.id)
|
||||
}
|
||||
}
|
||||
|
||||
function removeToast(id: number) {
|
||||
const toast = toasts.value.find(t => t.id === id)
|
||||
if (toast) {
|
||||
if (toast.timerId) {
|
||||
clearTimeout(toast.timerId)
|
||||
toast.timerId = null
|
||||
}
|
||||
toast.exiting = true
|
||||
setTimeout(() => {
|
||||
toasts.value = toasts.value.filter(t => t.id !== id)
|
||||
@@ -35,9 +76,71 @@ export const useToastStore = defineStore('toast', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function success(message: string) { addToast(message, 'success') }
|
||||
function error(message: string) { addToast(message, 'error') }
|
||||
function info(message: string) { addToast(message, 'info') }
|
||||
function pauseToast(id: number) {
|
||||
const toast = toasts.value.find(t => t.id === id)
|
||||
if (toast && !toast.paused && toast.timerId) {
|
||||
clearTimeout(toast.timerId)
|
||||
toast.timerId = null
|
||||
toast.paused = true
|
||||
}
|
||||
}
|
||||
|
||||
return { toasts, addToast, removeToast, success, error, info }
|
||||
function resumeToast(id: number) {
|
||||
const toast = toasts.value.find(t => t.id === id)
|
||||
if (toast && toast.paused) {
|
||||
toast.paused = false
|
||||
startTimer(toast)
|
||||
}
|
||||
}
|
||||
|
||||
function undoToast(id: number) {
|
||||
const toast = toasts.value.find(t => t.id === id)
|
||||
if (toast && toast.onUndo) {
|
||||
toast.onUndo()
|
||||
}
|
||||
removeToast(id)
|
||||
}
|
||||
|
||||
function setPersistentNotifications(val: boolean) {
|
||||
persistentNotifications.value = val
|
||||
if (val) {
|
||||
// Clear all running timers
|
||||
for (const toast of toasts.value) {
|
||||
if (toast.timerId) {
|
||||
clearTimeout(toast.timerId)
|
||||
toast.timerId = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Restart timers for non-paused toasts
|
||||
for (const toast of toasts.value) {
|
||||
if (!toast.paused && !toast.exiting) {
|
||||
startTimer(toast)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function success(message: string, options?: ToastOptions) {
|
||||
addToast(message, 'success', options)
|
||||
}
|
||||
function error(message: string, options?: ToastOptions) {
|
||||
addToast(message, 'error', options)
|
||||
}
|
||||
function info(message: string, options?: ToastOptions) {
|
||||
addToast(message, 'info', options)
|
||||
}
|
||||
|
||||
return {
|
||||
toasts,
|
||||
addToast,
|
||||
removeToast,
|
||||
pauseToast,
|
||||
resumeToast,
|
||||
undoToast,
|
||||
setPersistentNotifications,
|
||||
success,
|
||||
error,
|
||||
info,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user