feather-toast-vue 1.0.12 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,27 @@
1
- # Feather Toast Vue
1
+ # feather-toast-vue
2
2
 
3
- A lightweight Vue 3 toast notification plugin. Minimal by design, with smooth animations and a clean API that stays out of your way.
3
+ Lightweight, accessible toast notifications for Vue 3 with spring-based animations, promise support, swipe-to-dismiss, and a minimal footprint.
4
4
 
5
5
  ---
6
6
 
7
- ## Features
8
-
9
- | Feature | Description |
10
- |---------|-------------|
11
- | 6 Variants | `default` · `success` · `error` · `warning` · `info` · `loading` |
12
- | 6 Positions | `top-right` · `top-left` · `top-center` · `bottom-right` · `bottom-left` · `bottom-center` |
13
- | Smooth Animations | Spring-based slide + fade + scale with `cubic-bezier(0.16, 1, 0.3, 1)` |
14
- | Swipe to Dismiss | Horizontal swipe gesture with snap-back |
15
- | Hover Pause | Auto-dismiss timer pauses accurately on hover |
16
- | Promise API | `toast.promise()` — loading → success / error |
17
- | Action Button | Optional inline action on any toast |
18
- | Vue Plugin | One-line install with `app.use(ToastPlugin)` |
19
- | Zero Dependencies | Only Vue 3 — no Tailwind, no UI library |
20
- | TypeScript | Fully typed API |
21
- | Accessible | `aria-live`, `role="alert"`, `prefers-reduced-motion` support |
7
+ ## Table of Contents
8
+
9
+ 1. [Installation](#installation)
10
+ 2. [Setup](#setup)
11
+ 3. [Quick Start](#quick-start)
12
+ 4. [Variants](#variants)
13
+ 5. [Positioning](#positioning)
14
+ 6. [Stack Limit](#stack-limit)
15
+ 7. [Global Configuration](#global-configuration)
16
+ 8. [toast.undo](#toastundo)
17
+ 9. [toast.step](#toapststep)
18
+ 10. [toast.progress](#toastprogress)
19
+ 11. [toast.copy](#toastcopy)
20
+ 12. [toast.promise](#toastpromise)
21
+ 13. [toast.custom](#toastcustom)
22
+ 14. [toast.network](#toastnetwork)
23
+ 15. [TypeScript API Reference](#typescript-api-reference)
24
+ 16. [CSS Theming](#css-custom-properties-theming)
22
25
 
23
26
  ---
24
27
 
@@ -26,12 +29,22 @@ A lightweight Vue 3 toast notification plugin. Minimal by design, with smooth an
26
29
 
27
30
  ```bash
28
31
  npm install feather-toast-vue
32
+ # or
33
+ yarn add feather-toast-vue
34
+ # or
35
+ pnpm add feather-toast-vue
29
36
  ```
30
37
 
31
- ### Setup
38
+ ---
39
+
40
+ ## Setup
41
+
42
+ ### Vue 3 (Vite / Webpack)
43
+
44
+ Register the plugin once in your application entry file.
32
45
 
46
+ **`src/main.ts`**
33
47
  ```ts
34
- // main.ts
35
48
  import { createApp } from 'vue'
36
49
  import App from './App.vue'
37
50
  import { CreateFeatherToast } from 'feather-toast-vue'
@@ -41,237 +54,607 @@ app.use(CreateFeatherToast)
41
54
  app.mount('#app')
42
55
  ```
43
56
 
44
- `ToastContainer` and styles are mounted automatically by the plugin.
57
+ The plugin automatically mounts `<ToastContainer />`, injects its stylesheet, and provides the `toast` instance via `provide/inject`. No CSS import needed.
58
+
59
+ Need to load the CSS yourself (strict CSP, SSR critical CSS)? Turn injection off and import the file:
60
+
61
+ ```ts
62
+ import 'feather-toast-vue/style.css'
63
+ app.use(CreateFeatherToast, { injectStyles: false })
64
+ ```
45
65
 
46
66
  ---
47
67
 
48
- ## Usage
68
+ ### Nuxt 3
49
69
 
50
- ### Basic
70
+ Create a client-only plugin file. Toast relies on browser APIs and must run exclusively on the client.
51
71
 
72
+ **`plugins/toast.client.ts`**
52
73
  ```ts
74
+ import { defineNuxtPlugin } from '#app'
75
+ import { CreateFeatherToast } from 'feather-toast-vue'
76
+
77
+ export default defineNuxtPlugin((nuxtApp) => {
78
+ nuxtApp.vueApp.use(CreateFeatherToast)
79
+ })
80
+ ```
81
+
82
+ > The `.client.ts` suffix ensures Nuxt only loads this plugin in the browser, never on the server.
83
+
84
+ ---
85
+
86
+ ## Quick Start
87
+
88
+ Call `useFeatherToast()` inside any Vue component's `<script setup>` to access the `toast` object.
89
+
90
+ ```vue
91
+ <script setup lang="ts">
53
92
  import { useFeatherToast } from 'feather-toast-vue'
54
93
 
55
94
  const { toast } = useFeatherToast()
56
95
 
57
- toast({
58
- title: 'Event Created',
59
- description: 'Your event has been scheduled.',
60
- })
96
+ function save() {
97
+ toast.success('Changes saved successfully!')
98
+ }
99
+ </script>
100
+
101
+ <template>
102
+ <button @click="save">Save</button>
103
+ </template>
61
104
  ```
62
105
 
63
- ### Variants
106
+ ---
107
+
108
+ ## Variants
64
109
 
65
110
  ```ts
66
- toast.success({ title: 'Saved', description: 'Changes applied.' })
67
- toast.error({ title: 'Error', description: 'Something went wrong.' })
68
- toast.warning({ title: 'Warning', description: 'Disk space is low.' })
69
- toast.info({ title: 'Info', description: 'Version 2.4 is available.' })
70
- toast.loading({ title: 'Uploading...', description: 'Please wait.' })
111
+ const { toast } = useFeatherToast()
112
+
113
+ toast('New notification') // default
114
+ toast.success('Profile updated!')
115
+ toast.error('Request failed.')
116
+ toast.warning('Storage running low.')
117
+ toast.info('Version 2.4 available.')
118
+ toast.loading('Uploading files...')
119
+
120
+ // Pass anything thrown straight in  the message is extracted
121
+ try { await save() } catch (err) { toast.error(err) }
122
+
123
+ // Full options object
124
+ toast.success({
125
+ title: 'Payment successful',
126
+ description: 'Your invoice has been paid.',
127
+ duration: 6000,
128
+ position: 'top-right',
129
+ })
71
130
  ```
72
131
 
73
- ### Custom Position
132
+ | Variant | Duration | Dismissible | Notes |
133
+ |---------------|-------------|-------------|------------------------------------|
134
+ | `default` | 5 000 ms | Yes | Neutral notification |
135
+ | `success` | 5 000 ms | Yes | Positive confirmation |
136
+ | `error` | 5 000 ms | Yes | Failure or exception |
137
+ | `warning` | 5 000 ms | Yes | Caution / low-priority issue |
138
+ | `info` | 5 000 ms | Yes | Informational message |
139
+ | `loading` | Persistent | No | Spinner; use with `toast.promise` |
140
+ | `offline` | Persistent | No | Network connectivity lost |
141
+ | `update` | 8 000 ms | Yes | App or version update available |
142
+
143
+ ---
144
+
145
+ ## Positioning
74
146
 
75
147
  ```ts
76
148
  toast.success({
77
149
  title: 'Saved',
78
- position: 'top-center', // default: 'bottom-right'
150
+ position: 'top-right',
151
+ // 'top-right' | 'top-left' | 'top-center'
152
+ // 'bottom-right' | 'bottom-left' | 'bottom-center'
79
153
  })
80
154
  ```
81
155
 
82
- ### Promise API
156
+ ---
157
+
158
+ ## Stack Limit
83
159
 
84
- Automatically transitions `loading → success` or `loading → error`:
160
+ When the number of visible toasts at a given position exceeds the configured maximum, the oldest toast is smoothly dismissed.
85
161
 
86
162
  ```ts
87
- toast.promise(
88
- fetch('/api/projects', { method: 'POST' }),
89
- {
90
- loading: {
91
- title: 'Creating project...',
92
- description: 'Setting up your workspace.',
93
- },
94
- success: (data) => ({
95
- title: `"${data.name}" created`,
96
- description: 'Your project is ready.',
97
- }),
98
- error: (err) => ({
99
- title: 'Failed to create project',
100
- description: err instanceof Error ? err.message : 'Unknown error.',
101
- }),
102
- }
103
- )
163
+ toast.config({ max: 3 }) // Allow at most 3 simultaneous toasts
164
+ toast.config({ max: 0 }) // Unlimited
104
165
  ```
105
166
 
106
- ### Action Button
167
+ ---
168
+
169
+ ## Global Configuration
170
+
171
+ Configure defaults once � typically in your root component.
107
172
 
108
173
  ```ts
109
- toast({
110
- title: 'Message archived',
111
- description: 'Moved to archive.',
112
- action: {
113
- label: 'Undo',
114
- onClick: () => {
115
- toast.success({ title: 'Restored', description: 'Message moved back to inbox.' })
116
- },
117
- },
174
+ import { useFeatherToast } from 'feather-toast-vue'
175
+
176
+ const { toast } = useFeatherToast()
177
+
178
+ toast.config({
179
+ max: 3, // Max visible toasts per position (0 = unlimited)
180
+ duration: 4000, // Default auto-dismiss timeout in milliseconds
181
+ position: 'bottom-right', // Default spawn position
182
+ visibleToasts: 3, // Toasts shown in the collapsed stack
183
+ expand: false, // true = always expanded instead of the Sonner-style stack
184
+ richColors: false, // Tinted backgrounds for success/error/warning/info/update
185
+ theme: 'light', // 'light' | 'dark' | 'system'
118
186
  })
187
+
188
+ const config = toast.getConfig()
189
+ // => { max: 3, duration: 4000, position: 'bottom-right' }
119
190
  ```
120
191
 
121
- ### Dismiss
192
+ ---
193
+
194
+ ## Stacking & Gestures
195
+
196
+ Toasts stack like shadcn/Sonner: newer toasts sit in front and older ones peek out behind.
197
+
198
+ - **Desktop:** hover the stack to expand it. All timers pause while it is expanded.
199
+ - **Touch:** tap the stack to expand it, and tap outside to collapse it.
200
+ - **Swipe** left or right to dismiss. A quick flick also works.
201
+ - Timers also pause while the browser tab is hidden.
202
+ - All motion is `transform`/`opacity` only, and `prefers-reduced-motion` falls back to a plain fade.
203
+
204
+ ### Deduplicate with `id`
205
+
206
+ Passing an `id` that is already on screen updates that toast in place instead of stacking a duplicate:
122
207
 
123
208
  ```ts
124
- // Dismiss by ID
125
- const id = toast.loading({ title: 'Working...' })
126
- toast.dismiss(id)
209
+ toast.success({ id: 'autosave', title: 'Draft saved' })
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Reactive toasts
127
215
 
128
- // Dismiss all
129
- toast.dismissAll()
216
+ `title`, `description` and `progress` accept a `ref`, a `computed` or a getter. The toast updates when the value changes, with no update calls needed:
217
+
218
+ ```ts
219
+ const pct = ref(0)
220
+ toast.loading({
221
+ title: computed(() => `Uploading& ${pct.value}%`),
222
+ progress: pct,
223
+ })
130
224
  ```
131
225
 
132
226
  ---
133
227
 
134
- ## API Reference
228
+ ## toast.flash
135
229
 
136
- ### `useToast()`
230
+ Works like `toast()`, but survives a full page load. If the page reloads or navigates away while the toast is visible, it shows again on the next page. Use it after login redirects, OAuth round-trips and classic form posts. On SPA navigation it just stays on screen.
137
231
 
138
232
  ```ts
139
- const { toast, toasts, dismiss, dismissAll } = useToast()
233
+ toast.flash.success('Signed in') // also .error / .warning / .info, or toast.flash({ ... })
234
+ window.location.href = '/dashboard'
140
235
  ```
141
236
 
142
- | Property | Type | Description |
143
- |----------|------|-------------|
144
- | `toast(options)` | `(options: ToastOptions) => string` | Show a default toast, returns its ID |
145
- | `toast.success(options)` | `Function` | Success variant |
146
- | `toast.error(options)` | `Function` | Error variant |
147
- | `toast.warning(options)` | `Function` | Warning variant |
148
- | `toast.info(options)` | `Function` | Info variant |
149
- | `toast.loading(options)` | `Function` | Loading variant — no auto-dismiss |
150
- | `toast.promise(promise, opts)` | `Function` | Promise-based toast flow |
151
- | `toast.dismiss(id)` | `Function` | Dismiss a specific toast by ID |
152
- | `toast.dismissAll()` | `Function` | Dismiss all active toasts |
153
- | `toasts` | `Ref<Toast[]>` | Reactive list of all active toasts |
237
+ Only the title, description, variant, position and duration are carried over (as plain JSON in `sessionStorage`). Buttons and custom components are not.
154
238
 
155
239
  ---
156
240
 
157
- ### `ToastOptions`
241
+ ## Testing
242
+
243
+ The store can be read without rendering anything, so tests don't need a DOM or `<ToastContainer />`. The helpers work with Vitest, Jest or any runner that catches thrown errors.
158
244
 
159
245
  ```ts
160
- interface ToastOptions {
161
- title?: string
162
- description?: string
163
- variant?: 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'
164
- position?: 'top-right' | 'top-left' | 'top-center'
165
- | 'bottom-right' | 'bottom-left' | 'bottom-center'
166
- duration?: number // ms · default: 5000 · loading: Infinity
167
- dismissible?: boolean // default: true (false for loading)
168
- action?: {
169
- label: string
170
- onClick: () => void
171
- }
246
+ import { expectToast, expectNoToasts, getToasts, clickToastAction, resetToasts } from 'feather-toast-vue'
247
+
248
+ beforeEach(() => resetToasts()) // toasts, config and pending flashes
249
+
250
+ it('confirms before deleting', async () => {
251
+ const done = store.deleteFiles() // calls toast.confirm(...)
252
+ await clickToastAction({ title: /Delete/ }) // or (matcher, 'cancel')
253
+ await done
254
+ expectToast({ variant: 'success', title: 'Files deleted' })
255
+ })
256
+ ```
257
+
258
+ A failing `expectToast` lists every active toast, so you can see what showed up instead. `getToasts()` returns plain snapshots (refs resolved) that are safe for `toEqual`.
259
+
260
+ ---
261
+
262
+ ## Duplicate grouping
263
+
264
+ Firing the same plain toast (same variant, title, description and position) while it's still visible doesn't stack a copy. The visible toast gets a **�N** badge and its timer restarts. That keeps error storms and repeated clicks readable. Turn it off with `toast.config({ group: false })`.
265
+
266
+ ---
267
+
268
+ ## toast.retry
269
+
270
+ Runs an async task behind a loading toast. If it throws, the toast becomes an error with a **Retry** button that re-runs the task in the same toast. The returned promise resolves with the task's result, or rejects when the user dismisses the error or `maxRetries` runs out.
271
+
272
+ ```ts
273
+ const users = await toast.retry(() => api.get('/users'), {
274
+ loading: 'Loading users...',
275
+ success: (res) => `Loaded ${res.data.length} users`,
276
+ // error: (err) => ({ title: 'Could not load users', description: String(err) }), // default: the error's message
277
+ maxRetries: 3,
278
+ })
279
+ ```
280
+
281
+ ---
282
+
283
+ ## toast.confirm
284
+
285
+ Inline confirmation that resolves to `true` or `false`. Cancel, close, swipe and timeout all resolve `false`.
286
+
287
+ ```ts
288
+ const ok = await toast.confirm('Delete 3 files?', {
289
+ description: 'This cannot be undone.',
290
+ confirmLabel: 'Delete', // default "Confirm"
291
+ cancelLabel: 'Keep', // default "Cancel"
292
+ duration: 10000, // optional auto-cancel, shows a countdown bar
293
+ })
294
+ if (ok) await deleteFiles()
295
+ ```
296
+
297
+ Any toast can also take a secondary `cancel: { label, onClick }` button next to `action`.
298
+
299
+ ---
300
+
301
+ ## toast.undo
302
+
303
+ Renders a toast with a live countdown bar and an **Undo** action button. Use this for destructive operations where the user needs a grace period to revert.
304
+
305
+ ```ts
306
+ const { toast } = useFeatherToast()
307
+
308
+ function deleteFile(fileId: string) {
309
+ toast.undo('File "invoice-2026.pdf" deleted', {
310
+ description: 'Click Undo within 5 seconds to restore the file.',
311
+ duration: 5000,
312
+ undoLabel: 'Undo Delete',
313
+ onUndo: async () => {
314
+ await restoreFile(fileId)
315
+ toast.success('File restored to Documents.')
316
+ },
317
+ onDismiss: () => {
318
+ // Called only if countdown expired without clicking Undo
319
+ permanentlyDelete(fileId)
320
+ },
321
+ })
172
322
  }
173
323
  ```
174
324
 
325
+ ### UndoToastOptions
326
+
327
+ | Property | Type | Default | Description |
328
+ |---------------|-------------------------------|----------|----------------------------------------------------|
329
+ | `title` | `string` | � | Primary text of the toast. |
330
+ | `description` | `string` | � | Secondary explanation line. |
331
+ | `duration` | `number` | `5000` | Countdown timer in milliseconds. |
332
+ | `undoLabel` | `string` | `'Undo'` | Label for the revert button. |
333
+ | `onUndo` | `() => void \| Promise<void>` | � | Called when the user clicks Undo. |
334
+ | `onDismiss` | `() => void` | � | Called if the countdown expires without undo. |
335
+ | `position` | `ToastPosition` | Global | Overrides the global default position. |
336
+
175
337
  ---
176
338
 
177
- ### Positions
339
+ ## toast.step
340
+
341
+ Renders a single persistent loading toast and transitions it through sequential steps in-place � no flicker, no new cards. Ideal for multi-stage operations (deployments, imports, migrations).
178
342
 
343
+ ```ts
344
+ const { toast } = useFeatherToast()
345
+
346
+ async function runMigration() {
347
+ const workflow = toast.step({
348
+ title: 'Step 1/3: Validating schema...',
349
+ description: 'Checking data integrity.',
350
+ })
351
+
352
+ await validateSchema()
353
+ workflow.next({
354
+ title: 'Step 2/3: Deploying database migration...',
355
+ description: 'Applying schema patches to PostgreSQL.',
356
+ })
357
+
358
+ try {
359
+ await deployMigration()
360
+ workflow.done({
361
+ title: 'Step 3/3: Migration complete!',
362
+ description: 'Database updated with zero downtime.',
363
+ })
364
+ } catch (err) {
365
+ workflow.fail({
366
+ title: 'Migration failed',
367
+ description: err.message,
368
+ })
369
+ }
370
+ }
179
371
  ```
180
- +----------+------------+-----------+
181
- | top-left | top-center | top-right |
182
- | | | |
183
- | | | |
184
- | bot-left | bot-center | bot-right |
185
- +----------+------------+-----------+
372
+
373
+ ### StepToastController
374
+
375
+ | Method | Signature | Description |
376
+ |--------|-----------------------------------------|-------------------------------------------------|
377
+ | `next` | `(opts: Partial<ToastOptions>) => void` | Updates the toast in-place; keeps loading state.|
378
+ | `done` | `(opts?: Partial<ToastOptions>) => void`| Switches the toast to success variant. |
379
+ | `fail` | `(opts?: Partial<ToastOptions>) => void`| Switches the toast to error variant. |
380
+
381
+ ---
382
+
383
+ ## toast.progress
384
+
385
+ Spawns a loading toast with an explicit progress bar you control programmatically. Use for uploads, downloads, or any task with measurable progress.
386
+
387
+ ```ts
388
+ const { toast } = useFeatherToast()
389
+
390
+ async function uploadFile(file: File) {
391
+ const progressToast = toast.progress({
392
+ title: 'Uploading file...',
393
+ description: '0% completed',
394
+ })
395
+
396
+ xhr.addEventListener('progress', (e) => {
397
+ const pct = Math.round((e.loaded / e.total) * 100)
398
+ progressToast.setProgress(pct, { description: `${pct}% completed` })
399
+ })
400
+
401
+ await uploadPromise
402
+
403
+ progressToast.done({
404
+ title: 'Upload complete!',
405
+ description: `${file.name} saved to storage.`,
406
+ })
407
+ }
186
408
  ```
187
409
 
410
+ ### ProgressToastController
411
+
412
+ | Method | Signature | Description |
413
+ |---------------|-------------------------------------------------------|-----------------------------------------------|
414
+ | `setProgress` | `(pct: number, opts?: Partial<ToastOptions>) => void` | Updates the bar (0�100) and toast text. |
415
+ | `done` | `(opts?: Partial<ToastOptions>) => void` | Completes the task; switches to success. |
416
+ | `fail` | `(opts?: Partial<ToastOptions>) => void` | Marks the task failed; switches to error. |
417
+
188
418
  ---
189
419
 
190
- ## Design System
420
+ ## toast.copy
191
421
 
192
- ### Color Tokens
422
+ Copies text to the system clipboard and automatically shows a confirmation toast.
193
423
 
194
- | Token | Value | Usage |
195
- |-------|-------|-------|
196
- | `--toast-bg` | `#ffffff` | Card background |
197
- | `--toast-border` | `#e4e4e7` | Card border |
198
- | `--toast-text` | `#18181b` | Title text |
199
- | `--toast-text-muted` | `#71717a` | Description text |
200
- | `--toast-shadow` | `0 2px 8px rgba(0,0,0,0.06)` | Card elevation |
201
- | `--toast-radius` | `10px` | Card border radius |
202
- | `--toast-width` | `360px` | Card width |
424
+ ```ts
425
+ const { toast } = useFeatherToast()
203
426
 
204
- ### Icon Accent Colors
427
+ async function copyLink() {
428
+ await toast.copy('https://your-app.dev/share/xyz', {
429
+ title: 'Link copied!',
430
+ description: 'Share link has been copied to your clipboard.',
431
+ })
432
+ }
433
+ ```
205
434
 
206
- | Variant | Hex |
207
- |---------|-----|
208
- | Success | `#16a34a` |
209
- | Error | `#dc2626` |
210
- | Warning | `#d97706` |
211
- | Info | `#2563eb` |
212
- | Loading | `#71717a` |
435
+ On success: shows a `success` toast.
436
+ On failure: shows an `error` toast with the browser error message.
213
437
 
214
- ### Customization via CSS Variables
438
+ ---
215
439
 
216
- ```css
217
- /* Override in your global CSS */
218
- :root {
219
- --toast-bg: #ffffff;
220
- --toast-border: #e4e4e7;
221
- --toast-text: #18181b;
222
- --toast-text-muted: #71717a;
223
- --toast-radius: 10px;
224
- --toast-width: 360px;
225
- --toast-gap: 8px;
226
- --toast-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
440
+ ## toast.promise
441
+
442
+ Tracks an existing promise and automatically transitions loading ? success / error.
443
+
444
+ ```ts
445
+ const { toast } = useFeatherToast()
446
+
447
+ async function createProject(data: ProjectPayload) {
448
+ const request = fetch('/api/projects', {
449
+ method: 'POST',
450
+ body: JSON.stringify(data),
451
+ })
452
+
453
+ await toast.promise(request, {
454
+ loading: {
455
+ title: 'Creating project...',
456
+ description: 'Setting up your workspace.',
457
+ },
458
+ success: () => ({
459
+ title: 'Project created!',
460
+ description: 'Your workspace is ready.',
461
+ }),
462
+ error: (err) => ({
463
+ title: 'Failed to create project',
464
+ description: err instanceof Error ? err.message : 'An unknown error occurred.',
465
+ }),
466
+ })
227
467
  }
228
468
  ```
229
469
 
470
+ > `toast.promise` re-throws the rejection after showing the error toast. Wrap calls in `try/catch` or `.catch()` to handle the error downstream.
471
+
472
+ ### ToastPromiseOptions
473
+
474
+ | Property | Type | Description |
475
+ |-----------|---------------------------------------------------|------------------------------------------------|
476
+ | `loading` | `ToastOptions` | Shown while the promise is pending. |
477
+ | `success` | `ToastOptions \| ((data: T) => ToastOptions)` | Shown on fulfillment. |
478
+ | `error` | `ToastOptions \| ((err: unknown) => ToastOptions)`| Shown on rejection. |
479
+
230
480
  ---
231
481
 
232
- ## Animation Spec
482
+ ## toast.custom
233
483
 
234
- | Phase | Duration | Easing |
235
- |-------|----------|--------|
236
- | Enter | 400ms | `cubic-bezier(0.16, 1, 0.3, 1)` — spring |
237
- | Exit | 220ms | `cubic-bezier(0.4, 0, 1, 1)` — snappy |
238
- | Sibling reflow | 350ms | `cubic-bezier(0.16, 1, 0.3, 1)` — spring |
239
- | Swipe snap-back | 360ms | `cubic-bezier(0.16, 1, 0.3, 1)` — spring |
484
+ Render any Vue component, render function, or `h()` VNode directly inside a toast.
485
+
486
+ ```ts
487
+ import { h } from 'vue'
488
+ import { useFeatherToast } from 'feather-toast-vue'
489
+
490
+ const { toast } = useFeatherToast()
491
+
492
+ function showInvite(user: { name: string; initials: string }) {
493
+ toast.custom({
494
+ render: () =>
495
+ h('div', { style: 'display:flex;align-items:center;gap:12px;' }, [
496
+ h('div', {
497
+ style: 'width:32px;height:32px;border-radius:50%;background:#6366f1;color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;',
498
+ }, user.initials),
499
+ h('div', [
500
+ h('p', { style: 'font-size:13px;font-weight:500;margin:0;' }, `${user.name} invited you`),
501
+ h('p', { style: 'font-size:11px;opacity:0.65;margin:3px 0 0;' }, 'to collaborate on Project Alpha'),
502
+ ]),
503
+ ]),
504
+ duration: 6000,
505
+ })
506
+ }
507
+ ```
508
+
509
+ You can also pass a single-file component directly:
510
+
511
+ ```ts
512
+ import InviteCard from '@/components/InviteCard.vue'
513
+
514
+ toast.custom({
515
+ render: () => h(InviteCard, { user }),
516
+ duration: 6000,
517
+ })
518
+ ```
240
519
 
241
520
  ---
242
521
 
243
- ## File Structure
522
+ ## toast.network
523
+
524
+ `toast.network()` automatically monitors the browser's `online` and `offline` events. It:
525
+
526
+ - Shows a **persistent, non-dismissible offline toast** the instant the device loses internet.
527
+ - Dismisses it and shows a **success toast** when the connection is restored.
528
+ - Detects if the device is **already offline at registration time** and shows the toast immediately.
529
+
530
+ Returns a `NetworkToastController` with a `stop()` method to clean up listeners.
531
+
532
+ ### Recommended: App-Level Registration
533
+
534
+ Register once in your root `App.vue` to monitor connectivity for the entire application.
535
+
536
+ ```vue
537
+ <!-- App.vue -->
538
+ <script setup lang="ts">
539
+ import { onMounted, onUnmounted } from 'vue'
540
+ import { useFeatherToast } from 'feather-toast-vue'
541
+ import type { NetworkToastController } from 'feather-toast-vue'
542
+
543
+ const { toast } = useFeatherToast()
544
+ let networkMonitor: NetworkToastController | null = null
545
+
546
+ onMounted(() => {
547
+ networkMonitor = toast.network()
548
+ })
244
549
 
550
+ onUnmounted(() => {
551
+ networkMonitor?.stop()
552
+ })
553
+ </script>
245
554
  ```
246
- src/
247
- ├── assets/
248
- │ └── toast.css # All styles & animations
249
- ├── components/
250
- │ ├── Toast.vue # Individual toast card
251
- │ └── ToastContainer.vue # Positional viewport manager
252
- ├── composables/
253
- │ └── useToast.ts # Reactive toast API
254
- ├── plugins/
255
- │ └── toast.ts # Vue plugin (app.use)
256
- ├── types/
257
- │ └── toast.ts # TypeScript interfaces
258
- └── main.ts # App entry point
555
+
556
+ ### Custom Messages
557
+
558
+ ```ts
559
+ const monitor = toast.network({
560
+ offline: {
561
+ title: 'No internet connection',
562
+ description: 'Please check your Wi-Fi or mobile data.',
563
+ position: 'top-center',
564
+ },
565
+ online: {
566
+ title: 'Connection restored',
567
+ description: 'You are back online.',
568
+ position: 'top-center',
569
+ },
570
+ })
571
+
572
+ // Stop monitoring when no longer needed
573
+ monitor.stop()
259
574
  ```
260
575
 
576
+ ### NetworkToastOptions
577
+
578
+ | Option | Type | Default | Description |
579
+ |-----------|-------------------------|----------|------------------------------------------------------|
580
+ | `offline` | `Partial<ToastOptions>` | Built-in | Toast shown when internet connection is lost. |
581
+ | `online` | `Partial<ToastOptions>` | Built-in | Toast shown when internet connection is restored. |
582
+
583
+ ### NetworkToastController
584
+
585
+ | Method | Signature | Description |
586
+ |--------|--------------|-----------------------------------------------------------------|
587
+ | `stop` | `() => void` | Removes event listeners and dismisses the offline toast if present. |
588
+
261
589
  ---
262
590
 
263
- ## Bundle Size
591
+ ## TypeScript API Reference
592
+
593
+ All types are exported directly from the package:
594
+
595
+ ```ts
596
+ import type {
597
+ Toast,
598
+ ToastOptions,
599
+ ToastAction,
600
+ ToastVariant,
601
+ ToastPosition,
602
+ ToastConfig,
603
+ ToastPromiseOptions,
604
+ UndoToastOptions,
605
+ StepToastController,
606
+ ProgressToastController,
607
+ NetworkToastOptions,
608
+ NetworkToastController,
609
+ ToastComponent,
610
+ } from 'feather-toast-vue'
611
+ ```
612
+
613
+ ### ToastOptions
614
+
615
+ | Property | Type | Default | Description |
616
+ |---------------|-----------------------------------------|------------------|------------------------------------------------|
617
+ | `title` | `string` | � | Primary text of the toast. |
618
+ | `description` | `string` | `undefined` | Secondary line below the title. |
619
+ | `variant` | `ToastVariant` | `'default'` | Controls the icon and accent color. |
620
+ | `position` | `ToastPosition` | `'bottom-right'` | One of six supported positions. |
621
+ | `duration` | `number` | `5000` | Auto-dismiss delay in milliseconds. |
622
+ | `dismissible` | `boolean` | `true` | Whether the user can close the toast manually. |
623
+ | `progress` | `number` (0�100) | `undefined` | Explicit progress bar percentage. |
624
+ | `showProgress`| `boolean` | `false` | Renders an animated countdown bar. |
625
+ | `action` | `{ label: string; onClick: () => void }`| `undefined` | Inline action button. |
626
+ | `onDismiss` | `() => void` | `undefined` | Called when the toast is dismissed. |
627
+
628
+ ### ToastConfig
629
+
630
+ | Property | Type | Default | Description |
631
+ |------------|-----------------|------------------|--------------------------------------------------|
632
+ | `max` | `number` | `5` | Max visible toasts per position (0 = unlimited). |
633
+ | `duration` | `number` | `5000` | Default auto-dismiss timeout in milliseconds. |
634
+ | `position` | `ToastPosition` | `'bottom-right'` | Default spawn position. |
264
635
 
265
- | File | Raw | Gzipped |
266
- |------|-----|---------|
267
- | JS (all files) | ~12 KB | ~4 KB |
268
- | CSS | ~6 KB | ~2 KB |
269
- | **Total** | **~18 KB** | **~6 KB** |
636
+ ---
637
+
638
+ ## CSS Custom Properties (Theming)
270
639
 
271
- Zero external dependencies beyond Vue 3.
640
+ ```css
641
+ :root {
642
+ --toast-bg: #18181b;
643
+ --toast-border: rgba(255, 255, 255, 0.08);
644
+ --toast-text: #fafafa;
645
+ --toast-text-muted: #a1a1aa;
646
+ --toast-success: #22c55e;
647
+ --toast-error: #ef4444;
648
+ --toast-warning: #f59e0b;
649
+ --toast-info: #3b82f6;
650
+ --toast-radius: 8px;
651
+ --toast-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
652
+ --toast-z-index: 9999;
653
+ }
654
+ ```
272
655
 
273
656
  ---
274
657
 
275
658
  ## License
276
659
 
277
- MIT © feather-toast
660
+ MIT (c) zin-ix