✨ feat: add loading bar and toast notification system with responsive design
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Portal action — teleports an element to document.body.
|
||||
* Usage: <div use:portal>…</div>
|
||||
* SSR-safe: only runs when document is available.
|
||||
*/
|
||||
export function portal(node: HTMLElement) {
|
||||
if (typeof document === "undefined") return
|
||||
|
||||
document.body.appendChild(node)
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Toast Notification System
|
||||
* Provides a simple toast notification API with auto-dismiss and responsive positioning.
|
||||
*/
|
||||
import { writable, derived } from "svelte/store"
|
||||
|
||||
export type ToastType = "success" | "error" | "warning" | "info"
|
||||
|
||||
export interface Toast {
|
||||
id: string
|
||||
message: string
|
||||
type: ToastType
|
||||
duration: number
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
interface ToastStore {
|
||||
toasts: Toast[]
|
||||
}
|
||||
|
||||
const DEFAULT_DURATION = 3000 // 3 seconds
|
||||
|
||||
// Create the toast store
|
||||
const toastStore = writable<ToastStore>({ toasts: [] })
|
||||
|
||||
/**
|
||||
* Generate unique toast ID
|
||||
*/
|
||||
function generateToastId(): string {
|
||||
return `toast-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new toast notification
|
||||
*/
|
||||
export function addToast(message: string, type: ToastType = "info", duration: number = DEFAULT_DURATION): string {
|
||||
const id = generateToastId()
|
||||
|
||||
const toast: Toast = {
|
||||
id,
|
||||
message,
|
||||
type,
|
||||
duration,
|
||||
createdAt: Date.now(),
|
||||
}
|
||||
|
||||
toastStore.update((store) => ({
|
||||
toasts: [...store.toasts, toast],
|
||||
}))
|
||||
|
||||
// Auto-remove after duration
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
removeToast(id)
|
||||
}, duration)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a toast by ID
|
||||
*/
|
||||
export function removeToast(id: string): void {
|
||||
toastStore.update((store) => ({
|
||||
toasts: store.toasts.filter((t) => t.id !== id),
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all toasts
|
||||
*/
|
||||
export function clearToasts(): void {
|
||||
toastStore.set({ toasts: [] })
|
||||
}
|
||||
|
||||
// Export derived readable store for reactive access
|
||||
export const toasts = derived(toastStore, ($store) => $store.toasts)
|
||||
|
||||
// Export convenience object
|
||||
export const toast = {
|
||||
add: addToast,
|
||||
remove: removeToast,
|
||||
clear: clearToasts,
|
||||
}
|
||||
Reference in New Issue
Block a user