@catalystsoftware/ui 1.0.5 → 1.0.7
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/dist/data/data.tsx +29 -29
- package/dist/data/tailwind.config.js +3821 -261
- package/dist/data.tsx +29 -29
- package/package.json +4 -3
- package/components/catalyst-ui/buttons/burger.tsx +0 -207
- package/components/catalyst-ui/core/data-display/timeline.tsx +0 -210
- package/components/catalyst-ui/core/feedback/alert.tsx +0 -491
- package/components/catalyst-ui/core/feedback/spinner-1.tsx +0 -65
- package/components/catalyst-ui/core/feedback/toast.tsx +0 -1857
- package/components/catalyst-ui/core/navigation/menu.tsx +0 -164
- package/components/catalyst-ui/forms/toggle-class.tsx +0 -176
- package/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +0 -419
- package/components/catalyst-ui/hooks/use-counter.tsx +0 -13
- package/components/catalyst-ui/hooks/use-event-listener.tsx +0 -23
- package/components/catalyst-ui/hooks/use-export-markdown.tsx +0 -47
- package/components/catalyst-ui/hooks/use-focus.tsx +0 -17
- package/components/catalyst-ui/hooks/use-interval.tsx +0 -23
- package/components/catalyst-ui/hooks/use-is-client.tsx +0 -16
- package/components/catalyst-ui/hooks/use-media-query.tsx +0 -19
- package/components/catalyst-ui/hooks/use-mobile.tsx +0 -19
- package/components/catalyst-ui/hooks/use-resize-observer.tsx +0 -81
- package/components/catalyst-ui/hooks/use-timeout.tsx +0 -21
- package/components/catalyst-ui/hooks/use-timer.tsx +0 -209
- package/components/catalyst-ui/hooks/use-toggle.tsx +0 -12
- package/components/catalyst-ui/media/image.tsx +0 -13
- package/components/catalyst-ui/overlays/dual-sidebar.tsx +0 -4142
- package/components/catalyst-ui/overlays/sidebar-original.tsx +0 -726
- package/components/catalyst-ui/primitives/accordion.tsx +0 -250
- package/components/catalyst-ui/primitives/alert-dialog.tsx +0 -126
- package/components/catalyst-ui/primitives/aspect-ratio.tsx +0 -9
- package/components/catalyst-ui/primitives/avatar.tsx +0 -296
- package/components/catalyst-ui/primitives/badge.tsx +0 -57
- package/components/catalyst-ui/primitives/breadcrumb.tsx +0 -101
- package/components/catalyst-ui/primitives/button.tsx +0 -265
- package/components/catalyst-ui/primitives/calendar-v4.tsx +0 -208
- package/components/catalyst-ui/primitives/calendar.tsx +0 -295
- package/components/catalyst-ui/primitives/card.tsx +0 -618
- package/components/catalyst-ui/primitives/carousel.tsx +0 -238
- package/components/catalyst-ui/primitives/chart.tsx +0 -347
- package/components/catalyst-ui/primitives/checkbox.tsx +0 -225
- package/components/catalyst-ui/primitives/collapsible.tsx +0 -212
- package/components/catalyst-ui/primitives/command.tsx +0 -393
- package/components/catalyst-ui/primitives/context-menu.tsx +0 -236
- package/components/catalyst-ui/primitives/dialog.tsx +0 -471
- package/components/catalyst-ui/primitives/drawer.tsx +0 -761
- package/components/catalyst-ui/primitives/dropdown-menu.tsx +0 -290
- package/components/catalyst-ui/primitives/empty.tsx +0 -104
- package/components/catalyst-ui/primitives/field.tsx +0 -244
- package/components/catalyst-ui/primitives/hover-card.tsx +0 -124
- package/components/catalyst-ui/primitives/input-otp.tsx +0 -76
- package/components/catalyst-ui/primitives/input.tsx +0 -64
- package/components/catalyst-ui/primitives/item.tsx +0 -196
- package/components/catalyst-ui/primitives/kbd.tsx +0 -75
- package/components/catalyst-ui/primitives/label.tsx +0 -24
- package/components/catalyst-ui/primitives/navigation-menu.tsx +0 -150
- package/components/catalyst-ui/primitives/pagination.tsx +0 -198
- package/components/catalyst-ui/primitives/popover.tsx +0 -232
- package/components/catalyst-ui/primitives/progress.tsx +0 -34
- package/components/catalyst-ui/primitives/radio-group.tsx +0 -43
- package/components/catalyst-ui/primitives/resizable.tsx +0 -56
- package/components/catalyst-ui/primitives/select.tsx +0 -155
- package/components/catalyst-ui/primitives/separator.tsx +0 -74
- package/components/catalyst-ui/primitives/sheet.tsx +0 -126
- package/components/catalyst-ui/primitives/skeleton.tsx +0 -15
- package/components/catalyst-ui/primitives/slider.tsx +0 -27
- package/components/catalyst-ui/primitives/switch.tsx +0 -187
- package/components/catalyst-ui/primitives/tabs.tsx +0 -335
- package/components/catalyst-ui/primitives/textarea.tsx +0 -24
- package/components/catalyst-ui/primitives/toggle-group.tsx +0 -55
- package/components/catalyst-ui/primitives/toggle.tsx +0 -42
- package/components/catalyst-ui/primitives/tooltip.tsx +0 -116
- package/components/catalyst-ui/utils/basic-auth.tsx +0 -40
- package/components/catalyst-ui/utils/context-storage.tsx +0 -19
- package/components/catalyst-ui/utils/cors-middleware.tsx +0 -71
- package/components/catalyst-ui/utils/deferred-content.tsx +0 -595
- package/components/catalyst-ui/utils/honeypot-middleware.tsx +0 -38
- package/components/catalyst-ui/utils/incId.tsx +0 -75
- package/components/catalyst-ui/utils/jwk-auth.tsx +0 -36
- package/components/catalyst-ui/utils/request-id.tsx +0 -14
- package/components/catalyst-ui/utils/secure-headers.tsx +0 -37
- package/components/catalyst-ui/utils/server-timing.tsx +0 -23
- package/components/catalyst-ui/utils/utils.ts +0 -43
- package/components/catalyst-ui/utils/with-cookie.tsx +0 -43
- package/components/catalyst-ui/x/accordian-x.tsx +0 -428
- package/components/catalyst-ui/x/alert-x.tsx +0 -413
- package/components/catalyst-ui/x/animated-text-x.tsx +0 -2242
- package/components/catalyst-ui/x/avatar-x.tsx +0 -515
- package/components/catalyst-ui/x/badge-x.tsx +0 -670
- package/components/catalyst-ui/x/button-X.tsx +0 -2857
- package/components/catalyst-ui/x/button-group-x.tsx +0 -847
- package/components/catalyst-ui/x/calendar-x.tsx +0 -1910
- package/components/catalyst-ui/x/card-x.tsx +0 -2597
- package/components/catalyst-ui/x/checkbox-x.tsx +0 -656
- package/components/catalyst-ui/x/collapsible-x.tsx +0 -1360
- package/components/catalyst-ui/x/combobox-x.tsx +0 -911
- package/components/catalyst-ui/x/data-table-x.tsx +0 -1753
- package/components/catalyst-ui/x/date-picker-x.tsx +0 -648
- package/components/catalyst-ui/x/dialog-x.tsx +0 -659
- package/components/catalyst-ui/x/dropdown-menu-x.tsx +0 -612
- package/components/catalyst-ui/x/hover-card-x.tsx +0 -375
- package/components/catalyst-ui/x/icon-x.tsx +0 -840
- package/components/catalyst-ui/x/input-mask-x.tsx +0 -981
- package/components/catalyst-ui/x/input-otp-x.tsx +0 -659
- package/components/catalyst-ui/x/loader-x.tsx +0 -1757
- package/components/catalyst-ui/x/pagination-x.tsx +0 -622
- package/components/catalyst-ui/x/popover-x.tsx +0 -744
- package/components/catalyst-ui/x/radio-group-x.tsx +0 -499
- package/components/catalyst-ui/x/select-x.tsx +0 -1127
- package/components/catalyst-ui/x/sheet-x.tsx +0 -668
- package/components/catalyst-ui/x/switch-x.tsx +0 -681
- package/components/catalyst-ui/x/table-x.tsx +0 -574
- package/components/catalyst-ui/x/tabs-x.tsx +0 -839
- package/components/catalyst-ui/x/textarea-x.tsx +0 -1263
- package/components/catalyst-ui/x/tooltip-x.tsx +0 -396
- package/components/catalyst-ui/x/tracker-x.tsx +0 -560
- package/data/bg-data.tsx +0 -901
- package/data/buttons-data.tsx +0 -2327
- package/data/charts-data.tsx +0 -102
- package/data/chat-data.tsx +0 -83
- package/data/code-data.tsx +0 -1040
- package/data/comboboxes-data.tsx +0 -1843
- package/data/command-data.tsx +0 -1381
- package/data/core-data.tsx +0 -15953
- package/data/crm-data.tsx +0 -47
- package/data/data.tsx +0 -159
- package/data/date-and-time-data.tsx +0 -554
- package/data/dependencies.tsx +0 -7
- package/data/ecommerce-data.tsx +0 -1387
- package/data/forms-data.tsx +0 -7890
- package/data/hooks-data.tsx +0 -5487
- package/data/index.ts +0 -34
- package/data/inputs-data.tsx +0 -557
- package/data/interactive-data.tsx +0 -5394
- package/data/lofi-data.tsx +0 -18295
- package/data/marketing-data.tsx +0 -2546
- package/data/media-data.tsx +0 -1510
- package/data/motion-data.tsx +0 -5801
- package/data/overlay-data.tsx +0 -4136
- package/data/pdf-data.tsx +0 -124
- package/data/pos-data.tsx +0 -213
- package/data/postcss.config.js +0 -6
- package/data/primitive-data.tsx +0 -5170
- package/data/prompt-data.tsx +0 -1226
- package/data/requiredLibs.ts +0 -4
- package/data/sandbox-data.tsx +0 -1
- package/data/sidebars-data.tsx +0 -5421
- package/data/stacks-data.tsx +0 -32
- package/data/table-data.tsx +0 -706
- package/data/tailwind.config.js +0 -270
- package/data/tailwind.config.ngin.js +0 -3830
- package/data/tailwind.css +0 -431
- package/data/tools-data.tsx +0 -6910
- package/data/typography-data.tsx +0 -2050
- package/data/utils-data.tsx +0 -6500
- package/data/x-data.tsx +0 -1171
|
@@ -1,1857 +0,0 @@
|
|
|
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
|
-
|