wip bsky client for the web & android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(toasts): toast store

vi fad72c4d b84555a2

+179
+179
src/stores/toast.ts
··· 1 + import { 2 + IconCheckRounded, 3 + IconErrorRounded, 4 + IconInfoRounded, 5 + } from '@iconify-prerendered/vue-material-symbols' 6 + import { defineStore } from 'pinia' 7 + import { ref, markRaw } from 'vue' 8 + 9 + type ToastPayload = { 10 + type: ToastType 11 + message: string 12 + /** interval in milliseconds before the toast is automatically dismisses */ 13 + interval?: number 14 + onDismiss?: () => void 15 + onClick?: () => void 16 + } 17 + 18 + export enum ToastType { 19 + Success = 'success', 20 + Error = 'error', 21 + Info = 'info', 22 + } 23 + 24 + const toastIcons = { 25 + [ToastType.Success]: IconCheckRounded, 26 + [ToastType.Error]: IconErrorRounded, 27 + [ToastType.Info]: IconInfoRounded, 28 + } 29 + 30 + type ToastItem = { 31 + id: string 32 + type: ToastType 33 + message: string 34 + interval: number 35 + onDismiss?: () => void 36 + onClick?: () => void 37 + icon: unknown 38 + } 39 + 40 + export const useToastStore = defineStore('toast', () => { 41 + const toasts = ref<ToastItem[]>([]) 42 + const timers = new Map<string, number>() 43 + 44 + const MAX_TOASTS = 5 45 + const DEFAULT_INTERVALS: Record<ToastType, number> = { 46 + [ToastType.Success]: 3000, 47 + [ToastType.Error]: 6000, 48 + [ToastType.Info]: 4000, 49 + } 50 + 51 + function uId() { 52 + return Math.random().toString(36).substring(2, 9) 53 + } 54 + 55 + function pruneIfNeeded() { 56 + while (toasts.value.length > MAX_TOASTS) { 57 + const t = toasts.value.shift() 58 + if (t) { 59 + clearTimer(t.id) 60 + t.onDismiss?.() 61 + } 62 + } 63 + } 64 + 65 + function clearTimer(id: string) { 66 + const t = timers.get(id) 67 + if (t) { 68 + window.clearTimeout(t) 69 + timers.delete(id) 70 + } 71 + } 72 + 73 + function removeById(id: string) { 74 + const index = toasts.value.findIndex((t) => t.id === id) 75 + if (index !== -1) { 76 + toasts.value.splice(index, 1) 77 + } 78 + clearTimer(id) 79 + } 80 + 81 + function show(payload: ToastPayload) { 82 + const id = uId() 83 + const interval = payload.interval ?? DEFAULT_INTERVALS[payload.type] ?? 4000 84 + const icon = markRaw(toastIcons[payload.type]) 85 + 86 + const item: ToastItem = { 87 + id, 88 + type: payload.type, 89 + message: payload.message, 90 + interval, 91 + onDismiss: payload.onDismiss, 92 + onClick: payload.onClick, 93 + icon, 94 + } 95 + 96 + toasts.value.push(item) 97 + pruneIfNeeded() 98 + 99 + const timer = window.setTimeout(() => { 100 + dismiss(id) 101 + }, interval) 102 + timers.set(id, timer) 103 + 104 + return id 105 + } 106 + 107 + function success( 108 + message: string, 109 + opts?: { interval?: number; onDismiss?: () => void; onClick?: () => void }, 110 + ) { 111 + return show({ 112 + type: ToastType.Success, 113 + message, 114 + interval: opts?.interval, 115 + onDismiss: opts?.onDismiss, 116 + onClick: opts?.onClick, 117 + }) 118 + } 119 + 120 + function error( 121 + message: string, 122 + opts?: { interval?: number; onDismiss?: () => void; onClick?: () => void }, 123 + ) { 124 + return show({ 125 + type: ToastType.Error, 126 + message, 127 + interval: opts?.interval, 128 + onDismiss: opts?.onDismiss, 129 + onClick: opts?.onClick, 130 + }) 131 + } 132 + 133 + function info( 134 + message: string, 135 + opts?: { interval?: number; onDismiss?: () => void; onClick?: () => void }, 136 + ) { 137 + return show({ 138 + type: ToastType.Info, 139 + message, 140 + interval: opts?.interval, 141 + onDismiss: opts?.onDismiss, 142 + onClick: opts?.onClick, 143 + }) 144 + } 145 + 146 + function dismiss(id: string) { 147 + const toast = toasts.value.find((t) => t.id === id) 148 + if (!toast) return 149 + removeById(id) 150 + toast.onDismiss?.() 151 + } 152 + 153 + function dismissAll() { 154 + for (const t of [...toasts.value]) { 155 + removeById(t.id) 156 + t.onDismiss?.() 157 + } 158 + toasts.value = [] 159 + timers.clear() 160 + } 161 + 162 + function handleClick(id: string) { 163 + const toast = toasts.value.find((t) => t.id === id) 164 + if (!toast) return 165 + toast.onClick?.() 166 + dismiss(id) 167 + } 168 + 169 + return { 170 + toasts, 171 + show, 172 + success, 173 + error, 174 + info, 175 + dismiss, 176 + dismissAll, 177 + handleClick, 178 + } 179 + })