@catalystsoftware/ui 1.0.4 → 1.0.5

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.
Files changed (157) hide show
  1. package/data/tailwind.config.js +261 -3821
  2. package/dist/components/catalyst-ui/buttons/burger.tsx +207 -0
  3. package/dist/components/catalyst-ui/core/data-display/timeline.tsx +210 -0
  4. package/dist/components/catalyst-ui/core/feedback/alert.tsx +491 -0
  5. package/dist/components/catalyst-ui/core/feedback/spinner-1.tsx +65 -0
  6. package/dist/components/catalyst-ui/core/feedback/toast.tsx +1857 -0
  7. package/dist/components/catalyst-ui/core/navigation/menu.tsx +164 -0
  8. package/dist/components/catalyst-ui/forms/toggle-class.tsx +176 -0
  9. package/dist/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +419 -0
  10. package/dist/components/catalyst-ui/hooks/use-counter.tsx +13 -0
  11. package/dist/components/catalyst-ui/hooks/use-event-listener.tsx +23 -0
  12. package/dist/components/catalyst-ui/hooks/use-export-markdown.tsx +47 -0
  13. package/dist/components/catalyst-ui/hooks/use-focus.tsx +17 -0
  14. package/dist/components/catalyst-ui/hooks/use-interval.tsx +23 -0
  15. package/dist/components/catalyst-ui/hooks/use-is-client.tsx +16 -0
  16. package/dist/components/catalyst-ui/hooks/use-media-query.tsx +19 -0
  17. package/dist/components/catalyst-ui/hooks/use-mobile.tsx +19 -0
  18. package/dist/components/catalyst-ui/hooks/use-resize-observer.tsx +81 -0
  19. package/dist/components/catalyst-ui/hooks/use-timeout.tsx +21 -0
  20. package/dist/components/catalyst-ui/hooks/use-timer.tsx +209 -0
  21. package/dist/components/catalyst-ui/hooks/use-toggle.tsx +12 -0
  22. package/dist/components/catalyst-ui/media/image.tsx +13 -0
  23. package/dist/components/catalyst-ui/overlays/dual-sidebar.tsx +4142 -0
  24. package/dist/components/catalyst-ui/overlays/sidebar-original.tsx +726 -0
  25. package/dist/components/catalyst-ui/primitives/accordion.tsx +250 -0
  26. package/dist/components/catalyst-ui/primitives/alert-dialog.tsx +126 -0
  27. package/dist/components/catalyst-ui/primitives/aspect-ratio.tsx +9 -0
  28. package/dist/components/catalyst-ui/primitives/avatar.tsx +296 -0
  29. package/dist/components/catalyst-ui/primitives/badge.tsx +57 -0
  30. package/dist/components/catalyst-ui/primitives/breadcrumb.tsx +101 -0
  31. package/dist/components/catalyst-ui/primitives/button.tsx +265 -0
  32. package/dist/components/catalyst-ui/primitives/calendar-v4.tsx +208 -0
  33. package/dist/components/catalyst-ui/primitives/calendar.tsx +295 -0
  34. package/dist/components/catalyst-ui/primitives/card.tsx +618 -0
  35. package/dist/components/catalyst-ui/primitives/carousel.tsx +238 -0
  36. package/dist/components/catalyst-ui/primitives/chart.tsx +347 -0
  37. package/dist/components/catalyst-ui/primitives/checkbox.tsx +225 -0
  38. package/dist/components/catalyst-ui/primitives/collapsible.tsx +212 -0
  39. package/dist/components/catalyst-ui/primitives/command.tsx +393 -0
  40. package/dist/components/catalyst-ui/primitives/context-menu.tsx +236 -0
  41. package/dist/components/catalyst-ui/primitives/dialog.tsx +471 -0
  42. package/dist/components/catalyst-ui/primitives/drawer.tsx +761 -0
  43. package/dist/components/catalyst-ui/primitives/dropdown-menu.tsx +290 -0
  44. package/dist/components/catalyst-ui/primitives/empty.tsx +104 -0
  45. package/dist/components/catalyst-ui/primitives/field.tsx +244 -0
  46. package/dist/components/catalyst-ui/primitives/hover-card.tsx +124 -0
  47. package/dist/components/catalyst-ui/primitives/input-otp.tsx +76 -0
  48. package/dist/components/catalyst-ui/primitives/input.tsx +64 -0
  49. package/dist/components/catalyst-ui/primitives/item.tsx +196 -0
  50. package/dist/components/catalyst-ui/primitives/kbd.tsx +75 -0
  51. package/dist/components/catalyst-ui/primitives/label.tsx +24 -0
  52. package/dist/components/catalyst-ui/primitives/navigation-menu.tsx +150 -0
  53. package/dist/components/catalyst-ui/primitives/pagination.tsx +198 -0
  54. package/dist/components/catalyst-ui/primitives/popover.tsx +232 -0
  55. package/dist/components/catalyst-ui/primitives/progress.tsx +34 -0
  56. package/dist/components/catalyst-ui/primitives/radio-group.tsx +43 -0
  57. package/dist/components/catalyst-ui/primitives/resizable.tsx +56 -0
  58. package/dist/components/catalyst-ui/primitives/select.tsx +155 -0
  59. package/dist/components/catalyst-ui/primitives/separator.tsx +74 -0
  60. package/dist/components/catalyst-ui/primitives/sheet.tsx +126 -0
  61. package/dist/components/catalyst-ui/primitives/skeleton.tsx +15 -0
  62. package/dist/components/catalyst-ui/primitives/slider.tsx +27 -0
  63. package/dist/components/catalyst-ui/primitives/switch.tsx +187 -0
  64. package/dist/components/catalyst-ui/primitives/tabs.tsx +335 -0
  65. package/dist/components/catalyst-ui/primitives/textarea.tsx +24 -0
  66. package/dist/components/catalyst-ui/primitives/toggle-group.tsx +55 -0
  67. package/dist/components/catalyst-ui/primitives/toggle.tsx +42 -0
  68. package/dist/components/catalyst-ui/primitives/tooltip.tsx +116 -0
  69. package/dist/components/catalyst-ui/utils/basic-auth.tsx +40 -0
  70. package/dist/components/catalyst-ui/utils/context-storage.tsx +19 -0
  71. package/dist/components/catalyst-ui/utils/cors-middleware.tsx +71 -0
  72. package/dist/components/catalyst-ui/utils/deferred-content.tsx +595 -0
  73. package/dist/components/catalyst-ui/utils/honeypot-middleware.tsx +38 -0
  74. package/dist/components/catalyst-ui/utils/incId.tsx +75 -0
  75. package/dist/components/catalyst-ui/utils/jwk-auth.tsx +36 -0
  76. package/dist/components/catalyst-ui/utils/request-id.tsx +14 -0
  77. package/dist/components/catalyst-ui/utils/secure-headers.tsx +37 -0
  78. package/dist/components/catalyst-ui/utils/server-timing.tsx +23 -0
  79. package/dist/components/catalyst-ui/utils/utils.ts +43 -0
  80. package/dist/components/catalyst-ui/utils/with-cookie.tsx +43 -0
  81. package/dist/components/catalyst-ui/x/accordian-x.tsx +428 -0
  82. package/dist/components/catalyst-ui/x/alert-x.tsx +413 -0
  83. package/dist/components/catalyst-ui/x/animated-text-x.tsx +2242 -0
  84. package/dist/components/catalyst-ui/x/avatar-x.tsx +515 -0
  85. package/dist/components/catalyst-ui/x/badge-x.tsx +670 -0
  86. package/dist/components/catalyst-ui/x/button-X.tsx +2857 -0
  87. package/dist/components/catalyst-ui/x/button-group-x.tsx +847 -0
  88. package/dist/components/catalyst-ui/x/calendar-x.tsx +1910 -0
  89. package/dist/components/catalyst-ui/x/card-x.tsx +2597 -0
  90. package/dist/components/catalyst-ui/x/checkbox-x.tsx +656 -0
  91. package/dist/components/catalyst-ui/x/collapsible-x.tsx +1360 -0
  92. package/dist/components/catalyst-ui/x/combobox-x.tsx +911 -0
  93. package/dist/components/catalyst-ui/x/data-table-x.tsx +1753 -0
  94. package/dist/components/catalyst-ui/x/date-picker-x.tsx +648 -0
  95. package/dist/components/catalyst-ui/x/dialog-x.tsx +659 -0
  96. package/dist/components/catalyst-ui/x/dropdown-menu-x.tsx +612 -0
  97. package/dist/components/catalyst-ui/x/hover-card-x.tsx +375 -0
  98. package/dist/components/catalyst-ui/x/icon-x.tsx +840 -0
  99. package/dist/components/catalyst-ui/x/input-mask-x.tsx +981 -0
  100. package/dist/components/catalyst-ui/x/input-otp-x.tsx +659 -0
  101. package/dist/components/catalyst-ui/x/loader-x.tsx +1757 -0
  102. package/dist/components/catalyst-ui/x/pagination-x.tsx +622 -0
  103. package/dist/components/catalyst-ui/x/popover-x.tsx +744 -0
  104. package/dist/components/catalyst-ui/x/radio-group-x.tsx +499 -0
  105. package/dist/components/catalyst-ui/x/select-x.tsx +1127 -0
  106. package/dist/components/catalyst-ui/x/sheet-x.tsx +668 -0
  107. package/dist/components/catalyst-ui/x/switch-x.tsx +681 -0
  108. package/dist/components/catalyst-ui/x/table-x.tsx +574 -0
  109. package/dist/components/catalyst-ui/x/tabs-x.tsx +839 -0
  110. package/dist/components/catalyst-ui/x/textarea-x.tsx +1263 -0
  111. package/dist/components/catalyst-ui/x/tooltip-x.tsx +396 -0
  112. package/dist/components/catalyst-ui/x/tracker-x.tsx +560 -0
  113. package/dist/data/bg-data.tsx +901 -0
  114. package/dist/data/buttons-data.tsx +2327 -0
  115. package/dist/data/charts-data.tsx +102 -0
  116. package/dist/data/chat-data.tsx +83 -0
  117. package/dist/data/code-data.tsx +1040 -0
  118. package/dist/data/comboboxes-data.tsx +1843 -0
  119. package/dist/data/command-data.tsx +1381 -0
  120. package/dist/data/core-data.tsx +15953 -0
  121. package/dist/data/crm-data.tsx +47 -0
  122. package/dist/data/data.tsx +159 -0
  123. package/dist/data/date-and-time-data.tsx +554 -0
  124. package/dist/data/dependencies.tsx +7 -0
  125. package/dist/data/ecommerce-data.tsx +1387 -0
  126. package/dist/data/forms-data.tsx +7890 -0
  127. package/dist/data/hooks-data.tsx +5487 -0
  128. package/dist/data/index.ts +34 -0
  129. package/dist/data/inputs-data.tsx +557 -0
  130. package/dist/data/interactive-data.tsx +5394 -0
  131. package/dist/data/lofi-data.tsx +18295 -0
  132. package/dist/data/marketing-data.tsx +2546 -0
  133. package/dist/data/media-data.tsx +1510 -0
  134. package/dist/data/motion-data.tsx +5801 -0
  135. package/dist/data/overlay-data.tsx +4136 -0
  136. package/dist/data/pdf-data.tsx +124 -0
  137. package/dist/data/pos-data.tsx +213 -0
  138. package/dist/data/postcss.config.js +6 -0
  139. package/dist/data/primitive-data.tsx +5170 -0
  140. package/dist/data/prompt-data.tsx +1226 -0
  141. package/dist/data/requiredLibs.ts +4 -0
  142. package/dist/data/sandbox-data.tsx +1 -0
  143. package/dist/data/sidebars-data.tsx +5421 -0
  144. package/dist/data/stacks-data.tsx +32 -0
  145. package/dist/data/table-data.tsx +706 -0
  146. package/dist/data/tailwind.config.js +270 -0
  147. package/dist/data/tailwind.config.ngin.js +3830 -0
  148. package/dist/data/tailwind.css +431 -0
  149. package/dist/data/tools-data.tsx +6910 -0
  150. package/dist/data/typography-data.tsx +2050 -0
  151. package/dist/data/utils-data.tsx +6500 -0
  152. package/dist/data/x-data.tsx +1171 -0
  153. package/dist/data.tsx +159 -0
  154. package/package.json +1 -1
  155. package/dist/index.d.ts +0 -3
  156. package/dist/index.d.ts.map +0 -1
  157. package/dist/index.js.map +0 -362
