@wello-dev/notifications 0.0.0-stage → 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,47 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wello-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ --------------------------------------------------------------------------------
24
+
25
+ Portions of this package are derived from software under this licence:
26
+
27
+ MIT License
28
+
29
+ Copyright (c) 2021 Vitaly Rtishchev
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy
32
+ of this software and associated documentation files (the "Software"), to deal
33
+ in the Software without restriction, including without limitation the rights
34
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ copies of the Software, and to permit persons to whom the Software is
36
+ furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,169 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @wello-dev/notifications
2
+
3
+ Toasts for React with a sonner-style API, plus tab title / favicon badges and notification sounds.
4
+ No dependencies besides React. RTL, Arabic labels, light and dark themes built in; the CSS is injected on import.
5
+
6
+ ```bash
7
+ pnpm add @wello-dev/notifications
8
+ ```
9
+
10
+ ```tsx
11
+ import { toast, Toaster } from "@wello-dev/notifications";
12
+
13
+ function App() {
14
+ return (
15
+ <>
16
+ <Routes />
17
+ <Toaster />
18
+ </>
19
+ );
20
+ }
21
+
22
+ toast.success("Product saved");
23
+ ```
24
+
25
+ ## `toast`
26
+
27
+ ```ts
28
+ toast("Event created");
29
+ toast.success("Saved");
30
+ toast.error("Upload failed", { description: "The file is larger than 5 MB" });
31
+ toast.warning("Stock is low");
32
+ toast.info("A new version is available", { title: "Update" }); // message becomes the description
33
+ toast.danger("…"); // same as error
34
+
35
+ // Loading, then the result in the same toast
36
+ const id = toast.loading("Sending…");
37
+ toast.success("Sent", { id });
38
+
39
+ // Promise: returns the promise's value (and rethrows its error)
40
+ const order = await toast.promise(saveOrder(), {
41
+ loading: "Saving…",
42
+ success: (order) => `Order #${order.id} saved`,
43
+ error: (error) => (error instanceof Error ? error.message : "Could not save"),
44
+ });
45
+
46
+ toast("Order deleted", { action: { label: "Undo", onClick: restore }, cancel: { label: "Dismiss" } });
47
+ toast.custom((t) => <MyCard onClose={() => toast.dismiss(t.id)} />);
48
+
49
+ toast.update(id, { description: "2 of 3 files" });
50
+ toast.dismiss(id); // or toast.close(id); without an id closes all
51
+ toast.clear();
52
+ toast.pauseAll(); // e.g. while a dialog is open
53
+ toast.resumeAll();
54
+ toast.isActive(id);
55
+ ```
56
+
57
+ | Option | Type | Default | |
58
+ | ----------------------------------------------- | ------------------------------------ | ------------------------- | ---------------------------------------------------------- |
59
+ | `id` | `string \| number` | generated | Reusing an id replaces that toast |
60
+ | `title` / `description` | `ReactNode` | message / – | |
61
+ | `duration` | `number \| false` | per type | Alias: `autoClose`. `false` stays until closed |
62
+ | `icon` | `ReactNode \| false` | per type | |
63
+ | `color` | `string` | per type | Tailwind name (`'violet-600'`) or any CSS color |
64
+ | `action` / `cancel` | `{ label, onClick?, closeOnClick? }` | – | Buttons; they close the toast unless `closeOnClick: false` |
65
+ | `dismissible` | `boolean` | `true` (loading: `false`) | Close button, swipe and Esc |
66
+ | `progress` | `boolean` | toaster | Remaining-time bar |
67
+ | `important` | `boolean` | `true` for errors | `role="alert"`, announced right away |
68
+ | `sound` | `boolean \| string` | toaster | `true` for a tone, or a sound URL |
69
+ | `position` | `ToastPosition` | toaster | |
70
+ | `className` / `style` / `classNames` / `styles` | | | See below |
71
+ | `onOpen` / `onClose` / `onAutoClose` | `(toast) => void` | | |
72
+
73
+ ## `<Toaster>`
74
+
75
+ Mount one `<Toaster />` near the app root. If multiple instances are mounted, only the first
76
+ displays notifications; another takes over if it unmounts.
77
+
78
+ | Prop | Default | |
79
+ | ------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------- |
80
+ | `position` | `'bottom-start'` | `top-/bottom-` + `start`, `center`, `end` (or `left` / `right`) |
81
+ | `dir` | `'auto'` | `'auto'` follows `<html dir>`, also when it changes |
82
+ | `limit` | `4` | Shown at once per position; newer ones wait |
83
+ | `duration` | `4000` | |
84
+ | `durations` | error 5500, warning 5000, loading `false` | Per type |
85
+ | `colors` | emerald-600, red-600, amber-600, blue-600, slate-500 | Per type |
86
+ | `icons` | built in | Per type; `null` hides one |
87
+ | `closeButton` | `true` | |
88
+ | `progress` | `false` | |
89
+ | `swipe` | `true` | Swipe sideways to dismiss |
90
+ | `pauseOnHover` | `true` | Also while a toast has focus |
91
+ | `pauseWhenPageHidden` | `true` | |
92
+ | `sound` | `false` | For every toast |
93
+ | `theme` | light | Dark under `.dark` / `[data-theme="dark"]`; or `'dark'`, `'system'` |
94
+ | `width` / `gap` / `offset` / `zIndex` | `390` / `12` / `16` / `11000` | |
95
+ | `locale` / `labels` | `'en'` | `'ar'` for Arabic |
96
+ | `classNames` / `styles` | | Parts below, plus `toaster` |
97
+ | `container` | `document.body` | |
98
+
99
+ `start` and `end` follow the direction, so `bottom-start` is bottom-left in English and bottom-right in Arabic.
100
+
101
+ ## Styling
102
+
103
+ Parts: `toaster`, `toast`, `icon`, `content`, `title`, `description`, `actions`, `action`, `cancel`, `closeButton`, `progress`.
104
+
105
+ ```tsx
106
+ <Toaster
107
+ classNames={{ toast: "rounded-xl shadow-lg", title: "text-base", closeButton: "text-gray-400" }}
108
+ />;
109
+ toast.success("Saved", { classNames: { title: "font-bold" } });
110
+ ```
111
+
112
+ The CSS is in the `components` cascade layer, so Tailwind utilities override it without `!`.
113
+ Theme variables on `.wello-notifications-toaster`: `--wello-notifications-bg`, `--wello-notifications-fg`, `--wello-notifications-muted`, `--wello-notifications-border`, `--wello-notifications-ring`,
114
+ `--wello-notifications-hover`, `--wello-notifications-shadow`, `--wello-notifications-radius`, `--wello-notifications-font`; the accent of each toast is `--wello-notifications-tone`.
115
+
116
+ If your bundler drops the injected CSS, import it yourself: `import "@wello-dev/notifications/styles.css";`.
117
+
118
+ ## Tab title and favicon
119
+
120
+ ```ts
121
+ import { tabNotifications, createTabNotifications } from "@wello-dev/notifications";
122
+
123
+ tabNotifications.setUnreadCount(5); // "(5) My app" and a red dot on the favicon
124
+ tabNotifications.flashTitle("🔴 New order"); // until the user comes back to the tab
125
+ tabNotifications.stopFlashing();
126
+ tabNotifications.reset();
127
+
128
+ const tab = createTabNotifications({
129
+ dotColor: "#2563eb",
130
+ formatTitle: (n, t) => `${n} new · ${t}`,
131
+ });
132
+ ```
133
+
134
+ ## Sounds
135
+
136
+ Built-in sounds are synthesized in the browser (no files to host): layered bell / marimba tones
137
+ with a soft attack, a short room reverb and a limiter, balanced to the same loudness.
138
+
139
+ | Name | Sound | Used for toast type |
140
+ | ---------- | -------------------------------- | ------------------- |
141
+ | `success` | rising arpeggio ending on a bell | `success` |
142
+ | `error` | soft falling minor step | `error` |
143
+ | `warning` | two even mallet knocks | `warning` |
144
+ | `info` | single bell with its octave | `info` |
145
+ | `message` | chat-style "ding-dong" | – |
146
+ | `critical` | three quick rising pairs | – |
147
+ | `pop` | short bubble pop | plain `toast()` |
148
+
149
+ ```ts
150
+ import { playNotificationSound, playSound } from "@wello-dev/notifications";
151
+
152
+ playSound("message");
153
+ playSound("critical", { volume: 1 });
154
+ playNotificationSound("/sounds/notification.mp3"); // a file, or a built-in name
155
+ playNotificationSound("/sounds/alert.mp3", { volume: 0.5, fallback: "critical" }); // if the file can't play
156
+
157
+ toast.success("Order received", { sound: true }); // built-in sound of the type
158
+ toast("New message", { sound: "message" });
159
+ <Toaster sound />; // every toast
160
+ ```
161
+
162
+ `playTone(priority)` still works: `critical` → `critical`, `high` → `message`, `normal` → `info`, `low` → `pop`.
163
+ `scheduleSound(ctx, name)` renders a sound on any `AudioContext` / `OfflineAudioContext`.
164
+
165
+ Browsers block audio until the user has interacted with the page once.
166
+
167
+ ## Your own UI
168
+
169
+ `useToasts()` returns the open toasts (newest first), e.g. for a badge. `toastStore` exposes the raw state.