pejay-ui 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ {
2
+ "toast": {
3
+ "name": "Toast",
4
+ "category": "toast",
5
+ "files": ["templates/toast"],
6
+ "utils": ["cn.ts"],
7
+ "dependencies": ["overlays/portal"],
8
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ export * from "./portal";
@@ -0,0 +1,26 @@
1
+ import { createPortal } from "react-dom";
2
+ import { useEffect, useState, type ReactNode } from "react";
3
+
4
+ interface PortalProps {
5
+ children: ReactNode;
6
+ }
7
+ export function Portal({ children }: PortalProps) {
8
+ const [container, setContainer] = useState<HTMLDivElement | null>(null);
9
+
10
+ useEffect(() => {
11
+ const div = document.createElement("div");
12
+ div.id = "dynamic-portal";
13
+ document.body.appendChild(div);
14
+ setContainer(div);
15
+
16
+ return () => {
17
+ if (div.parentNode) {
18
+ document.body.removeChild(div);
19
+ }
20
+ };
21
+ }, []);
22
+
23
+ if (!container) return null;
24
+
25
+ return createPortal(children, container);
26
+ }
@@ -0,0 +1,183 @@
1
+ # Toast Notification Component
2
+
3
+ A toast notification system featuring:
4
+ - **Fully Customizable Styling**: Complete visual control over status designs.
5
+ - **Interactive Gestures**: Swipe-to-dismiss drag support for touch and pointer events.
6
+ - **Built-in Theme Presets**: Ready-to-use themes for Success, Error, Warning, and Info alerts.
7
+ - **Smart Timers**: Auto-dismiss timers that pause when the user hovers over a toast.
8
+ - **Custom Rendering**: Bypasses the default style to render custom React components and functions.
9
+ - **Transitions**: Smooth entry/exit animations (slide and fade transitions).
10
+
11
+ ---
12
+
13
+ ## 1. Setup
14
+
15
+ To use the toast notification system, place the `<ToastContainer />` at the root of your application (typically in `App.tsx` or `main.tsx`).
16
+
17
+ ```tsx
18
+ import { ToastContainer } from "@/pejay-ui/components/toast";
19
+
20
+ export default function App() {
21
+ return (
22
+ <>
23
+ {/* Your App Routing/Content */}
24
+ <ToastContainer placement="top-right" animationType="fade" />
25
+ </>
26
+ );
27
+ }
28
+ ```
29
+
30
+ ### `<ToastContainer />` Props
31
+
32
+ | Prop | Type | Default | Description |
33
+ | :--- | :--- | :--- | :--- |
34
+ | `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | The screen corner where notifications will stack. |
35
+ | `animationType` | `"fade" \| "slide"` | `"fade"` | The entrance and exit transition animation style. Can also be passed as `animation-type`. |
36
+
37
+ ---
38
+
39
+ ## 2. Usage & API
40
+
41
+ Import the `toast` function from the module:
42
+ ```ts
43
+ import { toast } from "@/pejay-ui/components/toast";
44
+ ```
45
+
46
+ ### Call Signatures
47
+
48
+ The status methods (`success`, `error`, `warning`, `info`) support two call signatures:
49
+
50
+ #### 1. Quick Message (String Only)
51
+ For displaying a single-line text message with default settings:
52
+ ```ts
53
+ toast.success("All changes saved!");
54
+ ```
55
+ You can also pass an optional configuration object as the second argument:
56
+ ```ts
57
+ toast.error("An error occurred", { duration: 5000, showClose: false });
58
+ ```
59
+
60
+ #### 2. Detailed Object Configuration
61
+ For full title and description control:
62
+ ```ts
63
+ toast.warning({
64
+ title: "Low Disk Space",
65
+ description: "You have less than 10% space remaining.",
66
+ duration: 6000
67
+ });
68
+ ```
69
+
70
+ ---
71
+
72
+ ### Shared Configuration Options (`ToastOptions`)
73
+ All toast methods accept an options object:
74
+
75
+ | Option | Type | Default | Description |
76
+ | :--- | :--- | :--- | :--- |
77
+ | `title` | `string` | — | Bold title text displayed in the toast. |
78
+ | `description` | `string` | — | Smaller detail text under the title. |
79
+ | `duration` | `number` | `4000` | Lifetime in milliseconds. Pass `Infinity` to disable auto-closing. |
80
+ | `showClose` | `boolean` | `true` | Whether to show the close cross button. |
81
+ | `dismiss` | `string` | — | An existing toast ID to dismiss before showing the new one. |
82
+ | `icon` | `React.ReactNode` | — | Custom React element to override the default status icon. |
83
+
84
+ ---
85
+
86
+ ## 3. Presets & Examples
87
+
88
+ ### A. Success Toast
89
+ Use for successful operations (submitting forms, saves, payments).
90
+ ```ts
91
+ toast.success({
92
+ title: "Payment Received",
93
+ description: "Your invoice #2093 has been paid successfully.",
94
+ duration: 4000,
95
+ });
96
+ ```
97
+
98
+ ### B. Error Toast
99
+ Use for failed requests, validation errors, or application crashes.
100
+ ```ts
101
+ toast.error({
102
+ title: "Upload Failed",
103
+ description: "The connection was lost. Please check your network and try again.",
104
+ duration: 6000,
105
+ showClose: true,
106
+ });
107
+ ```
108
+
109
+ ### C. Warning Toast
110
+ Use for alerts, soft errors, or actions requiring user attention.
111
+ ```ts
112
+ toast.warning({
113
+ title: "Unsaved Changes",
114
+ description: "Your work will be lost if you navigate away.",
115
+ duration: 5000,
116
+ });
117
+ ```
118
+
119
+ ### D. Info Toast
120
+ Use for general system notices or status updates.
121
+ ```ts
122
+ toast.info({
123
+ title: "System Maintenance",
124
+ description: "Scheduled maintenance will begin tonight at 12:00 AM EST.",
125
+ duration: Infinity, // Stays visible until manually closed
126
+ });
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 4. Custom Toasts
132
+
133
+ For completely custom designs, use `toast.custom()`. This method bypasses default styling and allows you to render any React component.
134
+
135
+ ### Custom Example with Manual Dismissal
136
+ You can pass a function to the `content` property. It receives the unique `id` of the toast, allowing you to trigger a manual dismiss from inside your custom UI:
137
+
138
+ ```tsx
139
+ toast.custom({
140
+ id: "my-custom-toast", // Optional custom string ID
141
+ duration: 10000, // 10 seconds
142
+ content: (id) => (
143
+ <div className="flex flex-col gap-3 p-4 w-full bg-slate-900 border border-violet-500/30 rounded-xl shadow-lg">
144
+ <div className="flex flex-col gap-1">
145
+ <h4 className="text-sm font-semibold text-white">Importing Contacts</h4>
146
+ <p className="text-xs text-slate-400">Please wait while we process your file.</p>
147
+ </div>
148
+ <div className="flex gap-2 justify-end">
149
+ <button
150
+ onClick={() => toast.dismiss(id)}
151
+ className="px-3 py-1 bg-violet-600 hover:bg-violet-700 text-xs font-semibold text-white rounded-md transition-colors"
152
+ >
153
+ Cancel Import
154
+ </button>
155
+ </div>
156
+ </div>
157
+ )
158
+ });
159
+ ```
160
+
161
+ ## 5. Dismissing Toasts
162
+
163
+ Toasts can be dismissed programmatically in two ways:
164
+
165
+ ### 1. By ID via `toast.dismiss()`
166
+ You can manually trigger the removal of a toast at any time by calling `toast.dismiss(id)` with the ID returned when the toast was created:
167
+ ```ts
168
+ // Trigger the toast and capture its generated ID
169
+ const toastId = toast.info("Uploading file...", { duration: Infinity });
170
+
171
+ // Dismiss it later (e.g. once the file upload succeeds)
172
+ toast.dismiss(toastId);
173
+ ```
174
+
175
+ ### 2. Auto-Dismissing when Triggering a New Toast (via `dismiss` option)
176
+ You can automatically dismiss an active toast by passing its ID in the `dismiss` option of a new toast. This is useful for transitioning states (e.g., from a loading state to a success/error state):
177
+ ```ts
178
+ // 1. Show loading toast with a fixed ID
179
+ toast.info("Saving changes...", { id: "saving-progress" });
180
+
181
+ // 2. Trigger success toast, which dismisses "saving-progress" before rendering
182
+ toast.success("All changes saved!", { dismiss: "saving-progress" });
183
+ ```
@@ -0,0 +1,320 @@
1
+ import { useEffect, useState, useRef } from "react";
2
+ import type { ToastData } from "./types";
3
+ import { toastStore } from "./store";
4
+ import {
5
+ CheckCircle,
6
+ AlertCircle,
7
+ AlertTriangle,
8
+ Info,
9
+ X,
10
+ } from "lucide-react";
11
+ import { cn } from "@/utils/cn";
12
+ import { Portal } from "../overlays";
13
+
14
+ // Dictionary mapping toast types to their respective Lucide icons and Tailwind styles
15
+ const TOAST_STYLES = {
16
+ success: {
17
+ Icon: CheckCircle,
18
+ iconColor: "text-emerald-500",
19
+ borderColor: "border-emerald-500/20",
20
+ bgGlow: "shadow-emerald-500/5",
21
+ accentColor: "bg-emerald-500",
22
+ },
23
+ error: {
24
+ Icon: AlertCircle,
25
+ iconColor: "text-red-500",
26
+ borderColor: "border-red-500/20",
27
+ bgGlow: "shadow-red-500/5",
28
+ accentColor: "bg-red-500",
29
+ },
30
+ warning: {
31
+ Icon: AlertTriangle,
32
+ iconColor: "text-amber-500",
33
+ borderColor: "border-amber-500/20",
34
+ bgGlow: "shadow-amber-500/5",
35
+ accentColor: "bg-amber-500",
36
+ },
37
+ info: {
38
+ Icon: Info,
39
+ iconColor: "text-sky-500",
40
+ borderColor: "border-sky-500/20",
41
+ bgGlow: "shadow-sky-500/5",
42
+ accentColor: "bg-sky-500",
43
+ },
44
+ } as const;
45
+
46
+ function ToastItem({
47
+ toast,
48
+ placement = "top-right",
49
+ animationType = "fade",
50
+ }: {
51
+ toast: ToastData;
52
+ placement?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
53
+ animationType?: "slide" | "fade";
54
+ }) {
55
+ const [dragOffset, setDragOffset] = useState(0);
56
+ const [isDragging, setIsDragging] = useState(false);
57
+ const [isDismissing, setIsDismissing] = useState(false);
58
+ const [animateState, setAnimateState] = useState(false);
59
+
60
+ const startXRef = useRef(0);
61
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
62
+ const isHoveringRef = useRef(false);
63
+
64
+ const isLeft = placement.endsWith("-left");
65
+ const slideOffset = isLeft ? -500 : 500;
66
+
67
+ // Helper to trigger the exit animation and remove toast from store after transition
68
+ const triggerDismiss = (customOffset?: number) => {
69
+ setIsDismissing(true);
70
+ if (customOffset !== undefined) {
71
+ setDragOffset(customOffset);
72
+ }
73
+ setTimeout(() => {
74
+ toastStore.remove(toast.id);
75
+ }, 400);
76
+ };
77
+
78
+ const startTimer = () => {
79
+ if (timerRef.current) clearTimeout(timerRef.current);
80
+ if (isHoveringRef.current) return;
81
+ const duration = toast.duration ?? 4000;
82
+ if (duration === Infinity) return;
83
+ timerRef.current = setTimeout(() => {
84
+ triggerDismiss();
85
+ }, duration);
86
+ };
87
+
88
+ const stopTimer = () => {
89
+ if (timerRef.current) {
90
+ clearTimeout(timerRef.current);
91
+ timerRef.current = null;
92
+ }
93
+ };
94
+
95
+ useEffect(() => {
96
+ startTimer();
97
+ return () => stopTimer();
98
+ }, [toast.id, toast.duration]);
99
+
100
+ // Use requestAnimationFrame to guarantee the browser registers the initial off-screen paint before triggering the transition
101
+ useEffect(() => {
102
+ let frame1: number;
103
+ let frame2: number;
104
+ frame1 = requestAnimationFrame(() => {
105
+ frame2 = requestAnimationFrame(() => {
106
+ setAnimateState(true);
107
+ });
108
+ });
109
+ return () => {
110
+ cancelAnimationFrame(frame1);
111
+ if (frame2) cancelAnimationFrame(frame2);
112
+ };
113
+ }, []);
114
+
115
+ const handleMouseEnter = () => {
116
+ isHoveringRef.current = true;
117
+ stopTimer();
118
+ };
119
+
120
+ const handleMouseLeave = () => {
121
+ isHoveringRef.current = false;
122
+ startTimer();
123
+ };
124
+
125
+ const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
126
+ if (isDismissing) return;
127
+ if (e.pointerType === "mouse" && e.button !== 0) return;
128
+
129
+ // Prevent dragging when clicking interactive elements (buttons, inputs, links, etc.)
130
+ const target = e.target as HTMLElement;
131
+ if (target.closest("button, input, select, textarea, a")) return;
132
+
133
+ setIsDragging(true);
134
+ startXRef.current = e.clientX;
135
+ stopTimer();
136
+ e.currentTarget.setPointerCapture(e.pointerId);
137
+ };
138
+
139
+ const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
140
+ if (!isDragging) return;
141
+ const offset = e.clientX - startXRef.current;
142
+ setDragOffset(offset);
143
+ };
144
+
145
+ const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
146
+ if (!isDragging) return;
147
+ setIsDragging(false);
148
+ e.currentTarget.releasePointerCapture(e.pointerId);
149
+
150
+ // Dismiss toast if dragged beyond threshold, otherwise snap back
151
+ if (Math.abs(dragOffset) > 100) {
152
+ triggerDismiss(dragOffset > 0 ? 500 : -500);
153
+ } else {
154
+ setDragOffset(0);
155
+ startTimer();
156
+ }
157
+ };
158
+
159
+ // Determine transition styles based on active animation option
160
+ const opacity = Math.max(0, 1 - Math.abs(dragOffset) / 300);
161
+
162
+ const opacityStyle = animationType === "slide"
163
+ ? (isDragging ? opacity : 1)
164
+ : (isDismissing || !animateState ? 0 : opacity);
165
+
166
+ const transformStyle = animationType === "slide"
167
+ ? (isDismissing
168
+ ? `translate3d(${dragOffset === 0 ? slideOffset : (dragOffset > 0 ? 500 : -500)}px, 0, 0)`
169
+ : (!animateState ? `translate3d(${slideOffset}px, 0, 0)` : `translate3d(${dragOffset}px, 0, 0)`))
170
+ : `translate3d(${dragOffset}px, 0, 0)`;
171
+
172
+ const itemStyle = {
173
+ transform: transformStyle,
174
+ opacity: opacityStyle,
175
+ touchAction: "none" as const,
176
+ willChange: "transform, opacity",
177
+ transition: isDragging
178
+ ? "none"
179
+ : "transform 0.4s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.4s ease-in-out",
180
+ };
181
+
182
+ // Return custom component directly, avoiding standard layout properties
183
+ if (toast.type === "custom") {
184
+ return (
185
+ <div
186
+ onPointerDown={handlePointerDown}
187
+ onPointerMove={handlePointerMove}
188
+ onPointerUp={handlePointerUp}
189
+ onPointerCancel={handlePointerUp}
190
+ onMouseEnter={handleMouseEnter}
191
+ onMouseLeave={handleMouseLeave}
192
+ style={itemStyle}
193
+ className={cn(
194
+ "relative overflow-hidden min-w-[320px] max-w-[400px] rounded-xl border backdrop-blur-md shadow-2xl flex items-start",
195
+ "select-none cursor-pointer active:cursor-grabbing",
196
+ "bg-gray-950/95 border-violet-500/20 shadow-violet-500/5",
197
+ )}
198
+ >
199
+ {typeof toast.content === "function" ? toast.content(toast.id!) : toast.content}
200
+ </div>
201
+ );
202
+ }
203
+
204
+ // Standard toast style configuration mapping
205
+ const typeKey = (toast.type && toast.type in TOAST_STYLES) ? (toast.type as keyof typeof TOAST_STYLES) : "info";
206
+ const { Icon, iconColor, borderColor, bgGlow, accentColor } = TOAST_STYLES[typeKey];
207
+
208
+ return (
209
+ <div
210
+ onPointerDown={handlePointerDown}
211
+ onPointerMove={handlePointerMove}
212
+ onPointerUp={handlePointerUp}
213
+ onPointerCancel={handlePointerUp}
214
+ onMouseEnter={handleMouseEnter}
215
+ onMouseLeave={handleMouseLeave}
216
+ style={itemStyle}
217
+ className={cn(
218
+ "relative overflow-hidden min-w-[320px] max-w-[400px] rounded-xl border backdrop-blur-md p-4 shadow-2xl flex gap-3 items-start",
219
+ "select-none cursor-pointer active:cursor-grabbing",
220
+ "bg-gray-950/95",
221
+ borderColor,
222
+ bgGlow,
223
+ )}
224
+ >
225
+ {accentColor && (
226
+ <div
227
+ className={cn("absolute left-0 top-0 bottom-0 w-[3px]", accentColor)}
228
+ />
229
+ )}
230
+
231
+ {toast.icon ? (
232
+ <div className="shrink-0 mt-0.5 ml-1 pointer-events-none flex items-center justify-center">
233
+ {toast.icon}
234
+ </div>
235
+ ) : (
236
+ <Icon
237
+ className={cn("shrink-0 mt-0.5 ml-1 pointer-events-none", iconColor)}
238
+ size={18}
239
+ />
240
+ )}
241
+
242
+ <div className="flex-1 flex flex-col gap-0.5 pointer-events-none">
243
+ {toast.message && (
244
+ <p className="text-sm font-medium text-white leading-normal pr-4">
245
+ {toast.message}
246
+ </p>
247
+ )}
248
+ {toast.title && (
249
+ <h3
250
+ className="font-semibold text-sm tracking-tight leading-tight pr-4 text-white"
251
+ >
252
+ {toast.title}
253
+ </h3>
254
+ )}
255
+ {toast.description && (
256
+ <p
257
+ className="text-xs font-medium mt-1 leading-normal text-gray-400"
258
+ >
259
+ {toast.description}
260
+ </p>
261
+ )}
262
+ </div>
263
+
264
+ {toast.showClose && (
265
+ <button
266
+ onClick={e => {
267
+ e.stopPropagation();
268
+ triggerDismiss();
269
+ }}
270
+ className="text-gray-500 hover:text-white transition-colors p-1 rounded-md hover:bg-gray-900 shrink-0 relative z-10"
271
+ >
272
+ <X size={14} />
273
+ </button>
274
+ )}
275
+ </div>
276
+ );
277
+ }
278
+
279
+ interface ToastContainerProps {
280
+ placement?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
281
+ "animation-type"?: "slide" | "fade";
282
+ animationType?: "slide" | "fade";
283
+ }
284
+
285
+ export function ToastContainer({
286
+ placement = "top-right",
287
+ "animation-type": animationTypeHyphen,
288
+ animationType = animationTypeHyphen ?? "fade",
289
+ }: ToastContainerProps) {
290
+ const [toasts, setToasts] = useState<ToastData[]>(toastStore.getToasts());
291
+
292
+ useEffect(() => {
293
+ const unsubscribe = toastStore.subscribe(setToasts);
294
+ return unsubscribe;
295
+ }, []);
296
+
297
+ const placementClasses = {
298
+ "top-right": "fixed top-4 right-4 flex-col",
299
+ "top-left": "fixed top-4 left-4 flex-col",
300
+ "bottom-right": "fixed bottom-4 right-4 flex-col-reverse",
301
+ "bottom-left": "fixed bottom-4 left-4 flex-col-reverse",
302
+ };
303
+
304
+ const containerClass = placementClasses[placement] || placementClasses["top-right"];
305
+
306
+ return (
307
+ <Portal>
308
+ <div className={cn("z-[9999] flex gap-3", containerClass)}>
309
+ {toasts.map(toast => (
310
+ <ToastItem
311
+ key={toast.id}
312
+ toast={toast}
313
+ placement={placement}
314
+ animationType={animationType}
315
+ />
316
+ ))}
317
+ </div>
318
+ </Portal>
319
+ );
320
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./container";
2
+ export * from "./toast";
3
+ export * from "./types";
4
+ export * from "./store";
@@ -0,0 +1,35 @@
1
+ import type { Listener, ToastData } from "./types";
2
+
3
+ class ToastStore {
4
+ private toasts: ToastData[] = [];
5
+ private listeners: Listener[] = [];
6
+
7
+ subscribe(listener: Listener) {
8
+ this.listeners.push(listener);
9
+ return () => {
10
+ this.listeners = this.listeners.filter((l) => l !== listener);
11
+ };
12
+ }
13
+ private publish() {
14
+ this.listeners.forEach((listener) => listener(this.toasts));
15
+ }
16
+ add(toast: ToastData) {
17
+ const newToast: ToastData = {
18
+ id: toast.id ?? crypto.randomUUID(),
19
+ ...toast,
20
+ };
21
+ this.toasts = [newToast, ...this.toasts];
22
+ this.publish();
23
+ return newToast.id!;
24
+ }
25
+ remove(id: string | undefined) {
26
+ this.toasts = this.toasts.filter((toast) => toast.id !== id);
27
+
28
+ this.publish();
29
+ }
30
+ getToasts() {
31
+ return this.toasts;
32
+ }
33
+ }
34
+
35
+ export const toastStore = new ToastStore();
@@ -0,0 +1,54 @@
1
+ import { toastStore } from "./store";
2
+ import type { ToastData } from "./types";
3
+
4
+ type ToastOptions = ToastData;
5
+ type ShortcutOptions = Omit<ToastData, "type">;
6
+
7
+ function createToast(options: ToastOptions) {
8
+ if (options.dismiss) {
9
+ toastStore.remove(options.dismiss);
10
+ }
11
+ return toastStore.add(options);
12
+ }
13
+
14
+ const handleShortcut = (
15
+ type: ToastData["type"],
16
+ messageOrOptions: string | ShortcutOptions,
17
+ options?: ShortcutOptions
18
+ ) => {
19
+ if (typeof messageOrOptions === "string") {
20
+ return createToast({ message: messageOrOptions, ...options, type });
21
+ }
22
+ return createToast({ ...messageOrOptions, type });
23
+ };
24
+
25
+ export const toast = Object.assign(
26
+ (options: ToastOptions) => {
27
+ return createToast(options);
28
+ },
29
+ {
30
+ success: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
31
+ return handleShortcut("success", messageOrOptions, options);
32
+ },
33
+
34
+ error: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
35
+ return handleShortcut("error", messageOrOptions, options);
36
+ },
37
+
38
+ info: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
39
+ return handleShortcut("info", messageOrOptions, options);
40
+ },
41
+
42
+ warning: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
43
+ return handleShortcut("warning", messageOrOptions, options);
44
+ },
45
+
46
+ custom: (options: { content: React.ReactNode | ((id: string) => React.ReactNode); id?: string; duration?: number; dismiss?: string }) => {
47
+ return createToast({ ...options, type: "custom" });
48
+ },
49
+
50
+ dismiss: (id: string) => {
51
+ toastStore.remove(id);
52
+ },
53
+ },
54
+ );
@@ -0,0 +1,15 @@
1
+ export type ToastType = "info" | "error" | "warning" | "success" | "custom";
2
+
3
+ export type Listener = (toasts: ToastData[]) => void;
4
+ export interface ToastData {
5
+ id?: string;
6
+ title?: string;
7
+ description?: string;
8
+ message?: string;
9
+ type?: ToastType;
10
+ duration?: number;
11
+ showClose?: boolean;
12
+ dismiss?: string;
13
+ icon?: React.ReactNode;
14
+ content?: React.ReactNode | ((id: string) => React.ReactNode);
15
+ }