your-toast 0.1.0 → 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
@@ -1,5 +1,8 @@
1
1
  # your-toast 🍞
2
2
 
3
+ ![npm](https://img.shields.io/npm/v/your-toast)
4
+ ![GitHub stars](https://img.shields.io/github/stars/masaudahmod/your-toast)
5
+
3
6
  Modern toast notification library for React apps.
4
7
 
5
8
  ✨ Clean & intuitive API
@@ -12,18 +15,22 @@ Modern toast notification library for React apps.
12
15
 
13
16
  ## 🚀 Features
14
17
 
15
- * 🔥 Minimal & powerful API (`toast()`)
16
- * Promise-based toast (`toast.promise`)
17
- * 🎨 Multiple variants (default, success, error, loading, action)
18
- * 📦 Stack management (auto limit)
19
- * 🔁 Update & dismiss control
20
- *Auto dismiss with duration
21
- * 🍏 Glass UI (modern design)
22
- * 🌙 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
23
30
 
24
31
  ---
25
32
 
26
- ## 📦 Install
33
+ ## 📦 Installation
27
34
 
28
35
  ```bash
29
36
  npm install your-toast
@@ -31,107 +38,353 @@ npm install your-toast
31
38
 
32
39
  ---
33
40
 
34
- ## 🚀 Basic Usage
41
+ ## ⚛️ React / Vite
35
42
 
36
- ```jsx
37
- "use client";
43
+ For client-side React applications such as Vite, import the provider and
44
+ toast API directly:
38
45
 
46
+ ```tsx
39
47
  import { YourToastProvider, toast } from "your-toast";
40
48
 
41
49
  export default function App() {
42
50
  return (
43
- <YourToastProvider>
51
+ <>
52
+ <YourToastProvider />
53
+
44
54
  <button onClick={() => toast("Hello from your-toast 🚀")}>
45
55
  Show Toast
46
56
  </button>
47
- </YourToastProvider>
57
+ </>
58
+ );
59
+ }
60
+ ```
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>
48
111
  );
49
112
  }
50
113
  ```
51
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
+
52
126
  ---
53
127
 
54
- ## 🎯 Variants
128
+ ## 🎯 Toast Variants
55
129
 
56
- ```js
130
+ ### Default
131
+
132
+ ```ts
57
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
+ ```
58
159
 
59
- toast.success("Saved successfully");
160
+ ### Loading
60
161
 
61
- toast.error("Something went wrong");
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
+ ```
62
178
 
63
- toast.loading("Loading...");
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
+ ---
193
+
194
+ ## 🎯 Action Toast
195
+
196
+ Add an interactive action to a toast:
197
+
198
+ ```ts
199
+ toast("File deleted", {
200
+ action: {
201
+ label: "Undo",
202
+ onClick: () => {
203
+ console.log("Undo clicked");
204
+ },
205
+ },
206
+ });
64
207
  ```
65
208
 
66
209
  ---
67
210
 
68
211
  ## ⚡ Promise Toast
69
212
 
70
- ```js
213
+ Show loading, success, and error states automatically:
214
+
215
+ ```ts
216
+ toast.promise(fetchData(), {
217
+ loading: "Loading...",
218
+ success: "Data loaded!",
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
71
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(), {
72
239
  loading: "Loading...",
73
240
  success: "Data loaded!",
74
- error: "Something went wrong",
241
+ error: "Failed to load data.",
75
242
  });
76
243
  ```
77
244
 
78
245
  ---
79
246
 
80
- ## 🔁 Update & Dismiss
247
+ ## 🔁 Update a Toast
81
248
 
82
- ```js
249
+ Every toast returns an ID:
250
+
251
+ ```ts
83
252
  const id = toast("Uploading...");
253
+ ```
84
254
 
85
- // update
255
+ Update it later:
256
+
257
+ ```ts
86
258
  toast.update(id, {
87
- title: "Uploaded!",
259
+ title: "Upload complete!",
88
260
  type: "success",
89
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!");
90
282
 
91
- // dismiss
92
283
  toast.dismiss(id);
93
284
  ```
94
285
 
286
+ Dismiss all active toasts:
287
+
288
+ ```ts
289
+ toast.dismiss();
290
+ ```
291
+
95
292
  ---
96
293
 
97
- ## 🧩 Advanced Example
294
+ ## Duration
98
295
 
99
- ```js
100
- toast("File deleted", {
101
- action: {
102
- label: "Undo",
103
- onClick: () => console.log("Undo clicked"),
104
- },
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",
105
313
  });
106
314
  ```
107
315
 
108
316
  ---
109
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
+
110
344
  ## 🎨 Supported Types
111
345
 
112
- * `default`
113
- * `success`
114
- * `error`
115
- * `loading`
116
- * `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`
117
357
 
118
358
  ---
119
359
 
120
360
  ## 🛠 Developer Friendly
121
361
 
122
- * Simple and predictable API
123
- * No extra setup required
124
- * Works with modern React (18+)
125
- * 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
126
372
 
127
373
  ---
128
374
 
129
- ## 🚧 Roadmap
375
+ ## 🗺 Roadmap
130
376
 
131
- * 🍏 Advanced glass UI polish
132
- * 🎞 Animation improvements
133
- * 🎨 Theme system
134
- * 📱 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
135
388
 
136
389
  ---
137
390
 
@@ -144,3 +397,8 @@ MIT
144
397
  ## 👨‍💻 Author
145
398
 
146
399
  Masaud Ahmod
400
+
401
+ ## ⭐ Support
402
+
403
+ If you find this useful, consider giving a ⭐ on GitHub:
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
+ };