your-toast 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,18 +15,22 @@ Modern toast notification library for React apps.
15
15
 
16
16
  ## 🚀 Features
17
17
 
18
- * 🔥 Minimal & powerful API (`toast()`)
19
- * Promise-based toast (`toast.promise`)
20
- * 🎨 Multiple variants (default, success, error, loading, action)
21
- * 📦 Stack management (auto limit)
22
- * 🔁 Update & dismiss control
23
- *Auto dismiss with duration
24
- * 🍏 Glass UI (modern design)
25
- * 🌙 Dark mode ready
18
+ - 🔥 Minimal and powerful `toast()` API
19
+ - 🎨 Multiple toast variants
20
+ - Promise-based notifications with `toast.promise()`
21
+ - 📦 Automatic toast stack management
22
+ - 🔁 Update and dismiss notifications programmatically
23
+ -Configurable auto-dismiss duration
24
+ - 🍏 Modern glass-style UI
25
+ - 🌙 Dark-mode ready
26
+ - 🎯 Action buttons
27
+ - 🔷 TypeScript support
28
+ - ⚛️ React 18+ support
29
+ - ▲ Next.js App Router friendly
26
30
 
27
31
  ---
28
32
 
29
- ## 📦 Install
33
+ ## 📦 Installation
30
34
 
31
35
  ```bash
32
36
  npm install your-toast
@@ -34,107 +38,353 @@ npm install your-toast
34
38
 
35
39
  ---
36
40
 
37
- ## 🚀 Basic Usage
41
+ ## ⚛️ React / Vite
38
42
 
39
- ```jsx
40
- "use client";
43
+ For client-side React applications such as Vite, import the provider and
44
+ toast API directly:
41
45
 
46
+ ```tsx
42
47
  import { YourToastProvider, toast } from "your-toast";
43
48
 
44
49
  export default function App() {
45
50
  return (
46
- <YourToastProvider>
51
+ <>
52
+ <YourToastProvider />
53
+
47
54
  <button onClick={() => toast("Hello from your-toast 🚀")}>
48
55
  Show Toast
49
56
  </button>
50
- </YourToastProvider>
57
+ </>
51
58
  );
52
59
  }
53
60
  ```
54
61
 
62
+ The provider only needs to be mounted once in your application.
63
+
64
+ ---
65
+
66
+ ## ▲ Next.js App Router
67
+
68
+ `your-toast` supports the Next.js App Router without making your root
69
+ layout a Client Component.
70
+
71
+ ### 1. Add the provider
72
+
73
+ Import the provider from the dedicated client entry:
74
+
75
+ ```tsx
76
+ import { YourToastProvider } from "your-toast/provider";
77
+ ```
78
+
79
+ Then add it to your root layout:
80
+
81
+ ```tsx
82
+ export default function RootLayout({
83
+ children,
84
+ }: {
85
+ children: React.ReactNode;
86
+ }) {
87
+ return (
88
+ <html lang="en">
89
+ <body>
90
+ {children}
91
+
92
+ <YourToastProvider />
93
+ </body>
94
+ </html>
95
+ );
96
+ }
97
+ ```
98
+
99
+ You do **not** need to add `"use client"` to your root `layout.tsx`.
100
+
101
+ ### 2. Trigger a toast from a Client Component
102
+
103
+ ```tsx
104
+ "use client";
105
+
106
+ import { toast } from "your-toast";
107
+
108
+ export default function SaveButton() {
109
+ return (
110
+ <button onClick={() => toast.success("Saved successfully!")}>Save</button>
111
+ );
112
+ }
113
+ ```
114
+
115
+ ### Why `your-toast/provider`?
116
+
117
+ The provider is exposed through a dedicated client entry point:
118
+
119
+ ```tsx
120
+ import { YourToastProvider } from "your-toast/provider";
121
+ ```
122
+
123
+ This keeps the Next.js Client Component boundary explicit while allowing
124
+ your root layout to remain a Server Component.
125
+
55
126
  ---
56
127
 
57
- ## 🎯 Variants
128
+ ## 🎯 Toast Variants
58
129
 
