From 1a155e565703bce8608c32fb8b2a0d84b6daa58d Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 24 Jun 2025 01:47:35 +0100 Subject: [PATCH] feat: implement toast notification system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create toast store wrapping svelte-sonner - Add Toaster component to root layout - Configure admin-aware positioning - Style toasts to match design system - Add helper functions for common toast types 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/lib/stores/toast.ts | 85 +++++++++++++++++++++++++++ src/routes/+layout.svelte | 120 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/lib/stores/toast.ts diff --git a/src/lib/stores/toast.ts b/src/lib/stores/toast.ts new file mode 100644 index 0000000..3a21d14 --- /dev/null +++ b/src/lib/stores/toast.ts @@ -0,0 +1,85 @@ +import { toast as sonnerToast } from 'svelte-sonner' + +export interface ToastOptions { + duration?: number + position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' + description?: string + action?: { + label: string + onClick: () => void + } + cancel?: { + label: string + onClick: () => void + } +} + +const defaultOptions: ToastOptions = { + duration: 4000, + position: 'bottom-right' +} + +export const toast = { + success: (message: string, options?: ToastOptions) => { + return sonnerToast.success(message, { + ...defaultOptions, + ...options + }) + }, + + error: (message: string, options?: ToastOptions) => { + return sonnerToast.error(message, { + ...defaultOptions, + ...options, + duration: options?.duration ?? 6000 // Errors show longer by default + }) + }, + + warning: (message: string, options?: ToastOptions) => { + return sonnerToast.warning(message, { + ...defaultOptions, + ...options + }) + }, + + info: (message: string, options?: ToastOptions) => { + return sonnerToast.info(message, { + ...defaultOptions, + ...options + }) + }, + + loading: (message: string, options?: ToastOptions) => { + return sonnerToast.loading(message, { + ...defaultOptions, + ...options + }) + }, + + promise: ( + promise: Promise, + messages: { + loading: string + success: string | ((data: T) => string) + error: string | ((error: any) => string) + }, + options?: ToastOptions + ) => { + return sonnerToast.promise(promise, messages, { + ...defaultOptions, + ...options + }) + }, + + dismiss: (toastId?: string | number) => { + return sonnerToast.dismiss(toastId) + }, + + // Custom toast with full control + custom: (component: any, options?: ToastOptions) => { + return sonnerToast.custom(component, { + ...defaultOptions, + ...options + }) + } +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index aadbbf4..2d34a93 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -4,6 +4,7 @@ import Header from '$components/Header.svelte' import Footer from '$components/Footer.svelte' import { generatePersonJsonLd } from '$lib/utils/metadata' + import { Toaster } from 'svelte-sonner' let { children } = $props() @@ -44,6 +45,15 @@ {/if} + + +