@@ -0,0 +1,1857 @@
1
+ import * as React from "react"
2
+ import { createContext, useContext, useState, useCallback, useEffect, useRef } from "react"
3
+ import { X, CheckCircle, AlertTriangle, XCircle, Info, Loader2 } from "lucide-react"
4
+ import { Button, } from "~/components/catalyst-ui"
5
+ import { cn } from "~/components/catalyst-ui"
6
+ import { motion, AnimatePresence } from 'framer-motion'
7
+
8
+ /**
9
+ * ★ ━━━━ ☆ ━━━━ Toast System ━━━━ ☆ ━━━━ ★
10
+ * import { useToasted } from '~/components/catalyst-ui'
11
+ *
12
+ * const { toast } = useToasted()
13
+ * toast.default('default', 'Accent default toast', { variant: 'accent' })
14
+ * toast.success('success', 'Accent success toast', { variant: 'accent' })
15
+ * toast.warning('warning!', 'Simple warning toast', { variant: 'accent' })
16
+ * toast.error('error!', 'Simple error toast', { variant: 'accent' })
17
+ * toast.info('info!', 'Simple info toast', { variant: 'accent' })
18
+ * toast.loading('loading!', 'Simple loading toast', { variant: 'accent' })
19
+ *
20
+ *
21
+ *
22
+ * type ToastType = 'default' | 'success' | 'warning' | 'error' | 'info' | 'loading'
23
+ * type ToastVariant = 'simple' | 'bordered' | 'filled' | 'accent' | 'banner' | 'card' | 'minimal' | 'soft'
24
+ * type ToastPosition = 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left'
25
+ *
26
+ * ★ ━━━━ ☆ ━━━━ USAGE ━━━━ ☆ ━━━━ ★
27
+ *
28
+ * ★ ━━━━━━━━━ Basic Setup ━━━━━━━━━ ★
29
+ * \`\`\`jsx
30
+ * // Wrap your app with Toasty provider
31
+ * import { Toasty } from "./ToastSystem";
32
+ *
33
+ * function App() {
34
+ * return (
35
+ * <Toasty position="top-right" limit={5}>
36
+ * <YourApp />
37
+ * </Toasty>
38
+ * );
39
+ * }
40
+ *
41
+ * // In any child component, use the hook
42
+ * import { useToasted } from "./ToastSystem";
43
+ *
44
+ * function MyComponent() {
45
+ * const { toast } = useToasted();
46
+ *
47
+ * return (
48
+ * <button onClick={() => toast.success("Operation successful!")}>
49
+ * Show Toast
50
+ * </button>
51
+ * );
52
+ * }
53
+ * \`\`\`
54
+ *
55
+ * ★ ━━━━━━━━━ Basic Toast Methods ━━━━━━━━━ ★
56
+ * \`\`\`jsx
57
+ * const { toast } = useToasted();
58
+ *
59
+ * // Success toast
60
+ * toast.success("Operation completed successfully!");
61
+ *
62
+ * // Error toast
63
+ * toast.error("Something went wrong!");
64
+ *
65
+ * // Warning toast
66
+ * toast.warning("Please check your input.");
67
+ *
68
+ * // Info toast
69
+ * toast.info("New update available.");
70
+ *
71
+ * // Loading toast
72
+ * const loadingId = toast.loading("Processing...");
73
+ * // Later: manually dismiss or update
74
+ *
75
+ * // Default toast
76
+ * toast.default("Default notification.");
77
+ * \`\`\`
78
+ *
79
+ * ★ ━━━━━━━━━ Toast with Description ━━━━━━━━━ ★
80
+ * \`\`\`jsx
81
+ * toast.success(
82
+ * "File uploaded",
83
+ * "Your file has been uploaded successfully and is now available for download.",
84
+ * { variant: "accent" }
85
+ * );
86
+ * \`\`\`
87
+ *
88
+ * ★ ━━━━━━━━━ Toast with Action Button ━━━━━━━━━ ★
89
+ * \`\`\`jsx
90
+ * toast.info(
91
+ * "New message received",
92
+ * "You have a new message from John",
93
+ * {
94
+ * variant: "card",
95
+ * action: {
96
+ * label: "View",
97
+ * onClick: () => navigate("/messages")
98
+ * }
99
+ * }
100
+ * );
101
+ * \`\`\`
102
+ *
103
+ * ★ ━━━━━━━━━ Toast with Custom Icon ━━━━━━━━━ ★
104
+ * \`\`\`jsx
105
+ * import { StarIcon } from "lucide-react";
106
+ *
107
+ * toast.default(
108
+ * "Added to favorites",
109
+ * "This item has been added to your favorites list.",
110
+ * {
111
+ * icon: <StarIcon className="w-5 h-5" />,
112
+ * variant: "soft"
113
+ * }
114
+ * );
115
+ * \`\`\`
116
+ *
117
+ * ★ ━━━━━━━━━ Promise Handling ━━━━━━━━━ ★
118
+ * \`\`\`jsx
119
+ * // Auto-handles loading, success, and error states
120
+ * const handleUpload = async () => {
121
+ * try {
122
+ * await toast.promise(
123
+ * uploadFile(file), // Your async function
124
+ * {
125
+ * loading: "Uploading file...",
126
+ * success: (data) => `File uploaded: ${data.filename}`,
127
+ * error: (err) => `Upload failed: ${err.message}`,
128
+ * description: "Please wait while we process your file.",
129
+ * variant: "bordered"
130
+ * }
131
+ * );
132
+ * } catch (error) {
133
+ * // Error already handled by toast
134
+ * }
135
+ * };
136
+ * \`\`\`
137
+ *
138
+ * ★ ━━━━━━━━━ Manual Toast Management ━━━━━━━━━ ★
139
+ * \`\`\`jsx
140
+ * const { toast, removeToast, updateToast } = useToasted();
141
+ *
142
+ * // Show loading toast and get its ID
143
+ * const toastId = toast.loading("Processing...");
144
+ *
145
+ * // Update it later
146
+ * updateToast(toastId, {
147
+ * type: "success",
148
+ * title: "Process complete!",
149
+ * duration: 3000,
150
+ * dismissible: true
151
+ * });
152
+ *
153
+ * // Or manually dismiss
154
+ * removeToast(toastId);
155
+ * \`\`\`
156
+ *
157
+ * ★ ━━━━━━━━━ Toast Variants ━━━━━━━━━ ★
158
+ * \`\`\`jsx
159
+ * // Simple (default)
160
+ * toast.success("Success!", { variant: "simple" });
161
+ *
162
+ * // Bordered
163
+ * toast.warning("Warning!", { variant: "bordered" });
164
+ *
165
+ * // Filled
166
+ * toast.error("Error!", { variant: "filled" });
167
+ *
168
+ * // Accent (left border)
169
+ * toast.info("Information", { variant: "accent" });
170
+ *
171
+ * // Banner
172
+ * toast.default("Announcement", { variant: "banner" });
173
+ *
174
+ * // Card
175
+ * toast.success("Profile updated", { variant: "card" });
176
+ *
177
+ * // Minimal
178
+ * toast.info("New notification", { variant: "minimal" });
179
+ *
180
+ * // Soft
181
+ * toast.success("Changes saved", { variant: "soft" });
182
+ * \`\`\`
183
+ *
184
+ * ★ ━━━━━━━━━ Toast Positions ━━━━━━━━━ ★
185
+ * \`\`\`jsx
186
+ * // Global position (in Toasty provider)
187
+ * <Toasty position="top-right">
188
+ *
189
+ * // Per-toast position (overrides global)
190
+ * toast.success("Custom position!", {
191
+ * position: "bottom-left",
192
+ * variant: "accent"
193
+ * });
194
+ * \`\`\`
195
+ *
196
+ * ★ ━━━━━━━━━ Interactive Features ━━━━━━━━━ ★
197
+ * \`\`\`jsx
198
+ * // Expandable long content
199
+ * toast.info(
200
+ * "Long Notification",
201
+ * "This is a very long message that will be truncated initially. Click to expand and see the full content. You can also drag to dismiss and hover to pause auto-dismissal.",
202
+ * {
203
+ * collapseThreshold: 100, // Characters before truncation
204
+ * variant: "card",
205
+ * duration: 8000
206
+ * }
207
+ * );
208
+ *
209
+ * // Non-dismissible toast (for loading states)
210
+ * toast.loading("Processing...", {
211
+ * dismissible: false,
212
+ * duration: 0 // No auto-dismiss
213
+ * });
214
+ * \`\`\`
215
+ *
216
+ * ★ ━━━━━━━━━ Stacking and Queuing ━━━━━━━━━ ★
217
+ * \`\`\`jsx
218
+ * // Multiple toasts of same type stack
219
+ * toast.success("Task 1 completed");
220
+ * toast.success("Task 2 completed"); // Shows as "2x"
221
+ *
222
+ * // Queue system when limit reached
223
+ * // Max 3 visible toasts
224
+ * <Toasty limit={3}>
225
+ *
226
+ * // Grouped by type and title
227
+ * toast.info("New message from Alice");
228
+ * toast.info("New message from Bob"); // Groups with previous
229
+ * \`\`\`
230
+ *
231
+ * ★ ━━━━━━━━━ Props & Options ━━━━━━━━━ ★
232
+ *
233
+ * Toast Options:
234
+ * - `type`: ToastType - Toast type (auto-set by method)
235
+ * - `variant`: ToastVariant - Visual style variant
236
+ * - `title`: string | React.ReactNode - Main message
237
+ * - `description`: string | React.ReactNode - Optional description
238
+ * - `duration`: number - Auto-dismiss timeout (ms, 0 = no auto-dismiss)
239
+ * - `dismissible`: boolean - Whether user can dismiss (default: true)
240
+ * - `action`: { label: string, onClick: () => void } - Action button
241
+ * - `icon`: React.ReactNode - Custom icon override
242
+ * - `position`: ToastPosition - Display position
243
+ * - `collapseThreshold`: number - Character count before truncation
244
+ *
245
+ * Promise Options (for toast.promise):
246
+ * - `loading`: string | React.ReactNode - Loading message
247
+ * - `success`: string | React.ReactNode | ((data: any) => string | React.ReactNode)
248
+ * - `error`: string | React.ReactNode | ((error: any) => string | React.ReactNode)
249
+ * - `description`: string | React.ReactNode - Optional description
250
+ * - `duration`: number - Auto-dismiss timeout
251
+ * - `variant`: ToastVariant - Visual style
252
+ *
253
+ * Toasty Provider Props:
254
+ * - `position`: ToastPosition - Default position (default: "top-center")
255
+ * - `limit`: number - Maximum visible toasts (default: 3)
256
+ * - `children`: React.ReactNode - Your app content
257
+ *
258
+ * ★ ━━━━━━━━━ Keyboard Shortcuts ━━━━━━━━━ ★
259
+ * - `Escape`: Dismiss the most recent toast
260
+ *
261
+ * ★ ━━━━━━━━━ Features ━━━━━━━━━ ★
262
+ * - 🔄 **Auto-stacking**: Groups similar toasts
263
+ * - ⏳ **Queue system**: Manages overflow when limit reached
264
+ * - 🎨 **8 visual variants**: From simple to card styles
265
+ * - 📍 **6 positions**: All screen corners + centers
266
+ * - ⌨️ **Keyboard support**: Escape to dismiss
267
+ * - 🖱️ **Interactive**: Hover to pause, click to expand, drag to dismiss
268
+ * - 🔄 **Promise support**: Auto-handles async states
269
+ * - 📱 **Touch friendly**: Swipe to dismiss
270
+ * - ⏸️ **Auto-pause**: Hover pauses auto-dismissal
271
+ *
272
+ * ★ ━━━━━━━━━ Advanced Usage Example ━━━━━━━━━ ★
273
+ * \`\`\`jsx
274
+ * const { toast } = useToasted();
275
+ *
276
+ * const handleComplexOperation = async () => {
277
+ * const toastId = toast.loading("Starting process...", {
278
+ * variant: "accent",
279
+ * dismissible: false,
280
+ * position: "bottom-right"
281
+ * });
282
+ *
283
+ * // Update progress
284
+ * setTimeout(() => {
285
+ * toast.update(toastId, {
286
+ * title: "Processing step 1/3",
287
+ * description: "Fetching data..."
288
+ * });
289
+ * }, 1000);
290
+ *
291
+ * try {
292
+ * const result = await complexOperation();
293
+ *
294
+ * toast.update(toastId, {
295
+ * type: "success",
296
+ * title: "Operation complete!",
297
+ * description: \`Result: \${result}\`,
298
+ * duration: 5000,
299
+ * dismissible: true,
300
+ * variant: "card",
301
+ * action: {
302
+ * label: "View Details",
303
+ * onClick: () => showDetails(result)
304
+ * }
305
+ * });
306
+ * } catch (error) {
307
+ * toast.update(toastId, {
308
+ * type: "error",
309
+ * title: "Operation failed",
310
+ * description: error.message,
311
+ * duration: 8000,
312
+ * dismissible: true,
313
+ * variant: "filled"
314
+ * });
315
+ * }
316
+ * };
317
+ * \`\`\`
318
+ *
319
+ * ★ ━━━━━━━━━ Visual Variants Preview ━━━━━━━━━ ★
320
+ *
321
+ * Simple: Clean border, subtle colors
322
+ * Bordered: Thick colored borders, elevated
323
+ * Filled: Solid colored backgrounds
324
+ * Accent: Left border accent, subtle background
325
+ * Banner: Rounded, prominent background
326
+ * Card: Card-like with border and shadow
327
+ * Minimal: Transparent, text-only
328
+ * Soft: Light background with colored border
329
+ *
330
+ * Each variant has unique styling for all 6 toast types.
331
+ *
332
+ */
333
+
334
+ interface ToastAction {
335
+ label: string
336
+ onClick: () => void
337
+ }
338
+ interface Toast {
339
+ id: string
340
+ type: 'default' | 'success' | 'warning' | 'error' | 'info' | 'loading'
341
+ variant?: 'simple' | 'bordered' | 'filled' | 'accent' | 'banner' | 'card' | 'minimal' | 'soft'
342
+ title: string | React.ReactNode
343
+ description?: string | React.ReactNode
344
+ duration?: number
345
+ dismissible?: boolean
346
+ action?: ToastAction
347
+ icon?: React.ReactNode
348
+ position?: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left'
349
+ collapseThreshold?: number
350
+ }
351
+ interface PromiseOptions {
352
+ loading: string | React.ReactNode
353
+ success: string | React.ReactNode | ((data: any) => string | React.ReactNode)
354
+ error: string | React.ReactNode | ((error: any) => string | React.ReactNode)
355
+ description?: string | React.ReactNode
356
+ duration?: number
357
+ variant?: Toast['variant']
358
+ }
359
+
360
+ interface ToastContextType {
361
+ toasts: Toast[]
362
+ addToast: (toast: Omit<Toast, 'id'>) => string
363
+ removeToast: (id: string) => void
364
+ updateToast: (id: string, toast: Partial<Toast>) => void
365
+ toast: {
366
+ success: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
367
+ warning: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
368
+ error: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
369
+ info: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
370
+ loading: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
371
+ default: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) => string
372
+ promise: <T>(promise: Promise<T>, options: PromiseOptions) => Promise<T>
373
+ }
374
+ }
375
+ const ToastContext = createContext<ToastContextType | null>(null)
376
+ const toastIcons = {
377
+ default: null,
378
+ success: CheckCircle,
379
+ warning: AlertTriangle,
380
+ error: XCircle,
381
+ info: Info,
382
+ loading: Loader2,
383
+ }
384
+
385
+ const motionVariants = {
386
+ 'top-right': {
387
+ initial: { x: 400, opacity: 0, scale: 0.8 },
388
+ animate: { x: 0, opacity: 1, scale: 1 },
389
+ exit: { x: 400, opacity: 0, scale: 0.8 }
390
+ },
391
+ 'top-center': {
392
+ initial: { y: -100, opacity: 0, scale: 0.8 },
393
+ animate: { y: 0, opacity: 1, scale: 1 },
394
+ exit: { y: -100, opacity: 0, scale: 0.8 }
395
+ },
396
+ 'top-left': {
397
+ initial: { x: -400, opacity: 0, scale: 0.8 },
398
+ animate: { x: 0, opacity: 1, scale: 1 },
399
+ exit: { x: -400, opacity: 0, scale: 0.8 }
400
+ },
401
+ 'bottom-right': {
402
+ initial: { x: 400, opacity: 0, scale: 0.8 },
403
+ animate: { x: 0, opacity: 1, scale: 1 },
404
+ exit: { x: 400, opacity: 0, scale: 0.8 }
405
+ },
406
+ 'bottom-center': {
407
+ initial: { y: 100, opacity: 0, scale: 0.8 },
408
+ animate: { y: 0, opacity: 1, scale: 1 },
409
+ exit: { y: 100, opacity: 0, scale: 0.8 }
410
+ },
411
+ 'bottom-left': {
412
+ initial: { x: -400, opacity: 0, scale: 0.8 },
413
+ animate: { x: 0, opacity: 1, scale: 1 },
414
+ exit: { x: -400, opacity: 0, scale: 0.8 }
415
+ }
416
+ }
417
+ export const styleConfigs = {
418
+ simple: {
419
+ default: {
420
+ container: 'bg-background border border-border text-foreground p-3',
421
+ icon: 'text-muted-foreground',
422
+ title: 'text-foreground',
423
+ description: 'text-muted-foreground',
424
+ button: 'bg-primary text-primary-foreground hover:bg-primary/90',
425
+ dismissButton: 'text-muted-foreground hover:text-foreground',
426
+ },
427
+ success: {
428
+ container: "bg-green-950/50 border border-green-800/50 text-green-100 p-3",
429
+ icon: "text-green-400",
430
+ title: "text-green-100",
431
+ description: "text-green-200",
432
+ button: "bg-green-600 text-white hover:bg-green-700",
433
+ dismissButton: "text-green-400 hover:text-green-200"
434
+ },
435
+ warning: {
436
+ container: "bg-orange-950/50 border border-orange-800/50 text-orange-100 p-3",
437
+ icon: "text-orange-400",
438
+ title: "text-orange-100",
439
+ description: "text-orange-200",
440
+ button: "bg-orange-600 text-white hover:bg-orange-700",
441
+ dismissButton: "text-orange-400 hover:text-orange-200"
442
+ },
443
+ error: {
444
+ container: "bg-red-950/50 border border-red-800/50 text-red-100 p-3",
445
+ icon: "text-red-400",
446
+ title: "text-red-100",
447
+ description: "text-red-200",
448
+ button: "bg-red-600 text-white hover:bg-red-700",
449
+ dismissButton: "text-red-400 hover:text-red-200"
450
+ },
451
+ info: {
452
+ container: "bg-blue-950/50 border border-blue-800/50 text-blue-100 p-3",
453
+ icon: "text-blue-400",
454
+ title: "text-blue-100",
455
+ description: "text-blue-200",
456
+ button: "bg-blue-600 text-white hover:bg-blue-700",
457
+ dismissButton: "text-blue-400 hover:text-blue-200"
458
+ },
459
+ loading: {
460
+ container: "bg-background border border-border text-foreground p-3",
461
+ icon: "text-muted-foreground animate-spin",
462
+ title: "text-foreground",
463
+ description: "text-muted-foreground",
464
+ button: "bg-primary text-primary-foreground hover:bg-primary/90",
465
+ dismissButton: "text-muted-foreground hover:text-foreground"
466
+ }
467
+ },
468
+ bordered: {
469
+ default: {
470
+ container: 'bg-background border-2 border-border text-foreground shadow-sm p-3',
471
+ icon: 'text-primary',
472
+ title: 'text-foreground font-medium',
473
+ description: 'text-muted-foreground',
474
+ button: 'bg-primary text-primary-foreground hover:bg-primary/90 border-0',
475
+ dismissButton: 'text-muted-foreground hover:text-foreground border border-border hover:bg-muted',
476
+ },
477
+ success: {
478
+ container: "bg-green-950/20 border-2 border-green-500 text-green-100 shadow-md p-3",
479
+ icon: "text-green-400",
480
+ title: "text-green-100 font-semibold",
481
+ description: "text-green-200",
482
+ button: "bg-green-600 text-white hover:bg-green-700 border-0",
483
+ dismissButton: "text-green-400 hover:text-green-200 border border-green-500 hover:bg-green-950/40"
484
+ },
485
+ warning: {
486
+ container: "bg-amber-950/20 border-2 border-amber-500 text-amber-100 shadow-md p-3",
487
+ icon: "text-amber-400",
488
+ title: "text-amber-100 font-semibold",
489
+ description: "text-amber-200",
490
+ button: "bg-amber-600 text-white hover:bg-amber-700 border-0",
491
+ dismissButton: "text-amber-400 hover:text-amber-200 border border-amber-500 hover:bg-amber-950/40"
492
+ },
493
+ error: {
494
+ container: "bg-rose-950/20 border-2 border-rose-500 text-rose-100 shadow-md p-3",
495
+ icon: "text-rose-400",
496
+ title: "text-rose-100 font-semibold",
497
+ description: "text-rose-200",
498
+ button: "bg-rose-600 text-white hover:bg-rose-700 border-0",
499
+ dismissButton: "text-rose-400 hover:text-rose-200 border border-rose-500 hover:bg-rose-950/40"
500
+ },
501
+ info: {
502
+ container: "bg-cyan-950/20 border-2 border-cyan-500 text-cyan-100 shadow-md p-3",
503
+ icon: "text-cyan-400",
504
+ title: "text-cyan-100 font-semibold",
505
+ description: "text-cyan-200",
506
+ button: "bg-cyan-600 text-white hover:bg-cyan-700 border-0",
507
+ dismissButton: "text-cyan-400 hover:text-cyan-200 border border-cyan-500 hover:bg-cyan-950/40"
508
+ },
509
+ loading: {
510
+ container: "bg-background border-2 border-border text-foreground shadow-sm p-3",
511
+ icon: "text-primary animate-spin",
512
+ title: "text-foreground font-medium",
513
+ description: "text-muted-foreground",
514
+ button: "bg-primary text-primary-foreground hover:bg-primary/90 border-0",
515
+ dismissButton: "text-muted-foreground hover:text-foreground border border-border hover:bg-muted"
516
+ }
517
+ },
518
+ filled: {
519
+ default: {
520
+ container: 'bg-muted text-muted-foreground border-0 p-3',
521
+ icon: 'text-foreground',
522
+ title: 'text-foreground font-medium',
523
+ description: 'text-muted-foreground',
524
+ button: 'bg-background text-foreground hover:bg-background/80',
525
+ dismissButton: 'text-muted-foreground hover:text-foreground',
526
+ },
527
+ success: {
528
+ container: "bg-green-600 text-green-50 border-0 p-3",
529
+ icon: "text-green-100",
530
+ title: "text-green-50 font-medium",
531
+ description: "text-green-100",
532
+ button: "bg-green-800 text-green-50 hover:bg-green-900",
533
+ dismissButton: "text-green-200 hover:text-green-50"
534
+ },
535
+ warning: {
536
+ container: "bg-amber-600 text-amber-50 border-0 p-3",
537
+ icon: "text-amber-100",
538
+ title: "text-amber-50 font-medium",
539
+ description: "text-amber-100",
540
+ button: "bg-amber-800 text-amber-50 hover:bg-amber-900",
541
+ dismissButton: "text-amber-200 hover:text-amber-50"
542
+ },
543
+ error: {
544
+ container: "bg-red-600 text-red-50 border-0 p-3",
545
+ icon: "text-red-100",
546
+ title: "text-red-50 font-medium",
547
+ description: "text-red-100",
548
+ button: "bg-red-800 text-red-50 hover:bg-red-900",
549
+ dismissButton: "text-red-200 hover:text-red-50"
550
+ },
551
+ info: {
552
+ container: "bg-blue-600 text-blue-50 border-0 p-3",
553
+ icon: "text-blue-100",
554
+ title: "text-blue-50 font-medium",
555
+ description: "text-blue-100",
556
+ button: "bg-blue-800 text-blue-50 hover:bg-blue-900",
557
+ dismissButton: "text-blue-200 hover:text-blue-50"
558
+ },
559
+ loading: {
560
+ container: "bg-muted text-muted-foreground border-0 p-3",
561
+ icon: "text-foreground animate-spin",
562
+ title: "text-foreground font-medium",
563
+ description: "text-muted-foreground",
564
+ button: "bg-background text-foreground hover:bg-background/80",
565
+ dismissButton: "text-muted-foreground hover:text-foreground"
566
+ }
567
+ },
568
+ accent: {
569
+ default: {
570
+ container: 'bg-background border-l-4 border-primary text-foreground shadow-sm pl-4 p-3',
571
+ icon: 'text-primary',
572
+ title: 'text-foreground font-medium',
573
+ description: 'text-muted-foreground',
574
+ button: 'bg-primary text-primary-foreground hover:bg-primary/90',
575
+ dismissButton: 'text-muted-foreground hover:text-foreground',
576
+ },
577
+ success: {
578
+ container: "bg-green-950/30 border-l-4 border-green-400 text-green-100 shadow-sm pl-4 p-3",
579
+ icon: "text-green-400",
580
+ title: "text-green-100 font-semibold",
581
+ description: "text-green-200",
582
+ button: "bg-green-600 text-white hover:bg-green-700",
583
+ dismissButton: "text-green-400 hover:text-green-200"
584
+ },
585
+ warning: {
586
+ container: "bg-yellow-950/30 border-l-4 border-yellow-400 text-yellow-100 shadow-sm pl-4 p-3",
587
+ icon: "text-yellow-400",
588
+ title: "text-yellow-100 font-semibold",
589
+ description: "text-yellow-200",
590
+ button: "bg-yellow-600 text-white hover:bg-yellow-700",
591
+ dismissButton: "text-yellow-400 hover:text-yellow-200"
592
+ },
593
+ error: {
594
+ container: "bg-red-950/30 border-l-4 border-red-400 text-red-100 shadow-sm pl-4 p-3",
595
+ icon: "text-red-400",
596
+ title: "text-red-100 font-semibold",
597
+ description: "text-red-200",
598
+ button: "bg-red-600 text-white hover:bg-red-700",
599
+ dismissButton: "text-red-400 hover:text-red-200"
600
+ },
601
+ info: {
602
+ container: "bg-indigo-950/30 border-l-4 border-indigo-400 text-indigo-100 shadow-sm pl-4 p-3",
603
+ icon: "text-indigo-400",
604
+ title: "text-indigo-100 font-semibold",
605
+ description: "text-indigo-200",
606
+ button: "bg-indigo-600 text-white hover:bg-indigo-700",
607
+ dismissButton: "text-indigo-400 hover:text-indigo-200"
608
+ },
609
+ loading: {
610
+ container: "bg-background border-l-4 border-primary text-foreground shadow-sm pl-4 p-3",
611
+ icon: "text-primary animate-spin",
612
+ title: "text-foreground font-medium",
613
+ description: "text-muted-foreground",
614
+ button: "bg-primary text-primary-foreground hover:bg-primary/90",
615
+ dismissButton: "text-muted-foreground hover:text-foreground"
616
+ }
617
+ },
618
+ banner: {
619
+ default: {
620
+ container: 'bg-muted text-foreground border-0 rounded-lg px-4 py-3 p-3',
621
+ icon: 'text-primary',
622
+ title: 'text-foreground font-semibold',
623
+ description: 'text-muted-foreground',
624
+ button: 'bg-background text-foreground hover:bg-background/80 rounded-md',
625
+ dismissButton: 'text-muted-foreground hover:text-foreground',
626
+ },
627
+ success: {
628
+ container: "bg-green-700 text-green-50 border-0 rounded-lg px-4 py-3 p-3",
629
+ icon: "text-green-100",
630
+ title: "text-green-50 font-bold",
631
+ description: "text-green-100",
632
+ button: "bg-green-900 text-green-50 hover:bg-green-950 rounded-md",
633
+ dismissButton: "text-green-200 hover:text-green-50"
634
+ },
635
+ warning: {
636
+ container: "bg-orange-700 text-orange-50 border-0 rounded-lg px-4 py-3 p-3",
637
+ icon: "text-orange-100",
638
+ title: "text-orange-50 font-bold",
639
+ description: "text-orange-100",
640
+ button: "bg-orange-900 text-orange-50 hover:bg-orange-950 rounded-md",
641
+ dismissButton: "text-orange-200 hover:text-orange-50"
642
+ },
643
+ error: {
644
+ container: "bg-red-700 text-red-50 border-0 rounded-lg px-4 py-3 p-3",
645
+ icon: "text-red-100",
646
+ title: "text-red-50 font-bold",
647
+ description: "text-red-100",
648
+ button: "bg-red-900 text-red-50 hover:bg-red-950 rounded-md ",
649
+ dismissButton: "text-red-200 hover:text-red-50"
650
+ },
651
+ info: {
652
+ container: "bg-blue-700 text-blue-50 border-0 rounded-lg px-4 py-3 p-3",
653
+ icon: "text-blue-100",
654
+ title: "text-blue-50 font-bold",
655
+ description: "text-blue-100",
656
+ button: "bg-blue-900 text-blue-50 hover:bg-blue-950 rounded-md",
657
+ dismissButton: "text-blue-200 hover:text-blue-50"
658
+ },
659
+ loading: {
660
+ container: "bg-muted text-foreground border-0 rounded-lg px-4 py-3 p-3",
661
+ icon: "text-primary animate-spin",
662
+ title: "text-foreground font-semibold",
663
+ description: "text-muted-foreground",
664
+ button: "bg-background text-foreground hover:bg-background/80 rounded-md",
665
+ dismissButton: "text-muted-foreground hover:text-foreground"
666
+ }
667
+ },
668
+ card: {
669
+ default: {
670
+ container: 'bg-card border border-border text-card-foreground shadow-lg rounded-lg p-3',
671
+ icon: 'text-primary',
672
+ title: 'text-card-foreground font-semibold',
673
+ description: 'text-muted-foreground',
674
+ button: 'bg-primary text-primary-foreground hover:bg-primary/90',
675
+ dismissButton: 'text-muted-foreground hover:text-foreground',
676
+ },
677
+ success: {
678
+ container: "bg-green-900/50 border border-green-700 text-green-100 shadow-xl rounded-lg p-4 backdrop-blur-sm",
679
+ icon: "text-green-400",
680
+ title: "text-green-100 font-bold",
681
+ description: "text-green-200",
682
+ button: "bg-green-600 text-white hover:bg-green-700 shadow-sm",
683
+ dismissButton: "text-green-400 hover:text-green-200 hover:bg-green-800/50 rounded-full"
684
+ },
685
+ warning: {
686
+ container: "bg-amber-900/50 border border-amber-700 text-amber-100 shadow-xl rounded-lg p-4 backdrop-blur-sm",
687
+ icon: "text-amber-400",
688
+ title: "text-amber-100 font-bold",
689
+ description: "text-amber-200",
690
+ button: "bg-amber-600 text-white hover:bg-amber-700 shadow-sm",
691
+ dismissButton: "text-amber-400 hover:text-amber-200 hover:bg-amber-800/50 rounded-full"
692
+ },
693
+ error: {
694
+ container: "bg-red-900/50 border border-red-700 text-red-100 shadow-xl rounded-lg p-4 backdrop-blur-sm",
695
+ icon: "text-red-400",
696
+ title: "text-red-100 font-bold",
697
+ description: "text-red-200",
698
+ button: "bg-red-600 text-white hover:bg-red-700 shadow-sm",
699
+ dismissButton: "text-red-400 hover:text-red-200 hover:bg-red-800/50 rounded-full"
700
+ },
701
+ info: {
702
+ container: "bg-blue-900/50 border border-blue-700 text-blue-100 shadow-xl rounded-lg p-4 backdrop-blur-sm",
703
+ icon: "text-blue-400",
704
+ title: "text-blue-100 font-bold",
705
+ description: "text-blue-200",
706
+ button: "bg-blue-600 text-white hover:bg-blue-700 shadow-sm",
707
+ dismissButton: "text-blue-400 hover:text-blue-200 hover:bg-blue-800/50 rounded-full"
708
+ },
709
+ loading: {
710
+ container: "bg-card border border-border text-card-foreground shadow-lg rounded-lg p-3",
711
+ icon: "text-primary animate-spin",
712
+ title: "text-card-foreground font-semibold",
713
+ description: "text-muted-foreground",
714
+ button: "bg-primary text-primary-foreground hover:bg-primary/90",
715
+ dismissButton: "text-muted-foreground hover:text-foreground"
716
+ }
717
+ },
718
+ minimal: {
719
+ default: {
720
+ container: 'bg-transparent border-0 text-foreground p-3',
721
+ icon: 'text-muted-foreground',
722
+ title: 'text-foreground',
723
+ description: 'text-muted-foreground text-sm',
724
+ button: 'text-primary hover:text-primary/80 underline bg-transparent p-0',
725
+ dismissButton: 'text-muted-foreground hover:text-foreground bg-transparent',
726
+ },
727
+ success: {
728
+ container: "bg-transparent border-0 text-green-200 p-3",
729
+ icon: "text-green-400",
730
+ title: "text-green-200 font-medium",
731
+ description: "text-green-300 text-sm",
732
+ button: "text-green-400 hover:text-green-200 underline bg-transparent p-0",
733
+ dismissButton: "text-green-500 hover:text-green-300 bg-transparent"
734
+ },
735
+ warning: {
736
+ container: "bg-transparent border-0 text-amber-200 p-3",
737
+ icon: "text-amber-400",
738
+ title: "text-amber-200 font-medium",
739
+ description: "text-amber-300 text-sm",
740
+ button: "text-amber-400 hover:text-amber-200 underline bg-transparent p-0",
741
+ dismissButton: "text-amber-500 hover:text-amber-300 bg-transparent"
742
+ },
743
+ error: {
744
+ container: "bg-transparent border-0 text-red-200 p-3",
745
+ icon: "text-red-400",
746
+ title: "text-red-200 font-medium",
747
+ description: "text-red-300 text-sm",
748
+ button: "text-red-400 hover:text-red-200 underline bg-transparent p-0",
749
+ dismissButton: "text-red-500 hover:text-red-300 bg-transparent"
750
+ },
751
+ info: {
752
+ container: "bg-transparent border-0 text-blue-200 p-3",
753
+ icon: "text-blue-400",
754
+ title: "text-blue-200 font-medium",
755
+ description: "text-blue-300 text-sm",
756
+ button: "text-blue-400 hover:text-blue-200 underline bg-transparent p-0",
757
+ dismissButton: "text-blue-500 hover:text-blue-300 bg-transparent"
758
+ },
759
+ loading: {
760
+ container: "bg-transparent border-0 text-foreground p-3",
761
+ icon: "text-muted-foreground animate-spin",
762
+ title: "text-foreground",
763
+ description: "text-muted-foreground text-sm",
764
+ button: "text-primary hover:text-primary/80 underline bg-transparent p-0",
765
+ dismissButton: "text-muted-foreground hover:text-foreground bg-transparent"
766
+ }
767
+
768
+ },
769
+ soft: {
770
+ default: {
771
+ container: 'bg-primary/10 border border-primary/20 text-foreground p-3',
772
+ icon: 'text-primary',
773
+ title: 'text-foreground font-medium',
774
+ description: 'text-muted-foreground',
775
+ button: 'bg-primary text-primary-foreground hover:bg-primary/90',
776
+ dismissButton: 'text-muted-foreground hover:text-foreground',
777
+ },
778
+ success: {
779
+ container: "bg-green-500/10 border border-green-500/20 text-green-100 p-3",
780
+ icon: "text-green-400",
781
+ title: "text-green-100 font-medium",
782
+ description: "text-green-200",
783
+ button: "bg-green-600 text-white hover:bg-green-700",
784
+ dismissButton: "text-green-400 hover:text-green-200"
785
+ },
786
+ warning: {
787
+ container: "bg-amber-500/10 border border-amber-500/20 text-amber-100 p-3",
788
+ icon: "text-amber-400",
789
+ title: "text-amber-100 font-medium",
790
+ description: "text-amber-200",
791
+ button: "bg-amber-600 text-white hover:bg-amber-700",
792
+ dismissButton: "text-amber-400 hover:text-amber-200"
793
+ },
794
+ error: {
795
+ container: "bg-red-500/10 border border-red-500/20 text-red-100 p-3",
796
+ icon: "text-red-400",
797
+ title: "text-red-100 font-medium",
798
+ description: "text-red-200",
799
+ button: "bg-red-600 text-white hover:bg-red-700",
800
+ dismissButton: "text-red-400 hover:text-red-200"
801
+ },
802
+ info: {
803
+ container: "bg-blue-500/10 border border-blue-500/20 text-blue-100 p-3",
804
+ icon: "text-blue-400",
805
+ title: "text-blue-100 font-medium",
806
+ description: "text-blue-200",
807
+ button: "bg-blue-600 text-white hover:bg-blue-700",
808
+ dismissButton: "text-blue-400 hover:text-blue-200"
809
+ },
810
+ loading: {
811
+ container: "bg-primary/10 border border-primary/20 text-foreground p-3",
812
+ icon: "text-primary animate-spin",
813
+ title: "text-foreground font-medium",
814
+ description: "text-muted-foreground",
815
+ button: "bg-primary text-primary-foreground hover:bg-primary/90",
816
+ dismissButton: "text-muted-foreground hover:text-foreground"
817
+ }
818
+ }
819
+ }
820
+ const animationVariants = {
821
+ 'top-right': {
822
+ enter: 'animate-in slide-in-from-right-full fade-in-0 duration-300',
823
+ exit: 'animate-out slide-out-to-right-full fade-out-0 duration-200'
824
+ },
825
+ 'top-center': {
826
+ enter: 'animate-in slide-in-from-top-2 fade-in-0 duration-300',
827
+ exit: 'animate-out slide-out-to-top-2 fade-out-0 duration-200'
828
+ },
829
+ 'top-left': {
830
+ enter: 'animate-in slide-in-from-left-full fade-in-0 duration-300',
831
+ exit: 'animate-out slide-out-to-left-full fade-out-0 duration-200'
832
+ },
833
+ 'bottom-right': {
834
+ enter: 'animate-in slide-in-from-right-full fade-in-0 duration-300',
835
+ exit: 'animate-out slide-out-to-right-full fade-out-0 duration-200'
836
+ },
837
+ 'bottom-center': {
838
+ enter: 'animate-in slide-in-from-bottom-2 fade-in-0 duration-300',
839
+ exit: 'animate-out slide-out-to-bottom-2 fade-out-0 duration-200'
840
+ },
841
+ 'bottom-left': {
842
+ enter: 'animate-in slide-in-from-left-full fade-in-0 duration-300',
843
+ exit: 'animate-out slide-out-to-left-full fade-out-0 duration-200'
844
+ }
845
+ }
846
+ const ToastComponent: React.FC<{
847
+ toast: Toast
848
+ onDismiss: () => void
849
+ position: string
850
+ stackCount?: number,
851
+ animated?: boolean
852
+ }> = ({ toast, onDismiss, position, stackCount, animated = false }) => {
853
+ const [isExpanded, setIsExpanded] = useState(false)
854
+ const [isPaused, setIsPaused] = useState(false)
855
+ const [isExiting, setIsExiting] = useState(false)
856
+ const [dragX, setDragX] = useState(0)
857
+ const startX = useRef(0)
858
+ const timerRef = useRef<NodeJS.Timeout>()
859
+
860
+ const variant = toast.variant || 'simple'
861
+ const styles = styleConfigs[variant]?.[toast.type] || styleConfigs.simple[toast.type]
862
+ const IconComponent = toast.icon ? null : toastIcons[toast.type]
863
+ const animations = animationVariants[position] || animationVariants['top-center']
864
+ const motionAnim = motionVariants[position] || motionVariants['top-center']
865
+ // Auto-dismiss timer with pause support
866
+ useEffect(() => {
867
+ if (toast.duration === 0 || toast.type === 'loading' || isPaused) {
868
+ return
869
+ }
870
+
871
+ timerRef.current = setTimeout(() => {
872
+ handleDismiss()
873
+ }, toast.duration || 4000)
874
+
875
+ return () => {
876
+ if (timerRef.current) clearTimeout(timerRef.current)
877
+ }
878
+ }, [toast.duration, isPaused, toast.type])
879
+
880
+ const handleDismiss = () => {
881
+ setIsExiting(true)
882
+ setTimeout(() => {
883
+ onDismiss()
884
+ }, 200)
885
+ }
886
+
887
+ // Touch/Mouse drag handlers
888
+ const handleDragStart = (clientX: number) => {
889
+ startX.current = clientX
890
+ setIsPaused(true)
891
+ }
892
+
893
+ const handleDragMove = (clientX: number) => {
894
+ const diff = clientX - startX.current
895
+ if (Math.abs(diff) > 10) {
896
+ setDragX(diff)
897
+ }
898
+ }
899
+
900
+ const handleDragEnd = () => {
901
+ if (Math.abs(dragX) > 100) {
902
+ handleDismiss()
903
+ } else {
904
+ setDragX(0)
905
+ setIsPaused(false)
906
+ }
907
+ }
908
+
909
+ const hasLongContent = () => {
910
+ const titleLength = typeof toast.title === 'string' ? toast.title.length : 50
911
+ const descLength = typeof toast.description === 'string' ? toast.description.length : 50
912
+ return titleLength + descLength > toast.collapseThreshold || 150
913
+ }
914
+
915
+ const shouldCollapse = hasLongContent() && !isExpanded
916
+ const ToastWrapper = animated ? motion.div : 'div'
917
+ const wrapperProps = animated ? {
918
+ initial: motionAnim.initial,
919
+ animate: motionAnim.animate,
920
+ exit: motionAnim.exit,
921
+ transition: { type: 'spring', damping: 25, stiffness: 300 }
922
+ } : {}
923
+
924
+ return (
925
+ <ToastWrapper
926
+ {...wrapperProps}
927
+ className={cn(
928
+ "relative flex items-start gap-3 rounded-lg shadow-lg backdrop-blur-sm cursor-pointer select-none touch-pan-y",
929
+ styles.container,
930
+ isExiting ? animations.exit : animations.enter,
931
+ stackCount && stackCount > 1 && "mb-1"
932
+ )}
933
+ style={{
934
+ transform: `translateX(${dragX}px)`,
935
+ opacity: Math.max(0.5, 1 - Math.abs(dragX) / 200),
936
+ transition: dragX === 0 ? 'transform 0.2s, opacity 0.2s' : 'none'
937
+ }}
938
+ onMouseEnter={() => setIsPaused(true)}
939
+ onMouseLeave={() => setIsPaused(false)}
940
+ onClick={() => hasLongContent() && setIsExpanded(!isExpanded)}
941
+ onMouseDown={(e) => handleDragStart(e.clientX)}
942
+ onMouseMove={(e) => dragX !== 0 && handleDragMove(e.clientX)}
943
+ onMouseUp={handleDragEnd}
944
+ onTouchStart={(e) => handleDragStart(e.touches[0].clientX)}
945
+ onTouchMove={(e) => handleDragMove(e.touches[0].clientX)}
946
+ onTouchEnd={handleDragEnd}
947
+ >
948
+ {toast.icon ? (
949
+ <div className={`w-5 h-5 mt-0.5 flex-shrink-0 ${styles.icon}`}>
950
+ {toast.icon}
951
+ </div>
952
+ ) : IconComponent ? (
953
+ <IconComponent className={`w-5 h-5 mt-0.5 flex-shrink-0 ${styles.icon}`} />
954
+ ) : null}
955
+
956
+ <div className="flex-1 min-w-0">
957
+ <div className={`font-semibold text-sm ${styles.title}`}>
958
+ {toast.title}
959
+ </div>
960
+ {toast.description && (
961
+ <div className={cn(
962
+ `text-sm mt-1 ${styles.description}`,
963
+ shouldCollapse && "line-clamp-2"
964
+ )}>
965
+ {toast.description}
966
+ </div>
967
+ )}
968
+
969
+ {toast.action && (
970
+ <button
971
+ onClick={(e) => {
972
+ e.stopPropagation()
973
+ toast.action.onClick()
974
+ }}
975
+ className={`mt-2 px-3 py-1.5 text-xs rounded-md transition-colors ${styles.button}`}
976
+ >
977
+ {toast.action.label}
978
+ </button>
979
+ )}
980
+
981
+ {hasLongContent() && (
982
+ <button
983
+ onClick={(e) => {
984
+ e.stopPropagation()
985
+ setIsExpanded(!isExpanded)
986
+ }}
987
+ className="text-xs mt-1 opacity-70 hover:opacity-100"
988
+ >
989
+ {isExpanded ? 'Show less' : 'Show more'}
990
+ </button>
991
+ )}
992
+ </div>
993
+
994
+ {stackCount && stackCount > 1 && (
995
+ <div className={`absolute -bottom-2 left-2 right-2 h-1 rounded-b-lg opacity-50 ${styles.container}`} />
996
+ )}
997
+
998
+ {toast.dismissible !== false && (
999
+ <button
1000
+ onClick={(e) => {
1001
+ e.stopPropagation()
1002
+ handleDismiss()
1003
+ }}
1004
+ className={`p-1 rounded-md transition-colors flex-shrink-0 ${styles.dismissButton}`}
1005
+ >
1006
+ <X className="w-4 h-4" />
1007
+ </button>
1008
+ )}
1009
+ </ToastWrapper>
1010
+ )
1011
+ }
1012
+ const ToastContainer: React.FC<{
1013
+ position: string
1014
+ toasts: Toast[]
1015
+ onRemove: (id: string) => void
1016
+ animated?: boolean
1017
+ }> = ({ position, toasts, onRemove, animated = false }) => {
1018
+ if (toasts.length === 0) return null
1019
+
1020
+ // Group toasts by type
1021
+ const groupedToasts = toasts.reduce((acc, toast) => {
1022
+ const key = `${toast.type}-${typeof toast.title === 'string' ? toast.title : 'custom'}`
1023
+ if (!acc[key]) {
1024
+ acc[key] = []
1025
+ }
1026
+ acc[key].push(toast)
1027
+ return acc
1028
+ }, {} as Record<string, Toast[]>)
1029
+
1030
+ // Create display toasts with stack counts
1031
+ const displayToasts = Object.values(groupedToasts).map(group => {
1032
+ const latest = group[group.length - 1]
1033
+ return {
1034
+ toast: latest,
1035
+ count: group.length
1036
+ }
1037
+ })
1038
+ const ContainerWrapper = animated ? AnimatePresence : React.Fragment
1039
+ const containerProps = animated ? { mode: 'popLayout' as const } : {}
1040
+ return (
1041
+ <div className={cn(
1042
+ "fixed z-50 flex flex-col gap-2 max-w-sm w-full pointer-events-none",
1043
+ position === "top-right" ? "top-4 right-4" :
1044
+ position === "top-center" ? "top-4 left-1/2 -translate-x-1/2" :
1045
+ position === "top-left" ? "top-4 left-4" :
1046
+ position === "bottom-right" ? "bottom-4 right-4" :
1047
+ position === "bottom-center" ? "bottom-4 left-1/2 -translate-x-1/2" :
1048
+ position === "bottom-left" ? "bottom-4 left-4" :
1049
+ "top-4 left-1/2 -translate-x-1/2"
1050
+ )}>
1051
+ <ContainerWrapper {...containerProps}>
1052
+ {displayToasts.map(({ toast, count }) => (
1053
+ <div key={toast.id} className="pointer-events-auto">
1054
+ <ToastComponent
1055
+ toast={toast}
1056
+ onDismiss={() => onRemove(toast.id)}
1057
+ position={position}
1058
+ stackCount={count}
1059
+ />
1060
+ </div>
1061
+ ))}
1062
+ </ContainerWrapper>
1063
+ </div>
1064
+ )
1065
+ }
1066
+ export function Toasty({
1067
+ position = 'top-center',
1068
+ limit = 3,
1069
+ animated = false,
1070
+ children,
1071
+ }: {
1072
+ position?: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left'
1073
+ limit?: number
1074
+ animated?: boolean
1075
+ children: React.ReactNode
1076
+ }) {
1077
+ const [toasts, setToasts] = useState<Toast[]>([])
1078
+ const [queue, setQueue] = useState<Toast[]>([])
1079
+
1080
+ // Process queue when toasts are dismissed
1081
+ useEffect(() => {
1082
+ if (toasts.length < limit && queue.length > 0) {
1083
+ const [nextToast, ...remainingQueue] = queue
1084
+ setToasts(prev => [...prev, nextToast])
1085
+ setQueue(remainingQueue)
1086
+ }
1087
+ }, [toasts.length, queue.length, limit])
1088
+
1089
+ // Keyboard shortcuts
1090
+ useEffect(() => {
1091
+ const handleKeyDown = (e: KeyboardEvent) => {
1092
+ if (e.key === 'Escape' && toasts.length > 0) {
1093
+ const lastToast = toasts[toasts.length - 1]
1094
+ if (lastToast.dismissible !== false) {
1095
+ removeToast(lastToast.id)
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ window.addEventListener('keydown', handleKeyDown)
1101
+ return () => window.removeEventListener('keydown', handleKeyDown)
1102
+ }, [toasts])
1103
+
1104
+ const addToast = useCallback((toast: Omit<Toast, 'id'>): string => {
1105
+ const id = Date.now().toString() + Math.random().toString(36).substr(2, 9)
1106
+ const newToast = { ...toast, id }
1107
+
1108
+ setToasts((prev) => {
1109
+ if (prev.length >= limit) {
1110
+ setQueue(q => [...q, newToast])
1111
+ return prev
1112
+ }
1113
+ return [...prev, newToast]
1114
+ })
1115
+
1116
+ return id
1117
+ }, [limit])
1118
+
1119
+ const removeToast = useCallback((id: string) => {
1120
+ setToasts((prev) => prev.filter((toast) => toast.id !== id))
1121
+ }, [])
1122
+
1123
+ const updateToast = useCallback((id: string, updates: Partial<Toast>) => {
1124
+ setToasts((prev) =>
1125
+ prev.map((toast) =>
1126
+ toast.id === id ? { ...toast, ...updates } : toast
1127
+ )
1128
+ )
1129
+ }, [])
1130
+
1131
+ const toastMethods = {
1132
+ success: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1133
+ addToast({ type: 'success', title, description, ...options }),
1134
+ warning: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1135
+ addToast({ type: 'warning', title, description, ...options }),
1136
+ error: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1137
+ addToast({ type: 'error', title, description, ...options }),
1138
+ info: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1139
+ addToast({ type: 'info', title, description, ...options }),
1140
+ loading: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1141
+ addToast({ type: 'loading', title, description, duration: 0, dismissible: false, ...options }),
1142
+ default: (title: string | React.ReactNode, description?: string | React.ReactNode, options?: Partial<Toast>) =>
1143
+ addToast({ type: 'default', title, description, ...options }),
1144
+ promise: async <T,>(promise: Promise<T>, options: PromiseOptions): Promise<T> => {
1145
+ const id = addToast({
1146
+ type: 'loading',
1147
+ title: options.loading,
1148
+ description: options.description,
1149
+ duration: 0,
1150
+ dismissible: false,
1151
+ variant: options.variant
1152
+ })
1153
+
1154
+ try {
1155
+ const result = await promise
1156
+ const successTitle = typeof options.success === 'function'
1157
+ ? options.success(result)
1158
+ : options.success
1159
+
1160
+ updateToast(id, {
1161
+ type: 'success',
1162
+ title: successTitle,
1163
+ duration: options.duration || 4000,
1164
+ dismissible: true
1165
+ })
1166
+
1167
+ return result
1168
+ } catch (error) {
1169
+ const errorTitle = typeof options.error === 'function'
1170
+ ? options.error(error)
1171
+ : options.error
1172
+
1173
+ updateToast(id, {
1174
+ type: 'error',
1175
+ title: errorTitle,
1176
+ duration: options.duration || 4000,
1177
+ dismissible: true
1178
+ })
1179
+
1180
+ throw error
1181
+ }
1182
+ }
1183
+ }
1184
+
1185
+ const contextValue: ToastContextType = {
1186
+ toasts,
1187
+ addToast,
1188
+ removeToast,
1189
+ updateToast,
1190
+ toast: toastMethods,
1191
+ }
1192
+
1193
+ // Group toasts by position
1194
+ const toastsByPosition = toasts.reduce((acc, toast) => {
1195
+ const pos = toast.position || position
1196
+ if (!acc[pos]) acc[pos] = []
1197
+ acc[pos].push(toast)
1198
+ return acc
1199
+ }, {} as Record<string, Toast[]>)
1200
+
1201
+ return (
1202
+ <ToastContext.Provider value={contextValue}>
1203
+ {children}
1204
+ {Object.entries(toastsByPosition).map(([pos, posToasts]) => (
1205
+ <ToastContainer
1206
+ key={pos}
1207
+ position={pos}
1208
+ toasts={posToasts}
1209
+ onRemove={removeToast}
1210
+ animated={animated}
1211
+ />
1212
+ ))}
1213
+ </ToastContext.Provider>
1214
+ )
1215
+ }
1216
+ export function useToasted() {
1217
+ const context = useContext(ToastContext)
1218
+
1219
+ if (!context) {
1220
+ throw new Error('useToasted must be used within a Toasty')
1221
+ }
1222
+
1223
+ return context
1224
+ }
1225
+ export function ToastDemo() {
1226
+ const { toast, updateToast } = useToasted()
1227
+ const [promiseStatus, setPromiseStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
1228
+
1229
+ // Mock async function
1230
+ const fetchData = () => {
1231
+ return new Promise((resolve, reject) => {
1232
+ setTimeout(() => {
1233
+ Math.random() > 0.5 ? resolve({ data: 'Success!' }) : reject(new Error('Failed to fetch'))
1234
+ }, 2000)
1235
+ })
1236
+ }
1237
+
1238
+ return (
1239
+ <div className="space-y-8 p-6 max-w-4xl mx-auto">
1240
+ {/* Basic Toasts */}
1241
+ <div>
1242
+ <h3 className="text-lg font-semibold mb-4">Basic Toasts</h3>
1243
+ <div className="flex flex-col gap-2">
1244
+ <Button variant='outline' onClick={() => toast.success('Success!', 'Your action was completed successfully.')}>
1245
+ Success Toast
1246
+ </Button>
1247
+ <Button variant='outline' onClick={() => toast.error('Error!', 'Something went wrong.')}>
1248
+ Error Toast
1249
+ </Button>
1250
+ <Button variant='outline' onClick={() => toast.warning('Warning!', 'Please check your input.')}>
1251
+ Warning Toast
1252
+ </Button>
1253
+ <Button variant='outline' onClick={() => toast.info('Info', 'Here is some information.')}>
1254
+ Info Toast
1255
+ </Button>
1256
+ <Button variant='outline' onClick={() => toast.default('Default', 'This is a default toast.')}>
1257
+ Default Toast
1258
+ </Button>
1259
+ </div>
1260
+ </div>
1261
+
1262
+ {/* Variants */}
1263
+ <div>
1264
+ <h3 className="text-lg font-semibold mb-4">Style Variants</h3>
1265
+ <div className="flex flex-wrap gap-2">
1266
+ <Button variant='outline' onClick={() => toast.success('Simple', 'Simple variant', { variant: 'simple' })}>
1267
+ Simple
1268
+ </Button>
1269
+ <Button variant='outline' onClick={() => toast.success('Bordered', 'Bordered variant', { variant: 'bordered' })}>
1270
+ Bordered
1271
+ </Button>
1272
+ <Button variant='outline' onClick={() => toast.success('Filled', 'Filled variant', { variant: 'filled' })}>
1273
+ Filled
1274
+ </Button>
1275
+ <Button variant='outline' onClick={() => toast.success('Accent', 'Accent variant', { variant: 'accent' })}>
1276
+ Accent
1277
+ </Button>
1278
+ <Button variant='outline' onClick={() => toast.success('Banner', 'Banner variant', { variant: 'banner' })}>
1279
+ Banner
1280
+ </Button>
1281
+ <Button variant='outline' onClick={() => toast.success('Card', 'Card variant', { variant: 'card' })}>
1282
+ Card
1283
+ </Button>
1284
+ <Button variant='outline' onClick={() => toast.success('Minimal', 'Minimal variant', { variant: 'minimal' })}>
1285
+ Minimal
1286
+ </Button>
1287
+ <Button variant='outline' onClick={() => toast.success('Soft', 'Soft variant', { variant: 'soft' })}>
1288
+ Soft
1289
+ </Button>
1290
+ </div>
1291
+ </div>
1292
+
1293
+ {/* Toast Update by ID */}
1294
+ <div>
1295
+ <h3 className="text-lg font-semibold mb-4">Update Toast by ID</h3>
1296
+ <Button variant='outline'
1297
+ onClick={() => {
1298
+ const id = toast.loading('Processing...', 'Please wait while we process your request.')
1299
+ setTimeout(() => {
1300
+ updateToast(id, {
1301
+ type: 'success',
1302
+ title: 'Complete!',
1303
+ description: 'Your request was processed successfully.',
1304
+ duration: 4000,
1305
+ dismissible: true
1306
+ })
1307
+ }, 2000)
1308
+ }}
1309
+ >
1310
+ Loading → Success
1311
+ </Button>
1312
+ </div>
1313
+
1314
+ {/* Promise Toast */}
1315
+ <div>
1316
+ <h3 className="text-lg font-semibold mb-4">Promise Toast</h3>
1317
+ <div className="flex gap-2">
1318
+ <Button variant='outline'
1319
+ onClick={() => {
1320
+ setPromiseStatus('loading')
1321
+ toast.promise(
1322
+ fetchData(),
1323
+ {
1324
+ loading: 'Fetching data...',
1325
+ success: 'Data fetched successfully!',
1326
+ error: (err) => `Error: ${err.message}`
1327
+ }
1328
+ )
1329
+ .then(() => setPromiseStatus('success'))
1330
+ .catch(() => setPromiseStatus('error'))
1331
+ }}
1332
+ disabled={promiseStatus === 'loading'}
1333
+ >
1334
+ Promise Toast (Random)
1335
+ </Button>
1336
+ <Button variant='outline'
1337
+ variant="outline"
1338
+ onClick={() => {
1339
+ toast.promise(
1340
+ new Promise((resolve) => setTimeout(() => resolve({ user: 'John' }), 1500)),
1341
+ {
1342
+ loading: 'Loading user...',
1343
+ success: (data) => `Welcome ${data.user}!`,
1344
+ error: 'Failed to load user',
1345
+ variant: 'card'
1346
+ }
1347
+ )
1348
+ }}
1349
+ >
1350
+ Promise with Data
1351
+ </Button>
1352
+ </div>
1353
+ </div>
1354
+
1355
+ {/* With Actions */}
1356
+ <div>
1357
+ <h3 className="text-lg font-semibold mb-4">With Actions</h3>
1358
+ <Button variant='outline'
1359
+ onClick={() =>
1360
+ toast.success('File uploaded', 'Your file has been uploaded successfully.', {
1361
+ action: {
1362
+ label: 'View',
1363
+ onClick: () => console.log('View clicked')
1364
+ }
1365
+ })
1366
+ }
1367
+ >
1368
+ Toast with Action
1369
+ </Button>
1370
+ </div>
1371
+
1372
+ {/* Custom Icons */}
1373
+ <div>
1374
+ <h3 className="text-lg font-semibold mb-4">Custom Icons</h3>
1375
+ <div className="flex gap-2">
1376
+ <Button variant='outline'
1377
+ onClick={() =>
1378
+ toast.success('Saved!', 'Your changes have been saved.', {
1379
+ icon: <CheckCircle className="w-5 h-5 text-green-400" />
1380
+ })
1381
+ }
1382
+ >
1383
+ Custom Success Icon
1384
+ </Button>
1385
+ <Button variant='outline'
1386
+ onClick={() =>
1387
+ toast.info('Notification', 'You have a new message.', {
1388
+ icon: <Bell className="w-5 h-5 text-blue-400" />
1389
+ })
1390
+ }
1391
+ >
1392
+ Bell Icon
1393
+ </Button>
1394
+ </div>
1395
+ </div>
1396
+
1397
+ {/* Per-Toast Positioning */}
1398
+ <div>
1399
+ <h3 className="text-lg font-semibold mb-4">Custom Positions</h3>
1400
+ <div className="flex flex-wrap gap-2">
1401
+ <Button variant='outline' onClick={() => toast.info('Top Right', '', { position: 'top-right' })}>
1402
+ Top Right
1403
+ </Button>
1404
+ <Button variant='outline' onClick={() => toast.info('Top Center', '', { position: 'top-center' })}>
1405
+ Top Center
1406
+ </Button>
1407
+ <Button variant='outline' onClick={() => toast.info('Top Left', '', { position: 'top-left' })}>
1408
+ Top Left
1409
+ </Button>
1410
+ <Button variant='outline' onClick={() => toast.info('Bottom Right', '', { position: 'bottom-right' })}>
1411
+ Bottom Right
1412
+ </Button>
1413
+ <Button variant='outline' onClick={() => toast.info('Bottom Center', '', { position: 'bottom-center' })}>
1414
+ Bottom Center
1415
+ </Button>
1416
+ <Button variant='outline' onClick={() => toast.info('Bottom Left', '', { position: 'bottom-left' })}>
1417
+ Bottom Left
1418
+ </Button>
1419
+ </div>
1420
+ </div>
1421
+
1422
+ {/* Long Content (Expandable) */}
1423
+ <div>
1424
+ <h3 className="text-lg font-semibold mb-4">Expandable Content</h3>
1425
+ <Button variant='outline'
1426
+ onClick={() =>
1427
+ toast.info(
1428
+ 'Long Title That Might Need Expansion',
1429
+ 'This is a very long description that contains a lot of information. The user might need to expand this to read the full content. It demonstrates the expand/collapse feature that shows a preview and allows expanding on hover or click to see the complete message.',
1430
+ { collapseThreshold: 100 }
1431
+ )
1432
+ }
1433
+ >
1434
+ Long Content Toast
1435
+ </Button>
1436
+ </div>
1437
+
1438
+ {/* Custom Duration */}
1439
+ <div>
1440
+ <h3 className="text-lg font-semibold mb-4">Custom Duration</h3>
1441
+ <div className="flex gap-2">
1442
+ <Button variant='outline' onClick={() => toast.info('Quick', 'Dismisses in 1 second', { duration: 1000 })}>
1443
+ 1 Second
1444
+ </Button>
1445
+ <Button variant='outline' onClick={() => toast.info('Normal', 'Dismisses in 4 seconds', { duration: 4000 })}>
1446
+ 4 Seconds
1447
+ </Button>
1448
+ <Button variant='outline' onClick={() => toast.info('Long', 'Dismisses in 10 seconds', { duration: 10000 })}>
1449
+ 10 Seconds
1450
+ </Button>
1451
+ <Button variant='outline' onClick={() => toast.info('Persistent', 'Must be dismissed manually', { duration: 0 })}>
1452
+ No Auto-Dismiss
1453
+ </Button>
1454
+ </div>
1455
+ </div>
1456
+
1457
+ {/* Non-Dismissible */}
1458
+ <div>
1459
+ <h3 className="text-lg font-semibold mb-4">Non-Dismissible</h3>
1460
+ <Button variant='outline'
1461
+ onClick={() => {
1462
+ const id = toast.loading('Processing...', 'This cannot be dismissed', {
1463
+ dismissible: false,
1464
+ duration: 0
1465
+ })
1466
+ setTimeout(() => {
1467
+ updateToast(id, {
1468
+ type: 'success',
1469
+ title: 'Done!',
1470
+ dismissible: true,
1471
+ duration: 4000
1472
+ })
1473
+ }, 3000)
1474
+ }}
1475
+ >
1476
+ Non-Dismissible Loading
1477
+ </Button>
1478
+ </div>
1479
+
1480
+ {/* Stacking Demo */}
1481
+ <div>
1482
+ <h3 className="text-lg font-semibold mb-4">Stacking (Same Type)</h3>
1483
+ <Button variant='outline'
1484
+ onClick={() => {
1485
+ toast.success('Item Added', 'Product added to cart')
1486
+ setTimeout(() => toast.success('Item Added', 'Product added to cart'), 500)
1487
+ setTimeout(() => toast.success('Item Added', 'Product added to cart'), 1000)
1488
+ }}
1489
+ >
1490
+ Trigger Multiple Same Toasts
1491
+ </Button>
1492
+ </div>
1493
+
1494
+ {/* Swipe to Dismiss Hint */}
1495
+ <div>
1496
+ <h3 className="text-lg font-semibold mb-4">Interactive Features</h3>
1497
+ <div className="space-y-2">
1498
+ <p className="text-sm text-muted-foreground">
1499
+ • <strong>Swipe to dismiss:</strong> Drag toasts left or right to dismiss them
1500
+ </p>
1501
+ <p className="text-sm text-muted-foreground">
1502
+ • <strong>Pause on hover:</strong> Hover over a toast to pause auto-dismiss
1503
+ </p>
1504
+ <p className="text-sm text-muted-foreground">
1505
+ • <strong>Keyboard shortcut:</strong> Press ESC to dismiss the most recent toast
1506
+ </p>
1507
+ <p className="text-sm text-muted-foreground">
1508
+ • <strong>Expand/Collapse:</strong> Click on long toasts to expand or collapse them
1509
+ </p>
1510
+ </div>
1511
+ <Button variant='outline' onClick={() => toast.info('Try Me!', 'Hover, swipe, or press ESC')}>
1512
+ Try Interactive Toast
1513
+ </Button>
1514
+ </div>
1515
+
1516
+ {/* Spam Test (Queue System) */}
1517
+ <div>
1518
+ <h3 className="text-lg font-semibold mb-4">Queue System (Limit Test)</h3>
1519
+ <Button variant='outline'
1520
+ onClick={() => {
1521
+ for (let i = 1; i <= 10; i++) {
1522
+ setTimeout(() => {
1523
+ toast.info(`Toast ${i}`, `This is toast number ${i}`)
1524
+ }, i * 100)
1525
+ }
1526
+ }}
1527
+ >
1528
+ Trigger 10 Toasts (Tests Limit)
1529
+ </Button>
1530
+ </div>
1531
+ </div>
1532
+ )
1533
+ }
1534
+
1535
+
1536
+ const ToastComponent1: React.FC<{ toast: Toast; onDismiss: () => void }> = ({ toast, onDismiss }) => {
1537
+ const variant = toast.variant || 'simple'
1538
+ const styles = styleConfigs[variant]?.[toast.type] || styleConfigs.simple[toast.type]
1539
+ const IconComponent = toastIcons[toast.type]
1540
+
1541
+ useEffect(() => {
1542
+ if (toast.duration !== 0) {
1543
+ const timer = setTimeout(() => {
1544
+ onDismiss()
1545
+ }, toast.duration || 4000)
1546
+
1547
+ return () => clearTimeout(timer)
1548
+ }
1549
+ }, [toast.duration, onDismiss])
1550
+
1551
+ return (
1552
+ <div className={`
1553
+ relative flex items-start gap-3 rounded-lg shadow-lg backdrop-blur-sm
1554
+ animate-in slide-in-from-top-2 fade-in-0 duration-300
1555
+ ${styles.container}
1556
+ `}>
1557
+ {IconComponent && (
1558
+ <IconComponent className={`w-5 h-5 mt-0.5 flex-shrink-0 ${styles.icon}`} />
1559
+ )}
1560
+
1561
+ <div className="flex-1 min-w-0">
1562
+ <div className={`font-semibold text-sm ${styles.title}`}>
1563
+ {toast.title}
1564
+ </div>
1565
+ {toast.description && (
1566
+ <div className={`text-sm mt-1 ${styles.description}`}>
1567
+ {toast.description}
1568
+ </div>
1569
+ )}
1570
+
1571
+ {toast.action && (
1572
+ <button
1573
+ onClick={toast.action.onClick}
1574
+ className={`mt-2 px-3 py-1.5 text-xs rounded-md transition-colors ${styles.button}`}
1575
+ >
1576
+ {toast.action.label}
1577
+ </button>
1578
+ )}
1579
+ </div>
1580
+
1581
+ {toast.dismissible !== false && (
1582
+ <button
1583
+ onClick={onDismiss}
1584
+ className={`p-1 rounded-md transition-colors flex-shrink-0 ${styles.dismissButton}`}
1585
+ >
1586
+ <X className="w-4 h-4" />
1587
+ </button>
1588
+ )}
1589
+ </div>
1590
+ )
1591
+ }
1592
+ const ToastContainer1: React.FC = ({ position }) => {
1593
+ const context = useContext(ToastContext);
1594
+ const p = position;
1595
+
1596
+ if (!context || context.toasts.length === 0) {
1597
+ return null;
1598
+ }
1599
+
1600
+ return (
1601
+ <div className={cn(
1602
+ "fixed z-50 flex flex-col gap-2 max-w-sm w-full",
1603
+ p === "top-right" ? "top-4 right-4" :
1604
+ p === "top-center" ? "top-4 left-1/2 transform -translate-x-1/2" :
1605
+ p === "top-left" ? "top-4 left-4" :
1606
+ p === "bottom-right" ? "bottom-4 right-4" :
1607
+ p === "bottom-center" ? "bottom-4 left-1/2 transform -translate-x-1/2" :
1608
+ p === "bottom-left" ? "bottom-4 left-4" :
1609
+ "top-4 left-1/2 transform -translate-x-1/2"
1610
+ )}>
1611
+ {context.toasts.map((toast) => (
1612
+ <ToastComponent
1613
+ key={toast.id}
1614
+ toast={toast}
1615
+ onDismiss={() => context.removeToast(toast.id)}
1616
+ />
1617
+ ))}
1618
+ </div>
1619
+ );
1620
+ };
1621
+ export function Toasty1({ positoin = 'top-center', children }: { children: React.ReactNode }) {
1622
+ const [toasts, setToasts] = useState<Toast[]>([])
1623
+
1624
+ const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
1625
+ const id = Date.now().toString() + Math.random().toString(36).substr(2, 9)
1626
+ const newToast = { ...toast, id }
1627
+
1628
+ setToasts((prev) => [...prev, newToast])
1629
+ }, [])
1630
+
1631
+ const removeToast = useCallback((id: string) => {
1632
+ setToasts((prev) => prev.filter((toast) => toast.id !== id))
1633
+ }, [])
1634
+
1635
+ const toastMethods = {
1636
+ success: (title: string, description?: string, options?: Partial<Toast>) =>
1637
+ addToast({ type: 'success', title, description, ...options }),
1638
+ warning: (title: string, description?: string, options?: Partial<Toast>) =>
1639
+ addToast({ type: 'warning', title, description, ...options }),
1640
+ error: (title: string, description?: string, options?: Partial<Toast>) =>
1641
+ addToast({ type: 'error', title, description, ...options }),
1642
+ info: (title: string, description?: string, options?: Partial<Toast>) =>
1643
+ addToast({ type: 'info', title, description, ...options }),
1644
+ default: (title: string, description?: string, options?: Partial<Toast>) =>
1645
+ addToast({ type: 'default', title, description, ...options }),
1646
+ }
1647
+
1648
+ const contextValue: ToastContextType = {
1649
+ toasts,
1650
+ addToast,
1651
+ removeToast,
1652
+ toast: toastMethods,
1653
+ }
1654
+
1655
+ return (
1656
+ <ToastContext.Provider value={contextValue}>
1657
+ {children}
1658
+ <ToastContainer positoin={positoin} />
1659
+ </ToastContext.Provider>
1660
+ )
1661
+ }
1662
+
1663
+
1664
+ export function ToastDemo1() {
1665
+ const { toast } = useToasted()
1666
+ // toast.default('default', 'Accent default toast', { variant: 'accent' })
1667
+ // toast.success('success', 'Accent success toast', { variant: 'accent' })
1668
+ // toast.warning('warning!', 'Simple warning toast', { variant: 'accent' })
1669
+ // toast.error('error!', 'Simple error toast', { variant: 'accent' })
1670
+ // toast.info('info!', 'Simple info toast', { variant: 'accent' })
1671
+ return (
1672
+ <div className="min-h-screen bg-background p-8">
1673
+ <div className="max-w-2xl mx-auto">
1674
+ <h1 className="text-3xl font-bold text-foreground mb-8 text-center">
1675
+ Toast Component Demo
1676
+ </h1>
1677
+
1678
+ {/* Accent Variant */}
1679
+ <div className="space-y-2">
1680
+ <h3 className="text-lg font-semibold text-foreground">Accent Variant</h3>
1681
+ <div className="grid gap-2 md:grid-cols-2">
1682
+ <Button variant="outline"
1683
+ onClick={() => toast.default('default', 'Accent default toast', { variant: 'accent' })}>
1684
+ Accent default
1685
+ </Button>
1686
+ <Button variant="outline"
1687
+ onClick={() => toast.success('success', 'Accent success toast', { variant: 'accent' })}>
1688
+ Accent success
1689
+ </Button>
1690
+ <Button variant="outline"
1691
+ onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'accent' })}>
1692
+ Simple warning
1693
+ </Button>
1694
+ <Button variant="outline"
1695
+ onClick={() => toast.error('error!', 'Simple error toast', { variant: 'accent' })}>
1696
+ Simple error
1697
+ </Button>
1698
+ <Button variant="outline"
1699
+ onClick={() => toast.info('info!', 'Simple info toast', { variant: 'accent' })}>
1700
+ Simple info
1701
+ </Button>
1702
+ </div>
1703
+ </div>
1704
+
1705
+ <div className="space-y-6">
1706
+ {/* Simple Variant */}
1707
+ <div className="space-y-2">
1708
+ <h3 className="text-lg font-semibold text-foreground">Simple Variant</h3>
1709
+ <div className="grid gap-2 md:grid-cols-2">
1710
+ <Button variant="outline" onClick={() => toast.success('Success!', 'Simple success toast', { variant: 'accent' })}>
1711
+ Simple Success
1712
+ </Button>
1713
+ <Button variant="outline" onClick={() => toast.error('Error!', 'Simple error toast', { variant: 'simple' })}>
1714
+ Simple Error
1715
+ </Button>
1716
+ <Button variant="outline" onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'simple' })}>
1717
+ Simple warning
1718
+ </Button>
1719
+ <Button variant="outline" onClick={() => toast.info('info!', 'Simple info toast', { variant: 'simple' })}>
1720
+ Simple info
1721
+ </Button>
1722
+ <Button variant="outline" onClick={() => toast.default('default!', 'Simple default toast', { variant: 'simple' })}>
1723
+ Simple default
1724
+ </Button>
1725
+
1726
+ </div>
1727
+ </div>
1728
+
1729
+ {/* Bordered Variant */}
1730
+ <div className="space-y-2">
1731
+ <h3 className="text-lg font-semibold text-foreground">Bordered Variant</h3>
1732
+ <div className="grid gap-2 md:grid-cols-2">
1733
+ <Button variant="outline"
1734
+ onClick={() => toast.success('success!', 'Bordered success toast', { variant: 'bordered' })}>
1735
+ Bordered Warning
1736
+ </Button>
1737
+ <Button variant="outline"
1738
+ onClick={() => toast.error('error', 'Bordered error toast', { variant: 'bordered' })}>
1739
+ Bordered Info
1740
+ </Button>
1741
+ <Button variant="outline"
1742
+
1743
+ onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'bordered' })}>
1744
+ Simple warning
1745
+ </Button>
1746
+ <Button variant="outline"
1747
+ onClick={() => toast.info('info!', 'Simple info toast', { variant: 'bordered' })}>
1748
+ Simple info
1749
+ </Button>
1750
+ <Button variant="outline"
1751
+ onClick={() => toast.default('default!', 'Simple default toast', { variant: 'bordered' })}>
1752
+ Simple default
1753
+ </Button>
1754
+
1755
+ </div>
1756
+ </div>
1757
+
1758
+ {/* Filled Variant */}
1759
+ <div className="space-y-2">
1760
+ <h3 className="text-lg font-semibold text-foreground">Filled Variant</h3>
1761
+ <div className="grid gap-2 md:grid-cols-2">
1762
+ <Button variant="outline" onClick={() => toast.success('Payment Complete', 'Filled success toast', { variant: 'filled' })}>
1763
+ Filled Success
1764
+ </Button>
1765
+ <Button variant="outline" onClick={() => toast.error('Access Denied', 'Filled error toast', { variant: 'filled' })}>
1766
+ Filled Error
1767
+ </Button>
1768
+ <Button variant="outline" onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'filled' })}>
1769
+ Simple warning
1770
+ </Button>
1771
+ <Button variant="outline" onClick={() => toast.info('info!', 'Simple info toast', { variant: 'filled' })}>
1772
+ Simple info
1773
+ </Button>
1774
+ <Button variant="outline" onClick={() => toast.default('default!', 'Simple default toast', { variant: 'filled' })}>
1775
+ Simple default
1776
+ </Button>
1777
+ </div>
1778
+ </div>
1779
+
1780
+
1781
+
1782
+ {/* Card Variant */}
1783
+ <div className="space-y-2">
1784
+ <h3 className="text-lg font-semibold text-foreground">Card Variant</h3>
1785
+ <div className="grid gap-2 md:grid-cols-2">
1786
+ <Button variant="outline" onClick={() => toast.success('Mission Complete', 'Card success toast with backdrop blur', { variant: 'card' })}>
1787
+ Card Success
1788
+ </Button>
1789
+ <Button variant="outline" onClick={() => toast.error('Critical Error', 'Card error toast with backdrop blur', { variant: 'card' })}>
1790
+ Card Error
1791
+ </Button>
1792
+ <Button variant="outline" onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'card' })}>
1793
+ Simple warning
1794
+ </Button>
1795
+ <Button variant="outline" onClick={() => toast.info('info!', 'Simple info toast', { variant: 'card' })}>
1796
+ Simple info
1797
+ </Button>
1798
+ <Button variant="outline" onClick={() => toast.default('default!', 'Simple default toast', { variant: 'card' })}>
1799
+ Simple default
1800
+ </Button>
1801
+ </div>
1802
+ </div>
1803
+
1804
+ {/* Minimal Variant */}
1805
+ <div className="space-y-2">
1806
+ <h3 className="text-lg font-semibold text-foreground">Minimal Variant</h3>
1807
+ <div className="grid gap-2 md:grid-cols-2">
1808
+ <Button variant="outline" onClick={() => toast.success('Saved', 'Minimal success toast', { variant: 'minimal' })}>
1809
+ Minimal Success
1810
+ </Button>
1811
+ <Button variant="outline" onClick={() => toast.error('error', 'Minimal info toast', { variant: 'minimal' })}>
1812
+ Minimal error
1813
+ </Button>
1814
+ <Button variant="outline" onClick={() => toast.warning('warning!', 'Simple warning toast', { variant: 'minimal' })}>
1815
+ Simple warning
1816
+ </Button>
1817
+ <Button variant="outline" onClick={() => toast.info('info!', 'Simple info toast', { variant: 'minimal' })}>
1818
+ Simple info
1819
+ </Button>
1820
+ <Button variant="outline" onClick={() => toast.default('default!', 'Simple default toast', { variant: 'minimal' })}>
1821
+ Simple default
1822
+ </Button>
1823
+ </div>
1824
+ </div>
1825
+
1826
+ {/* Special Options */}
1827
+ <div className="space-y-2">
1828
+ <h3 className="text-lg font-semibold text-foreground">Special Options</h3>
1829
+ <div className="grid gap-2 md:grid-cols-2">
1830
+ <Button variant="outline" onClick={() => toast.success('With Action', 'This toast has an action button', {
1831
+ variant: 'bordered',
1832
+ action: { label: 'Undo', onClick: () => toast.info('Undone!', 'Action was undone') }
1833
+ })}>
1834
+ Toast with Action
1835
+ </Button>
1836
+ <Button variant="outline" onClick={() => toast.error('Persistent', 'This toast stays until dismissed', {
1837
+ variant: 'filled',
1838
+ duration: 0
1839
+ })}>
1840
+ Persistent Toast
1841
+ </Button>
1842
+
1843
+ </div>
1844
+ </div>
1845
+ </div>
1846
+ </div>
1847
+ </div>
1848
+ )
1849
+ }
1850
+ export const ToastDemo2: React.FC = () => {
1851
+ return (
1852
+ <Toasty>
1853
+ <ToastDemo />
1854
+ </Toasty>
1855
+ );
1856
+ };
1857
+