Complete Svelte integration with stores and reactive statements
Try the Svelte integration in action:
npm install modern-toasts
<script>
import toast from 'modern-toasts';
function handleClick() {
toast.success('Hello from Svelte!');
}
</script>
<button on:click={handleClick}>
Show Toast
</button>
// stores/toast.js
import { writable } from 'svelte/store';
import toast from 'modern-toasts';
// Configure toast on import
toast.configure({
position: 'bottom-right',
maxVisibleStackToasts: 3,
enableBorderAnimation: true,
enableFillAnimation: true,
pauseBackgroundToastsOnHover: true
});
// Create stores for toast state
export const toastConfig = writable({
position: 'bottom-right',
maxVisible: 3,
enableAnimations: true
});
export const activeToasts = writable([]);
// Toast methods
export const toastService = {
success: (message, options = {}) => {
const id = toast.success(message, options);
activeToasts.update(toasts => [...toasts, { id, type: 'success', message }]);
return id;
},
error: (message, options = {}) => {
const id = toast.error(message, options);
activeToasts.update(toasts => [...toasts, { id, type: 'error', message }]);
return id;
},
info: (message, options = {}) => {
const id = toast.info(message, options);
activeToasts.update(toasts => [...toasts, { id, type: 'info', message }]);
return id;
},
warning: (message, options = {}) => {
const id = toast.warning(message, options);
activeToasts.update(toasts => [...toasts, { id, type: 'warning', message }]);
return id;
},
dismiss: (id) => {
toast.dismiss(id);
activeToasts.update(toasts => toasts.filter(t => t.id !== id));
},
dismissAll: () => {
toast.dismissAll();
activeToasts.set([]);
},
configure: (config) => {
toast.configure(config);
toastConfig.update(current => ({ ...current, ...config }));
}
};
// Listen to toast events to update store
toast.on('dismiss', (toastData) => {
activeToasts.update(toasts => toasts.filter(t => t.id !== toastData.id));
});
<script>
import { toastService, activeToasts } from '../stores/toast.js';
async function saveData() {
try {
await api.saveData();
toastService.success('Data saved successfully!');
} catch (error) {
toastService.error('Failed to save data');
}
}
</script>
<button on:click={saveData}>Save Data</button>
{#if $activeToasts.length > 0}
<p>Active toasts: {$activeToasts.length}</p>
{/if}
<script>
import { toastService, toastConfig } from '../stores/toast.js';
let position = 'bottom-right';
let maxVisible = 3;
let enableAnimations = true;
// Reactive statement to update configuration
$: {
toastService.configure({
position,
maxVisibleStackToasts: maxVisible,
enableBorderAnimation: enableAnimations,
enableFillAnimation: enableAnimations
});
}
// Reactive toast based on data changes
let userData = {};
$: if (userData.name) {
toastService.info(`Welcome, ${userData.name}!`);
}
</script>
<select bind:value={position}>
<option value="top-left">Top Left</option>
<option value="top-right">Top Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="bottom-right">Bottom Right</option>
</select>
<input type="range" bind:value={maxVisible} min="1" max="5">
<label>Max Visible: {maxVisible}</label>
<input type="checkbox" bind:checked={enableAnimations}>
<label>Enable Animations</label>
<script>
import { toastService } from '../stores/toast.js';
let formData = {
email: '',
password: '',
confirmPassword: ''
};
let errors = {};
// Reactive validation
$: {
errors = {};
if (formData.email && !isValidEmail(formData.email)) {
errors.email = 'Invalid email format';
}
if (formData.password && formData.password.length < 8) {
errors.password = 'Password must be at least 8 characters';
}
if (formData.confirmPassword && formData.password !== formData.confirmPassword) {
errors.confirmPassword = 'Passwords do not match';
}
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
async function handleSubmit() {
// Show validation errors as toasts
if (Object.keys(errors).length > 0) {
Object.values(errors).forEach(error => {
toastService.error(error);
});
return;
}
try {
await submitForm(formData);
toastService.success('Account created successfully!', {
autoDismiss: 5000
});
// Reset form
formData = { email: '', password: '', confirmPassword: '' };
} catch (error) {
toastService.error('Failed to create account');
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<input
type="email"
bind:value={formData.email}
placeholder="Email"
class:error={errors.email}
>
<input
type="password"
bind:value={formData.password}
placeholder="Password"
class:error={errors.password}
>
<input
type="password"
bind:value={formData.confirmPassword}
placeholder="Confirm Password"
class:error={errors.confirmPassword}
>
<button type="submit" disabled={Object.keys(errors).length > 0}>
Create Account
</button>
</form>
<script>
import { toastService } from '../stores/toast.js';
import { onMount } from 'svelte';
let loading = false;
let data = null;
let error = null;
async function fetchData() {
loading = true;
error = null;
const loadingToastId = toastService.info('Loading data...', {
autoDismiss: 0
});
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
data = await response.json();
toastService.dismiss(loadingToastId);
toastService.success('Data loaded successfully!');
} catch (err) {
error = err.message;
toastService.dismiss(loadingToastId);
if (err.message.includes('404')) {
toastService.error('Data not found');
} else if (err.message.includes('500')) {
toastService.error('Server error. Please try again.');
} else {
toastService.error('Network error occurred');
}
} finally {
loading = false;
}
}
// Auto-fetch on mount
onMount(fetchData);
</script>
<button on:click={fetchData} disabled={loading}>
{loading ? 'Loading...' : 'Refresh Data'}
</button>
{#if data}
<div>Data loaded: {JSON.stringify(data)}</div>
{/if}
<script>
import { toastService } from '../stores/toast.js';
let liked = false;
let bookmarked = false;
function toggleLike() {
liked = !liked;
if (liked) {
toastService.success('â¤ī¸ Added to favorites!', {
autoDismiss: 2000
});
} else {
toastService.info('đ Removed from favorites', {
autoDismiss: 2000
});
}
}
function toggleBookmark() {
bookmarked = !bookmarked;
if (bookmarked) {
toastService.info('đ Bookmarked!', {
autoDismiss: 2000
});
} else {
toastService.info('đ Bookmark removed', {
autoDismiss: 2000
});
}
}
async function shareContent() {
try {
await navigator.clipboard.writeText(window.location.href);
toastService.success('đ Link copied to clipboard!');
} catch (error) {
toastService.error('Failed to copy link');
}
}
</script>
<button on:click={toggleLike} class:liked>
{liked ? 'â¤ī¸' : 'đ¤'} Like
</button>
<button on:click={toggleBookmark} class:bookmarked>
{bookmarked ? 'đ' : 'đ'} Bookmark
</button>
<button on:click={shareContent}>
đ Share
</button>
<!-- ToastTrigger.svelte -->
<script>
import { toastService } from '../stores/toast.js';
export let type = 'info';
export let message = '';
export let label = 'Show Toast';
export let options = {};
export let disabled = false;
function triggerToast() {
const toastMessage = message || `${type} toast triggered!`;
toastService[type](toastMessage, options);
}
</script>
<button
class="btn btn-{type}"
on:click={triggerToast}
{disabled}
>
{label}
</button>
<style>
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
color: white;
}
.btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-success { background: #10b981; }
.btn-error { background: #ef4444; }
.btn-info { background: #3b82f6; }
.btn-warning { background: #f59e0b; }
</style>
<script>
import ToastTrigger from './ToastTrigger.svelte';
</script>
<ToastTrigger
type="success"
message="Operation completed successfully!"
label="Complete Task"
options={{ autoDismiss: 3000 }}
/>
<ToastTrigger
type="error"
message="Something went wrong!"
label="Trigger Error"
/>
<!-- App.svelte -->
<script>
import { onMount } from 'svelte';
import { toastService } from './stores/toast.js';
onMount(() => {
// Configure toast globally
toastService.configure({
position: 'top-right',
maxVisibleStackToasts: 4,
defaultDuration: 4000,
enableBorderAnimation: true,
enableFillAnimation: true,
pauseBackgroundToastsOnHover: true,
customCSS: `
.toast-container {
z-index: 9999;
}
.toast-svelte {
border-left: 4px solid #ff3e00;
}
`
});
});
</script>
<main>
<!-- Your app content -->
</main>
// stores/toast.ts
import { writable, type Writable } from 'svelte/store';
import toast, { type ToastOptions, type ToastType } from 'modern-toasts';
interface CustomToastOptions extends ToastOptions {
userId?: string;
component?: string;
}
interface ToastState {
id: string;
type: ToastType;
message: string;
timestamp: number;
}
export const activeToasts: Writable<ToastState[]> = writable([]);
export const toastService = {
success: (message: string, options?: CustomToastOptions): string => {
const id = toast.success(message, options);
activeToasts.update(toasts => [...toasts, {
id, type: 'success', message, timestamp: Date.now()
}]);
return id;
},
// ... other typed methods
};