diff --git a/src/App.vue b/src/App.vue index c3670ec..ccc382a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,6 +3,7 @@ import {computed} from 'vue'; import {RouterView, useRoute} from 'vue-router'; import {Sidebar} from './sidebar'; import {ConfirmModal} from '@/confirm'; +import {ToastContainer} from '@/toast'; import {routeNames} from '@/routes'; const route = useRoute(); @@ -23,6 +24,7 @@ const hideSidebar = computed(() => { + diff --git a/src/toast/index.ts b/src/toast/index.ts new file mode 100644 index 0000000..f746706 --- /dev/null +++ b/src/toast/index.ts @@ -0,0 +1,3 @@ +export { default as ToastContainer } from './ToastContainer.vue'; +export { toast, useToastStore } from './useToast'; +export type { ToastOptions, ToastType } from './useToast'; diff --git a/src/toast/useToast.ts b/src/toast/useToast.ts new file mode 100644 index 0000000..8bd45d8 --- /dev/null +++ b/src/toast/useToast.ts @@ -0,0 +1,63 @@ +import {defineStore} from "pinia"; +import {ref} from "vue"; + +export type ToastType = 'success' | 'error' | 'info'; + +export interface ToastOptions { + message: string; + title?: string; + type?: ToastType; + duration?: number; +} + +export interface Toast { + id: number; + message: string; + title?: string; + type: ToastType; +} + +const DEFAULT_DURATION = 5000; + +export const useToastStore = defineStore('toast', () => { + const toasts = ref([]); + let nextId = 0; + + const dismiss = (id: number) => { + toasts.value = toasts.value.filter(toast => toast.id !== id); + }; + + const push = (opts: ToastOptions | string): number => { + const options = typeof opts === "string" ? {message: opts} : opts; + const id = nextId++; + toasts.value.push({ + id, + message: options.message, + title: options.title, + type: options.type ?? 'info', + }); + const duration = options.duration ?? DEFAULT_DURATION; + if (duration > 0) { + setTimeout(() => dismiss(id), duration); + } + return id; + }; + + return {toasts, push, dismiss}; +}); + +interface ToastHelper { + (opts: ToastOptions | string): number; + success: (message: string) => number; + error: (message: string) => number; + info: (message: string) => number; +} + +export const toast: ToastHelper = Object.assign( + (opts: ToastOptions | string): number => useToastStore().push(opts), + { + success: (message: string): number => useToastStore().push({message, type: 'success'}), + error: (message: string): number => useToastStore().push({message, type: 'error'}), + info: (message: string): number => useToastStore().push({message, type: 'info'}), + }, +);