59
- ```js
130
+ ### Default
131
+
132
+ ```ts
60
133
  toast("Default message");
134
+ ```
135
+
136
+ ### Success
137
+
138
+ ```ts
139
+ toast.success("Saved successfully!");
140
+ ```
141
+
142
+ ### Error
143
+
144
+ ```ts
145
+ toast.error("Something went wrong!");
146
+ ```
147
+
148
+ ### Warning
149
+
150
+ ```ts
151
+ toast.warning("Please check your input.");
152
+ ```
153
+
154
+ ### Info
155
+
156
+ ```ts
157
+ toast.info("New update available.");
158
+ ```
159
+
160
+ ### Loading
161
+
162
+ ```ts
163
+ toast.loading("Uploading...");
164
+ ```
165
+
166
+ ---
167
+
168
+ ## ⚙️ Toast Options
169
+
170
+ Customize a toast with additional options:
171
+
172
+ ```ts
173
+ toast.success("Profile updated!", {
174
+ description: "Your profile has been saved successfully.",
175
+ duration: 3000,
176
+ });
177
+ ```
178
+
179
+ ### Available options
180
+
181
+ ```ts
182
+ {
183
+ description?: string;
184
+ duration?: number;
185
+ action?: {
186
+ label: string;
187
+ onClick: () => void;
188
+ };
189
+ }
190
+ ```
191
+
192
+ ---
61
193
 
62
- toast.success("Saved successfully");
194
+ ## 🎯 Action Toast
63
195
 
64
- toast.error("Something went wrong");
196
+ Add an interactive action to a toast:
65
197
 
66
- toast.loading("Loading...");
198
+ ```ts
199
+ toast("File deleted", {
200
+ action: {
201
+ label: "Undo",
202
+ onClick: () => {
203
+ console.log("Undo clicked");
204
+ },
205
+ },
206
+ });
67
207
  ```
68
208
 
69
209
  ---
70
210
 
71
211
  ## ⚡ Promise Toast
72
212
 
73
- ```js
213
+ Show loading, success, and error states automatically:
214
+
215
+ ```ts
74
216
  toast.promise(fetchData(), {
75
217
  loading: "Loading...",
76
218
  success: "Data loaded!",
77
- error: "Something went wrong",
219
+ error: "Something went wrong.",
220
+ });
221
+ ```
222
+
223
+ You can also generate messages dynamically from the resolved value or
224
+ error:
225
+
226
+ ```ts
227
+ toast.promise(fetchData(), {
228
+ loading: "Loading...",
229
+ success: (data) => `Loaded ${data.name}`,
230
+ error: (error) => "Failed to load data.",
231
+ });
232
+ ```
233
+
234
+ `toast.promise()` returns the original promise result, so it can still
235
+ be awaited:
236
+
237
+ ```ts
238
+ const data = await toast.promise(fetchData(), {
239
+ loading: "Loading...",
240
+ success: "Data loaded!",
241
+ error: "Failed to load data.",
78
242
  });
79
243
  ```
80
244
 
81
245
  ---
82
246
 
83
- ## 🔁 Update & Dismiss
247
+ ## 🔁 Update a Toast
248
+
249
+ Every toast returns an ID:
84
250
 
85
- ```js
251
+ ```ts
86
252
  const id = toast("Uploading...");
253
+ ```
254
+
255
+ Update it later:
87
256
 
88
- // update
257
+ ```ts
89
258
  toast.update(id, {
90
- title: "Uploaded!",
259
+ title: "Upload complete!",
91
260
  type: "success",
92
261
  });
262
+ ```
263
+
264
+ You can also update the description or duration:
265
+
266
+ ```ts
267
+ toast.update(id, {
268
+ title: "Almost done...",
269
+ description: "Processing your file.",
270
+ duration: 3000,
271
+ });
272
+ ```
273
+
274
+ ---
275
+
276
+ ## ❌ Dismiss Toasts
277
+
278
+ Dismiss a specific toast:
279
+
280
+ ```ts
281
+ const id = toast("Hello!");
93
282
 
