@opentui-ui/toast 0.0.1

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 ADDED
@@ -0,0 +1,660 @@
1
+ <div align="center">
2
+ <img width="512" src="https://github.com/user-attachments/assets/ab2088f4-167b-4290-aeed-03cd8e1750d9" />
3
+ </div>
4
+
5
+ <br />
6
+
7
+ <div align="center"><strong>A beautiful toast library for terminal UIs built on OpenTUI</strong></div>
8
+ <div align="center">
9
+ <sub>Built by <a href="https://x.com/msmps_">Matt Simpson</a> | Inspired by <a href="https://sonner.emilkowal.ski/">Sonner</a></sub>
10
+ </div>
11
+
12
+ <br />
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ bun add @opentui-ui/toast
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```ts
23
+ import { toast, ToasterRenderable } from "@opentui-ui/toast";
24
+
25
+ // 1. Add the toaster to your app (one line!)
26
+ ctx.root.add(new ToasterRenderable(ctx));
27
+
28
+ // 2. Show toasts from anywhere!
29
+ toast("Hello World");
30
+ ```
31
+
32
+ That's it! No providers, no context, no configuration required.
33
+
34
+ ## Quick Reference
35
+
36
+ ```ts
37
+ // Toast Types
38
+ toast("message"); // default
39
+ toast.success("message"); // green checkmark
40
+ toast.error("message"); // red X
41
+ toast.warning("message"); // yellow warning
42
+ toast.info("message"); // blue info
43
+ toast.loading("message"); // animated spinner
44
+
45
+ // Common Patterns
46
+ toast("msg", { description: "details" }); // Two-line toast
47
+ toast("msg", { duration: Infinity }); // Persistent (manual dismiss)
48
+ toast("msg", { action: { label: "Undo", onClick: fn } }); // With button
49
+
50
+ // Dismiss
51
+ const id = toast("Hello");
52
+ toast.dismiss(id); // Dismiss one
53
+ toast.dismiss(); // Dismiss all
54
+
55
+ // Update existing toast
56
+ const id = toast.loading("Uploading...");
57
+ toast.success("Done!", { id }); // Updates in place
58
+ ```
59
+
60
+ ## Toast Types
61
+
62
+ ```ts
63
+ toast("Default notification");
64
+ toast.success("Operation completed!");
65
+ toast.error("Something went wrong");
66
+ toast.warning("Please check your input");
67
+ toast.info("Did you know?");
68
+ toast.loading("Processing...");
69
+ ```
70
+
71
+ ## With Descriptions
72
+
73
+ ```ts
74
+ toast.success("File uploaded", {
75
+ description: "Your file has been saved to the cloud",
76
+ });
77
+ ```
78
+
79
+ ## Promise Toast
80
+
81
+ The `toast.promise()` API automatically shows loading, success, and error states:
82
+
83
+ ```ts
84
+ toast.promise(fetchData(), {
85
+ loading: "Fetching data...",
86
+ success: "Data loaded successfully!",
87
+ error: "Failed to load data",
88
+ });
89
+
90
+ // With dynamic messages
91
+ toast.promise(saveUser(data), {
92
+ loading: "Saving user...",
93
+ success: (user) => `${user.name} has been saved`,
94
+ error: (err) => `Error: ${err.message}`,
95
+ });
96
+ ```
97
+
98
+ ## Actions
99
+
100
+ Add interactive buttons to your toasts:
101
+
102
+ ```ts
103
+ toast("File deleted", {
104
+ action: {
105
+ label: "Undo",
106
+ onClick: () => restoreFile(),
107
+ },
108
+ });
109
+ ```
110
+
111
+ ## Updating Toasts
112
+
113
+ Update an existing toast by passing its ID:
114
+
115
+ ```ts
116
+ const id = toast.loading("Uploading...");
117
+
118
+ // Later...
119
+ toast.success("Upload complete!", { id });
120
+
121
+ // Or on error
122
+ toast.error("Upload failed", { id });
123
+ ```
124
+
125
+ ## Dismissing Toasts
126
+
127
+ ```ts
128
+ // Dismiss a specific toast
129
+ const id = toast("Hello");
130
+ toast.dismiss(id);
131
+
132
+ // Dismiss all toasts
133
+ toast.dismiss();
134
+ ```
135
+
136
+ ## Duration
137
+
138
+ ### Duration Presets
139
+
140
+ Use the built-in `TOAST_DURATION` presets for consistent, readable duration values:
141
+
142
+ ```ts
143
+ import { toast, TOAST_DURATION } from "@opentui-ui/toast";
144
+
145
+ // Quick confirmation (2s)
146
+ toast.success("Copied!", { duration: TOAST_DURATION.SHORT });
147
+
148
+ // Standard duration (4s) - this is the default
149
+ toast("Hello", { duration: TOAST_DURATION.DEFAULT });
150
+
151
+ // Important message (6s)
152
+ toast.warning("Check your settings", { duration: TOAST_DURATION.LONG });
153
+
154
+ // Critical information (10s)
155
+ toast.error("Connection lost", { duration: TOAST_DURATION.EXTENDED });
156
+
157
+ // Manual dismiss only
158
+ toast.info("Click to continue", { duration: TOAST_DURATION.PERSISTENT });
159
+ ```
160
+
161
+ ### Duration Preset Values
162
+
163
+ | Preset | Duration | Use Case |
164
+ | ------------ | -------- | ------------------------- |
165
+ | `SHORT` | 2000ms | Brief confirmations |
166
+ | `DEFAULT` | 4000ms | Standard notifications |
167
+ | `LONG` | 6000ms | Important messages |
168
+ | `EXTENDED` | 10000ms | Critical information |
169
+ | `PERSISTENT` | Infinity | Requires manual dismissal |
170
+
171
+ ### Custom Duration
172
+
173
+ You can also pass any number in milliseconds:
174
+
175
+ ```ts
176
+ // Custom duration (in milliseconds)
177
+ toast("This disappears in 10 seconds", {
178
+ duration: 10000,
179
+ });
180
+
181
+ // Persistent toast (won't auto-dismiss)
182
+ toast("I'll stay until dismissed", {
183
+ duration: Infinity,
184
+ });
185
+ ```
186
+
187
+ ## Themes
188
+
189
+ Optional theme presets are available via a separate import. These override the built-in defaults with alternative visual styles.
190
+
191
+ ```ts
192
+ import { ToasterRenderable } from "@opentui-ui/toast";
193
+ import { minimal } from "@opentui-ui/toast/themes";
194
+
195
+ const toaster = new ToasterRenderable(ctx, minimal);
196
+ ```
197
+
198
+ ### Available Themes
199
+
200
+ | Theme | Description |
201
+ | ------------ | --------------------------------- |
202
+ | `minimal` | Clean and unobtrusive, no borders |
203
+ | `monochrome` | Grayscale only, no colors |
204
+
205
+ ### Customizing Themes
206
+
207
+ Spread a theme and override specific options:
208
+
209
+ ```ts
210
+ import { minimal } from "@opentui-ui/toast/themes";
211
+
212
+ const toaster = new ToasterRenderable(ctx, {
213
+ ...minimal,
214
+ position: "bottom-right",
215
+ stackingMode: "stack",
216
+ });
217
+ ```
218
+
219
+ ### Theme Utilities
220
+
221
+ ```ts
222
+ import { themes } from "@opentui-ui/toast/themes";
223
+
224
+ // Access all themes
225
+ themes.minimal;
226
+ themes.monochrome;
227
+ ```
228
+
229
+ ### Theme Types
230
+
231
+ ```ts
232
+ import type { ToasterTheme } from "@opentui-ui/toast/themes";
233
+ ```
234
+
235
+ ## Toaster Configuration
236
+
237
+ Customize the toaster appearance and behavior:
238
+
239
+ ```ts
240
+ const toaster = new ToasterRenderable(ctx, {
241
+ // Position on screen
242
+ position: "bottom-right", // 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
243
+
244
+ // Gap between toasts (terminal rows)
245
+ gap: 1,
246
+
247
+ // How to handle multiple toasts
248
+ stackingMode: "single", // 'single' | 'stack'
249
+
250
+ // Max visible toasts in stack mode
251
+ visibleToasts: 3,
252
+
253
+ // Show close button on toasts
254
+ closeButton: false,
255
+
256
+ // Maximum width for toasts
257
+ maxWidth: 60,
258
+
259
+ // Offset from screen edges
260
+ offset: {
261
+ top: 1,
262
+ right: 2,
263
+ bottom: 1,
264
+ left: 2,
265
+ },
266
+
267
+ // Custom icons
268
+ icons: {
269
+ success: "✓",
270
+ error: "✗",
271
+ warning: "⚠",
272
+ info: "ℹ",
273
+ loading: "◌",
274
+ close: "×",
275
+ },
276
+
277
+ // Toast styling and duration options
278
+ toastOptions: {
279
+ style: {
280
+ /* base styles */
281
+ },
282
+ duration: 4000,
283
+ success: {
284
+ style: {
285
+ /* overrides */
286
+ },
287
+ duration: 3000,
288
+ },
289
+ // ... other types
290
+ },
291
+ });
292
+ ```
293
+
294
+ ### ToasterOptions Reference
295
+
296
+ | Option | Type | Default | Description |
297
+ | --------------- | ------------------------------ | ------------------------------------------ | ------------------------------------------------------------ |
298
+ | `position` | `Position` | `"bottom-right"` | Position on screen |
299
+ | `gap` | `number` | `1` | Gap between toasts (terminal rows) |
300
+ | `stackingMode` | `StackingMode` | `"single"` | How to handle multiple toasts: `"single"` or `"stack"` |
301
+ | `visibleToasts` | `number` | `3` | Max visible toasts in stack mode |
302
+ | `closeButton` | `boolean` | `false` | Show close button on toasts |
303
+ | `maxWidth` | `number` | `60` | Maximum width for toasts (terminal columns) |
304
+ | `offset` | `ToasterOffset` | `{ top: 1, right: 2, bottom: 1, left: 2 }` | Offset from screen edges |
305
+ | `icons` | `Partial<ToastIcons> \| false` | - | Custom icons for each toast type, or `false` to disable |
306
+ | `toastOptions` | `ToastOptions` | - | Default toast options (styles, duration, per-type overrides) |
307
+
308
+ ## Styling
309
+
310
+ Configure toast styles using the `toastOptions` prop:
311
+
312
+ ```ts
313
+ const toaster = new ToasterRenderable(ctx, {
314
+ toastOptions: {
315
+ // Base styles applied to all toasts
316
+ style: {
317
+ backgroundColor: "#1a1a1a",
318
+ foregroundColor: "#ffffff",
319
+ borderColor: "#333333",
320
+ borderStyle: "rounded", // 'single' | 'double' | 'rounded' | 'heavy'
321
+ paddingX: 1,
322
+ paddingY: 0,
323
+ },
324
+ // Default duration for all toasts
325
+ duration: 4000,
326
+ // Per-type overrides
327
+ success: {
328
+ style: { borderColor: "#22c55e" },
329
+ duration: 3000,
330
+ },
331
+ error: {
332
+ style: { borderColor: "#ef4444" },
333
+ duration: 6000,
334
+ },
335
+ warning: {
336
+ style: { borderColor: "#f59e0b" },
337
+ },
338
+ info: {
339
+ style: { borderColor: "#3b82f6" },
340
+ },
341
+ loading: {
342
+ style: { borderColor: "#6b7280" },
343
+ },
344
+ },
345
+ });
346
+ ```
347
+
348
+ ### ToastStyle Reference
349
+
350
+ | Property | Type | Default | Description |
351
+ | ------------------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
352
+ | `border` | `boolean \| BorderSides[]` | `true` | Border configuration. `true` = all sides, `false` = none, or array like `["left", "right"]` |
353
+ | `borderColor` | `string` | `"#333333"` | Border color (hex, rgb, or named) |
354
+ | `borderStyle` | `BorderStyle` | `"single"` | Border style: `"single"` \| `"double"` \| `"rounded"` \| `"heavy"` |
355
+ | `customBorderChars` | `BorderCharacters` | - | Custom border characters (overrides `borderStyle`) |
356
+ | `minHeight` | `number` | `3` | Minimum height in terminal rows |
357
+ | `maxWidth` | `number` | - | Maximum width in terminal columns |
358
+ | `minWidth` | `number` | - | Minimum width in terminal columns |
359
+ | `padding` | `number` | - | Uniform padding (all sides) |
360
+ | `paddingX` | `number` | `1` | Horizontal padding (left + right) |
361
+ | `paddingY` | `number` | `0` | Vertical padding (top + bottom) |
362
+ | `paddingTop` | `number` | - | Top padding |
363
+ | `paddingBottom` | `number` | - | Bottom padding |
364
+ | `paddingLeft` | `number` | - | Left padding |
365
+ | `paddingRight` | `number` | - | Right padding |
366
+ | `backgroundColor` | `string` | `"#1a1a1a"` | Background color |
367
+ | `foregroundColor` | `string` | `"#ffffff"` | Text/foreground color |
368
+ | `mutedColor` | `string` | `"#6b7280"` | Muted text color (for descriptions) |
369
+ | `iconColor` | `string` | - | Icon color (defaults to `borderColor`) |
370
+
371
+ ### Custom Border Characters
372
+
373
+ For full control over border rendering, use `customBorderChars` to define each border character:
374
+
375
+ ```ts
376
+ const toaster = new ToasterRenderable(ctx, {
377
+ toastOptions: {
378
+ style: {
379
+ border: ["left", "right"],
380
+ customBorderChars: {
381
+ topLeft: "",
382
+ topRight: "",
383
+ bottomLeft: "",
384
+ bottomRight: "",
385
+ horizontal: " ",
386
+ vertical: "┃",
387
+ topT: "",
388
+ bottomT: "",
389
+ leftT: "",
390
+ rightT: "",
391
+ cross: "",
392
+ },
393
+ },
394
+ },
395
+ });
396
+ ```
397
+
398
+ This is useful for creating unique border styles, like a vertical bar accent:
399
+
400
+ ```ts
401
+ // Vertical bar on left and right only
402
+ border: ["left", "right"],
403
+ customBorderChars: {
404
+ vertical: "┃",
405
+ // Other characters can be empty strings
406
+ topLeft: "", topRight: "", bottomLeft: "", bottomRight: "",
407
+ horizontal: " ", topT: "", bottomT: "", leftT: "", rightT: "", cross: "",
408
+ },
409
+ ```
410
+
411
+ ### Per-Toast Styles
412
+
413
+ Override styles on individual toasts:
414
+
415
+ ```ts
416
+ toast.success("Custom styled!", {
417
+ style: {
418
+ borderColor: "#8b5cf6",
419
+ backgroundColor: "#1e1b4b",
420
+ },
421
+ });
422
+ ```
423
+
424
+ ## Icon Sets
425
+
426
+ Choose from built-in icon sets based on terminal capabilities:
427
+
428
+ ```ts
429
+ import {
430
+ DEFAULT_ICONS, // Unicode icons (default)
431
+ ASCII_ICONS, // ASCII-only for limited terminals
432
+ MINIMAL_ICONS, // Single character icons
433
+ EMOJI_ICONS, // Emoji icons
434
+ } from "@opentui-ui/toast";
435
+
436
+ // Use ASCII icons for terminals with limited Unicode support
437
+ const toaster = new ToasterRenderable(ctx, {
438
+ icons: ASCII_ICONS,
439
+ });
440
+
441
+ // Use emoji icons for terminals with good emoji support
442
+ const toaster = new ToasterRenderable(ctx, {
443
+ icons: EMOJI_ICONS,
444
+ });
445
+ ```
446
+
447
+ ### Custom Loading Spinner
448
+
449
+ The `loading` icon can be either a static string or an animated spinner configuration:
450
+
451
+ ```ts
452
+ const toaster = new ToasterRenderable(ctx, {
453
+ icons: {
454
+ // Static loading icon (no animation)
455
+ loading: "...",
456
+
457
+ // Or animated spinner
458
+ loading: {
459
+ frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
460
+ interval: 80,
461
+ },
462
+ },
463
+ });
464
+ ```
465
+
466
+ Some spinner examples:
467
+
468
+ ```ts
469
+ // Dots spinner
470
+ { frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], interval: 80 }
471
+
472
+ // Circle spinner (default)
473
+ { frames: ["◜", "◠", "◝", "◞", "◡", "◟"], interval: 100 }
474
+
475
+ // Simple ASCII spinner
476
+ { frames: ["-", "\\", "|", "/"], interval: 100 }
477
+
478
+ // Bouncing bar
479
+ { frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]", "[ ]"], interval: 120 }
480
+ ```
481
+
482
+ ### Disabling Icons
483
+
484
+ To disable icons entirely, set `icons: false`:
485
+
486
+ ```ts
487
+ const toaster = new ToasterRenderable(ctx, {
488
+ icons: false,
489
+ });
490
+ ```
491
+
492
+ Individual toasts can still override this by providing a custom `icon`:
493
+
494
+ ```ts
495
+ // Icons are disabled globally, but this toast will show a custom icon
496
+ toast.success("Done!", { icon: "✓" });
497
+ ```
498
+
499
+ ## API Reference
500
+
501
+ ### `toast(message, options?)`
502
+
503
+ Show a default toast.
504
+
505
+ ### `toast.success(message, options?)`
506
+
507
+ Show a success toast with a checkmark icon.
508
+
509
+ ### `toast.error(message, options?)`
510
+
511
+ Show an error toast with an X icon.
512
+
513
+ ### `toast.warning(message, options?)`
514
+
515
+ Show a warning toast with a warning icon.
516
+
517
+ ### `toast.info(message, options?)`
518
+
519
+ Show an info toast with an info icon.
520
+
521
+ ### `toast.loading(message, options?)`
522
+
523
+ Show a loading toast with an animated spinner.
524
+
525
+ ### `toast.promise(promise, options)`
526
+
527
+ Show a toast that updates based on promise state.
528
+
529
+ ### `toast.dismiss(id?)`
530
+
531
+ Dismiss a specific toast by ID, or all toasts if no ID provided.
532
+
533
+ ### `toast.getToasts()`
534
+
535
+ Get all currently active toasts.
536
+
537
+ ### `toast.getHistory()`
538
+
539
+ Get all toasts ever created (including dismissed).
540
+
541
+ ### Toast Options
542
+
543
+ | Option | Type | Default | Description |
544
+ | ------------- | -------------------------- | ---------- | ---------------------------------- |
545
+ | `id` | `string \| number` | auto | Unique identifier for the toast |
546
+ | `description` | `string \| (() => string)` | - | Secondary text below the title |
547
+ | `duration` | `number` | `4000` | Time in ms before auto-dismiss |
548
+ | `dismissible` | `boolean` | `true` | Whether the toast can be dismissed |
549
+ | `icon` | `string` | type-based | Custom icon to display |
550
+ | `action` | `{ label, onClick }` | - | Action button configuration |
551
+ | `closeButton` | `boolean` | `false` | Show close button |
552
+ | `style` | `ToastStyle` | - | Per-toast style overrides |
553
+ | `onDismiss` | `(toast) => void` | - | Callback when dismissed |
554
+ | `onAutoClose` | `(toast) => void` | - | Callback when auto-closed |
555
+
556
+ ## Examples
557
+
558
+ ### Basic Example
559
+
560
+ ```ts
561
+ import { createCliRenderer } from "@opentui/core";
562
+ import { toast, ToasterRenderable } from "@opentui-ui/toast";
563
+
564
+ const renderer = await createCliRenderer();
565
+
566
+ // Add toaster
567
+ const toaster = new ToasterRenderable(renderer, {
568
+ position: "bottom-right",
569
+ });
570
+ renderer.root.add(toaster);
571
+
572
+ // Show some toasts
573
+ toast.success("Application started!");
574
+
575
+ setTimeout(() => {
576
+ toast.info("Press 'q' to quit");
577
+ }, 1000);
578
+ ```
579
+
580
+ ### Async Operation
581
+
582
+ ```ts
583
+ async function saveData(data: unknown) {
584
+ toast.promise(
585
+ fetch("/api/save", {
586
+ method: "POST",
587
+ body: JSON.stringify(data),
588
+ }),
589
+ {
590
+ loading: "Saving...",
591
+ success: "Saved!",
592
+ error: "Failed to save",
593
+ }
594
+ );
595
+ }
596
+ ```
597
+
598
+ ### Manual Loading State
599
+
600
+ ```ts
601
+ async function uploadFile(file: File) {
602
+ const id = toast.loading("Uploading...");
603
+
604
+ try {
605
+ const result = await upload(file);
606
+ toast.success(`Uploaded ${result.filename}`, { id });
607
+ } catch (error) {
608
+ toast.error("Upload failed", { id });
609
+ }
610
+ }
611
+ ```
612
+
613
+ ## TypeScript
614
+
615
+ Full TypeScript support with exported types:
616
+
617
+ ```ts
618
+ import type {
619
+ Action, // Action button configuration
620
+ ExternalToast, // Options for toast() calls
621
+ Position, // Toaster position type
622
+ PromiseData, // Configuration for toast.promise()
623
+ SpinnerConfig, // Animated spinner configuration { frames, interval }
624
+ StackingMode, // Stacking mode ('single' | 'stack')
625
+ ToasterOffset, // Offset configuration for positioning
626
+ ToasterOptions, // Configuration for ToasterRenderable
627
+ ToastIcons, // Custom icon set type
628
+ ToastOptions, // Default toast options (styles, duration, per-type overrides)
629
+ ToastStyle, // Per-toast styling options
630
+ ToastType, // Toast type variants
631
+ TypeToastOptions, // Per-type options (style + duration)
632
+ } from "@opentui-ui/toast";
633
+
634
+ // Border types (for customBorderChars) come from @opentui/core
635
+ import type { BorderCharacters, BorderSides, BorderStyle } from "@opentui/core";
636
+
637
+ // Type guards
638
+ import { isAction, isSpinnerConfig } from "@opentui-ui/toast";
639
+ ```
640
+
641
+ ### Constants
642
+
643
+ ```ts
644
+ import { TOAST_DURATION } from "@opentui-ui/toast";
645
+
646
+ // Duration presets
647
+ TOAST_DURATION.SHORT; // 2000ms - brief confirmations
648
+ TOAST_DURATION.DEFAULT; // 4000ms - standard notifications
649
+ TOAST_DURATION.LONG; // 6000ms - important messages
650
+ TOAST_DURATION.EXTENDED; // 10000ms - critical information
651
+ TOAST_DURATION.PERSISTENT; // Infinity - manual dismiss only
652
+ ```
653
+
654
+ ## Acknowledgments
655
+
656
+ Inspired by [Sonner](https://sonner.emilkowal.ski/) by Emil Kowalski.
657
+
658
+ ## License
659
+
660
+ MIT