94
- // dismiss
95
283
  toast.dismiss(id);
96
284
  ```
97
285
 
286
+ Dismiss all active toasts:
287
+
288
+ ```ts
289
+ toast.dismiss();
290
+ ```
291
+
98
292
  ---
99
293
 
100
- ## 🧩 Advanced Example
294
+ ## Duration
101
295
 
102
- ```js
103
- toast("File deleted", {
104
- action: {
105
- label: "Undo",
106
- onClick: () => console.log("Undo clicked"),
107
- },
296
+ Toasts automatically disappear after their duration:
297
+
298
+ ```ts
299
+ toast("This disappears after 2 seconds.", {
300
+ duration: 2000,
301
+ });
302
+ ```
303
+
304
+ Loading toasts remain visible until they are updated or dismissed:
305
+
306
+ ```ts
307
+ const id = toast.loading("Uploading...");
308
+
309
+ // Later
310
+ toast.update(id, {
311
+ title: "Upload complete!",
312
+ type: "success",
108
313
  });
109
314
  ```
110
315
 
111
316
  ---
112
317
 
318
+ ## 🔷 TypeScript
319
+
320
+ `your-toast` is written with TypeScript and provides built-in type
321
+ definitions.
322
+
323
+ The package includes typed APIs for:
324
+
325
+ - Toast variants
326
+ - Toast options
327
+ - Toast actions
328
+ - Promise messages
329
+ - Toast updates
330
+
331
+ Example:
332
+
333
+ ```ts
334
+ toast.success("Success!", {
335
+ description: "Everything went well.",
336
+ duration: 3000,
337
+ });
338
+ ```
339
+
340
+ Your editor will provide autocomplete and type checking automatically.
341
+
342
+ ---
343
+
113
344
  ## 🎨 Supported Types
114
345
 
115
- * `default`
116
- * `success`
117
- * `error`
118
- * `loading`
119
- * `action`
346
+ Type API
347
+
348
+ ---
349
+
350
+ Default `toast()`
351
+ Success `toast.success()`
352
+ Error `toast.error()`
353
+ Warning `toast.warning()`
354
+ Info `toast.info()`
355
+ Loading `toast.loading()`
356
+ Action `toast()` with `action`
120
357
 
121
358
  ---
122
359
 
123
360
  ## 🛠 Developer Friendly
124
361
 
125
- * Simple and predictable API
126
- * No extra setup required
127
- * Works with modern React (18+)
128
- * Lightweight and customizable
362
+ - Simple and predictable API
363
+ - Minimal setup
364
+ - No external UI dependencies
365
+ - Built-in TypeScript definitions
366
+ - React 18+ compatible
367
+ - React 19 compatible
368
+ - Next.js App Router friendly
369
+ - Vite friendly
370
+ - ESM + CommonJS builds
371
+ - Lightweight package
129
372
 
130
373
  ---
131
374
 
132
- ## 🚧 Roadmap
375
+ ## 🗺 Roadmap
133
376
 
134
- * 🍏 Advanced glass UI polish
135
- * 🎞 Animation improvements
136
- * 🎨 Theme system
137
- * 📱 Mobile UX optimization
377
+ - 🍏 Advanced glass UI polish
378
+ - 🎞 Improved animations
379
+ - 🎨 Theme system
380
+ - 📱 Mobile UX optimization
381
+ - 📍 Toast positioning system
382
+ - 📊 Progress indicators
383
+ - ⏸ Pause on hover / focus
384
+ - 👆 Swipe-to-dismiss
385
+ - 🔔 Custom icons
386
+ - 🌐 RTL support
387
+ - ⚛️ Custom React content
138
388
 
139
389
  ---
140
390
 
@@ -151,4 +401,4 @@ Masaud Ahmod
151
401
  ## ⭐ Support
152
402
 
153
403
  If you find this useful, consider giving a ⭐ on GitHub:
154
- 👉 https://github.com/your-username/your-toast
404
+ 👉 https://github.com/masaudahmod/your-toast
@@ -0,0 +1,251 @@
1
+ // src/components/YourToastProvider.tsx
2
+ import { useEffect } from "react";
3
+
4
+ // src/hooks/useToast.ts
5
+ import { useSyncExternalStore } from "react";
6
+
7
+ // src/store/toastStore.ts
8
+ var toasts = [];
9
+ var listeners = /* @__PURE__ */ new Set();
10
+ function emit() {
11
+ listeners.forEach((listener) => listener());
12
+ }
13
+ var toastStore = {
14
+ subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => {
17
+ listeners.delete(listener);
18
+ };
19
+ },
20
+ getSnapshot() {
21
+ return toasts;
22
+ },
23
+ getServerSnapshot() {
24
+ return [];
25
+ },
26
+ add(toast) {
27
+ toasts = [toast, ...toasts].slice(0, 5);
28
+ emit();
29
+ },
30
+ remove(id) {
31
+ toasts = toasts.filter((toast) => toast.id !== id);
32
+ emit();
33
+ },
34
+ update(id, updated) {
35
+ toasts = toasts.map(
36
+ (toast) => toast.id === id ? { ...toast, ...updated } : toast
37
+ );
38
+ emit();
39
+ },
40
+ clear() {
41
+ toasts = [];
42
+ emit();
43
+ },
44
+ getToasts() {
45
+ return toasts;
46
+ }
47
+ };
48
+
49
+ // src/hooks/useToast.ts
50
+ function useToast() {
51
+ const toasts2 = useSyncExternalStore(
52
+ toastStore.subscribe,
53
+ toastStore.getSnapshot,
54
+ toastStore.getServerSnapshot
55
+ );
56
+ return { toasts: toasts2 };
57
+ }
58
+
59
+ // src/components/YourToastProvider.tsx
60
+ import { jsx, jsxs } from "react/jsx-runtime";
61
+ function YourToastProvider() {
62
+ const { toasts: toasts2 } = useToast();
63
+ useEffect(() => {
64
+ if (typeof document === "undefined") return;
65
+ const styleId = "your-toast-styles";
66
+ if (document.getElementById(styleId)) {
67
+ return;
68
+ }
69
+ const style = document.createElement("style");
70
+ style.id = styleId;
71
+ style.textContent = `
72
+ @keyframes yt-slide-in {
73
+ from {
74
+ opacity: 0;
75
+ transform: translate3d(20px, -10px, 0) scale(0.96);
76
+ }
77
+
78
+ to {
79
+ opacity: 1;
80
+ transform: translate3d(0, 0, 0) scale(1);
81
+ }
82
+ }
83
+
84
+ @keyframes yt-slide-out {
85
+ from {
86
+ opacity: 1;
87
+ transform: translate3d(0, 0, 0) scale(1);
88
+ }
89
+
90
+ to {
91
+ opacity: 0;
92
+ transform: translate3d(20px, -10px, 0) scale(0.96);
93
+ }
94
+ }
95
+
96
+ @media (prefers-reduced-motion: reduce) {
97
+ .yt-toast {
98
+ animation: none !important;
99
+ }
100
+ }
101
+ `;
102
+ document.head.appendChild(style);
103
+ return () => {
104
+ style.remove();
105
+ };
106
+ }, []);
107
+ return /* @__PURE__ */ jsx(
108
+ "div",
109
+ {
110
+ "aria-live": "polite",
111
+ "aria-atomic": "false",
112
+ style: containerStyle,
113
+ children: toasts2.map((toast) => /* @__PURE__ */ jsxs(
114
+ "div",
115
+ {
116
+ className: "yt-toast",
117
+ role: toast.type === "error" ? "alert" : "status",
118
+ style: {
119
+ ...toastStyle,
120
+ ...getVariantStyle(toast.type)
121
+ },
122
+ children: [
123
+ /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
124
+ toast.title && /* @__PURE__ */ jsx("span", { style: titleStyle, children: toast.title }),
125
+ toast.description && /* @__PURE__ */ jsx("span", { style: descriptionStyle, children: toast.description })
126
+ ] }),
127
+ toast.action && /* @__PURE__ */ jsx(
128
+ "button",
129
+ {
130
+ type: "button",
131
+ onClick: toast.action.onClick,
132
+ style: actionBtn,
133
+ children: toast.action.label
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx(
137
+ "button",
138
+ {
139
+ type: "button",
140
+ "aria-label": "Dismiss notification",
141
+ onClick: () => toastStore.remove(toast.id),
142
+ style: closeBtn,
143
+ children: "\xD7"
144
+ }
145
+ )
146
+ ]
147
+ },
148
+ toast.id
149
+ ))
150
+ }
151
+ );
152
+ }
153
+ function getVariantStyle(type) {
154
+ switch (type) {
155
+ case "success":
156
+ return {
157
+ background: "linear-gradient(135deg, rgba(22,163,74,.9), rgba(21,128,61,.8))"
158
+ };
159
+ case "error":
160
+ return {
161
+ background: "linear-gradient(135deg, rgba(220,38,38,.9), rgba(185,28,28,.8))"
162
+ };
163
+ case "loading":
164
+ return {
165
+ background: "linear-gradient(135deg, rgba(37,99,235,.9), rgba(29,78,216,.8))"
166
+ };
167
+ case "action":
168
+ return {
169
+ background: "linear-gradient(135deg, rgba(124,58,237,.9), rgba(109,40,217,.8))"
170
+ };
171
+ default:
172
+ return {
173
+ background: "rgba(20,20,25,.82)"
174
+ };
175
+ }
176
+ }
177
+ var containerStyle = {
178
+ position: "fixed",
179
+ top: "20px",
180
+ right: "20px",
181
+ zIndex: 99999,
182
+ display: "flex",
183
+ flexDirection: "column",
184
+ alignItems: "flex-end",
185
+ gap: "10px",
186
+ width: "min(420px, calc(100vw - 40px))",
187
+ pointerEvents: "none"
188
+ };
189
+ var toastStyle = {
190
+ width: "100%",
191
+ boxSizing: "border-box",
192
+ display: "flex",
193
+ alignItems: "center",
194
+ gap: "12px",
195
+ padding: "14px 16px",
196
+ borderRadius: "16px",
197
+ color: "#fff",
198
+ border: "1px solid rgba(255,255,255,.14)",
199
+ boxShadow: "0 12px 40px rgba(0,0,0,.28)",
200
+ backdropFilter: "blur(18px)",
201
+ WebkitBackdropFilter: "blur(18px)",
202
+ animation: "yt-slide-in .3s cubic-bezier(.16,1,.3,1)",
203
+ pointerEvents: "auto"
204
+ };
205
+ var contentStyle = {
206
+ flex: 1,
207
+ display: "flex",
208
+ flexDirection: "column",
209
+ minWidth: 0,
210
+ gap: "3px"
211
+ };
212
+ var titleStyle = {
213
+ fontSize: "14px",
214
+ fontWeight: 600,
215
+ lineHeight: 1.4
216
+ };
217
+ var descriptionStyle = {
218
+ fontSize: "13px",
219
+ lineHeight: 1.4,
220
+ opacity: 0.75
221
+ };
222
+ var closeBtn = {
223
+ flexShrink: 0,
224
+ width: "28px",
225
+ height: "28px",
226
+ display: "grid",
227
+ placeItems: "center",
228
+ border: "none",
229
+ borderRadius: "8px",
230
+ background: "rgba(255,255,255,.08)",
231
+ color: "#fff",
232
+ cursor: "pointer",
233
+ fontSize: "18px",
234
+ lineHeight: 1
235
+ };
236
+ var actionBtn = {
237
+ flexShrink: 0,
238
+ border: "1px solid rgba(255,255,255,.12)",
239
+ borderRadius: "8px",
240
+ padding: "7px 10px",
241
+ background: "rgba(255,255,255,.1)",
242
+ color: "#fff",
243
+ cursor: "pointer",
244
+ fontSize: "12px",
245
+ fontWeight: 500
246
+ };
247
+
248
+ export {
249
+ toastStore,
250
+ YourToastProvider
251
+ };