dynaura 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +422 -2
- package/dist/chunk-VCDDW4W7.js +563 -0
- package/dist/index.cjs +619 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +39 -0
- package/dist/styles.css +161 -0
- package/dist/svelte.cjs +595 -0
- package/dist/svelte.d.cts +10 -0
- package/dist/svelte.d.ts +10 -0
- package/dist/svelte.js +15 -0
- package/dist/vanilla.cjs +589 -0
- package/dist/vanilla.d.cts +86 -0
- package/dist/vanilla.d.ts +86 -0
- package/dist/vanilla.js +9 -0
- package/dist/vue.cjs +613 -0
- package/dist/vue.d.cts +43 -0
- package/dist/vue.d.ts +43 -0
- package/dist/vue.js +33 -0
- package/package.json +131 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dynaura
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,423 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# dynaura
|
|
4
|
+
|
|
5
|
+
**Dynamic Island toasts for the web.**<br />
|
|
6
|
+
One pill that spring-morphs between states, swipes away, and stays out of your way.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/dynaura)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](#typescript)
|
|
11
|
+
[](./package.json)
|
|
12
|
+
|
|
13
|
+
[**Documentation**](https://dynaura.ashutoshswamy.in) · [GitHub](https://github.com/ashutoshswamy/dynaura) · [Report a bug](https://github.com/ashutoshswamy/dynaura/issues)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<br />
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
dynaura.success({ title: "Saved", description: "Your changes are live." });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That is the whole setup: mount one `<Toaster />`, call `dynaura` from anywhere.
|
|
24
|
+
|
|
25
|
+
## Contents
|
|
26
|
+
|
|
27
|
+
[Why dynaura](#why-dynaura) · [Install](#install) · [Quick start](#quick-start) · [Recipes](#recipes) · [API](#api) · [Gestures](#gestures) · [Theming](#theming) · [Accessibility](#accessibility) · [Browser support](#browser-support) · [Troubleshooting](#troubleshooting) · [Development](#development) · [Author](#author)
|
|
28
|
+
|
|
29
|
+
## Why dynaura
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| **One island, real motion** | A single pill morphs between compact and expanded using a CSS `linear()` spring. No animation library. |
|
|
34
|
+
| **Rich cards** | `variant: "card"` turns the toast into a header tab plus any custom body, joined by a curved notch. Flight passes, ride trackers, receipts. |
|
|
35
|
+
| **Live activities** | Sticky toasts with a running timer or an animated waveform. |
|
|
36
|
+
| **Progress that feels alive** | A value bar and rolling digits, updated in place with `dynaura.update`. |
|
|
37
|
+
| **Built for touch** | Swipe to dismiss, tap or long-press to pin, hover to pause. |
|
|
38
|
+
| **Themeable** | Dark, light, system, or your own colors. Everything is a CSS variable. |
|
|
39
|
+
| **Any framework** | React, Vue, Svelte, or plain JS, all on one shared core. |
|
|
40
|
+
| **Light and accessible** | Zero dependencies, typed, `aria-live`, and `prefers-reduced-motion` respected. |
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm install dynaura
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
Mount **one** `Toaster`, import the stylesheet once, then call `dynaura` from anywhere.
|
|
51
|
+
|
|
52
|
+
### React
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { Toaster, dynaura } from "dynaura";
|
|
56
|
+
import "dynaura/styles.css";
|
|
57
|
+
|
|
58
|
+
export default function App() {
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<Toaster />
|
|
62
|
+
<button
|
|
63
|
+
onClick={() =>
|
|
64
|
+
dynaura.success({
|
|
65
|
+
title: "Saved",
|
|
66
|
+
description: "Your changes are live.",
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
Save
|
|
71
|
+
</button>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Works with Next.js App Router: the bundle ships with `"use client"`, so render `<Toaster />` from a client component or your root layout.
|
|
78
|
+
|
|
79
|
+
<details>
|
|
80
|
+
<summary><b>Vue</b></summary>
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<script setup>
|
|
84
|
+
import { Toaster, dynaura } from "dynaura/vue";
|
|
85
|
+
import "dynaura/styles.css";
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<Toaster position="top" theme="system" />
|
|
90
|
+
<button @click="dynaura.success('Saved')">Save</button>
|
|
91
|
+
</template>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
</details>
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary><b>Svelte</b></summary>
|
|
98
|
+
|
|
99
|
+
```svelte
|
|
100
|
+
<script>
|
|
101
|
+
import { toaster, dynaura } from "dynaura/svelte";
|
|
102
|
+
import "dynaura/styles.css";
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<div use:toaster={{ position: "top", theme: "system" }} />
|
|
106
|
+
<button on:click={() => dynaura.success("Saved")}>Save</button>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
</details>
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><b>Vanilla JS</b></summary>
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
import { createToaster, dynaura } from "dynaura/vanilla";
|
|
116
|
+
import "dynaura/styles.css";
|
|
117
|
+
|
|
118
|
+
const toaster = createToaster({ position: "top" });
|
|
119
|
+
dynaura.info("Hello");
|
|
120
|
+
// toaster.destroy() to unmount
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
</details>
|
|
124
|
+
|
|
125
|
+
## Recipes
|
|
126
|
+
|
|
127
|
+
### Success, error and actions
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
dynaura.error({
|
|
131
|
+
title: "Payment failed",
|
|
132
|
+
description: "Card ending 4242 was declined.",
|
|
133
|
+
button: { title: "Retry", onClick: retry },
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The island expands to show the description and button, then collapses before it dismisses. A `button` click dismisses the toast.
|
|
138
|
+
|
|
139
|
+
### Promises
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
dynaura.promise(save(), {
|
|
143
|
+
loading: "Saving…",
|
|
144
|
+
success: (data) => ({ title: "Saved", description: `Version ${data.v}` }),
|
|
145
|
+
error: { title: "Failed", description: "Check your connection." },
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The same island morphs from loading to success or error. `success` and `error` can be strings, option objects, or functions of the result. The original promise is returned.
|
|
150
|
+
|
|
151
|
+
### Progress
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const id = dynaura.loading({ title: "Uploading", progress: 0 });
|
|
155
|
+
|
|
156
|
+
onProgress((p) => dynaura.update(id, { progress: p })); // 0 to 1
|
|
157
|
+
|
|
158
|
+
dynaura.update(id, {
|
|
159
|
+
type: "success",
|
|
160
|
+
title: "Upload complete",
|
|
161
|
+
progress: undefined, // clear the bar
|
|
162
|
+
duration: 4000,
|
|
163
|
+
confetti: true,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Frequent `progress` updates do not restart the toast.
|
|
168
|
+
|
|
169
|
+
### Live activities
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
dynaura.live({
|
|
173
|
+
title: "Recording voice memo",
|
|
174
|
+
button: { title: "Stop", onClick: stop },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
dynaura.live({ title: "Now playing", kind: "wave" }); // animated waveform instead of a timer
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Rich cards
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
dynaura.info({
|
|
184
|
+
title: "Boarding soon",
|
|
185
|
+
trailing: "40 min",
|
|
186
|
+
variant: "card",
|
|
187
|
+
duration: 9000,
|
|
188
|
+
description: <BoardingPass />, // any React node (or a DOM Node in Vue, Svelte and vanilla)
|
|
189
|
+
button: { title: "Show boarding pass", onClick: openPass },
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Card content inherits the toast colors through `var(--dynaura-fg)`, `var(--dynaura-muted)` and `var(--state)`, so it follows any theme. It looks best with `theme="light"`. The header tab follows `position`: centered for `top` and `bottom`, flush to the edge for `-left` and `-right`.
|
|
194
|
+
|
|
195
|
+
### Queue
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// show toasts one after another instead of replacing the current one
|
|
199
|
+
<Toaster queue />
|
|
200
|
+
|
|
201
|
+
// or per toast
|
|
202
|
+
dynaura.info({ title: "Invite sent to Maya", queue: true, duration: 1600 });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Custom icons and trailing slots
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { CalendarDaysIcon } from "@heroicons/react/24/outline";
|
|
209
|
+
|
|
210
|
+
dynaura.info({
|
|
211
|
+
title: "Design sync starts soon",
|
|
212
|
+
icon: <CalendarDaysIcon width={16} height={16} />, // replaces the built-in icon
|
|
213
|
+
trailing: "5 min", // small right-side slot
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## API
|
|
218
|
+
|
|
219
|
+
### `dynaura`
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
223
|
+
| `success(opts)` `error(opts)` `warning(opts)` `info(opts)` | Show a toast. `opts` is a string (title only) or an options object. Returns the toast id. |
|
|
224
|
+
| `loading(opts)` | Sticky toast with a spinner. |
|
|
225
|
+
| `live(opts)` | Live activity: sticky, with a running timer (default) or a waveform (`kind: "wave"`). |
|
|
226
|
+
| `promise(promise, { loading, success, error })` | Morphs loading into success or error. Returns the original promise. |
|
|
227
|
+
| `update(id, patch)` | Patch a toast in place, including `type`. Also works on queued toasts. |
|
|
228
|
+
| `dismiss(id?)` | Dismiss the current toast (the next queued one shows) or a specific id. |
|
|
229
|
+
| `clear()` | Dismiss everything, including the queue. |
|
|
230
|
+
|
|
231
|
+
### Toast options
|
|
232
|
+
|
|
233
|
+
| Option | Type | Default | Description |
|
|
234
|
+
| ------------- | ----------------------------- | ----------------- | ----------------------------------------------------------------------- |
|
|
235
|
+
| `title` | `string` | required | Main text. |
|
|
236
|
+
| `description` | `string \| Node \| ReactNode` | | Shown when the island expands. |
|
|
237
|
+
| `icon` | same | built-in | Replaces the leading icon. |
|
|
238
|
+
| `trailing` | same | | Small right-side slot, e.g. `"2 min"`. |
|
|
239
|
+
| `duration` | `number \| null` | `4000` | Milliseconds. `null` is sticky. `loading` and `live` default to `null`. |
|
|
240
|
+
| `button` | `{ title, onClick }` | | Action button. The toast dismisses after the click. |
|
|
241
|
+
| `progress` | `number` (0 to 1) | | Value bar plus rolling-digit percent. |
|
|
242
|
+
| `confetti` | `boolean` | `false` | Confetti burst when shown. Skipped under reduced motion. |
|
|
243
|
+
| `kind` | `"timer" \| "wave"` | `"timer"` | Flavor of a `live` toast. |
|
|
244
|
+
| `variant` | `"default" \| "card"` | `"default"` | `"card"`: header tab plus a full-bleed custom body. |
|
|
245
|
+
| `queue` | `boolean` | Toaster's `queue` | Per-toast override of the Toaster option. |
|
|
246
|
+
|
|
247
|
+
> Strings are always inserted as text, never as HTML.
|
|
248
|
+
> React nodes work in the React adapter. Vue, Svelte and vanilla accept strings or DOM nodes.
|
|
249
|
+
|
|
250
|
+
### `<Toaster />` and `createToaster()` options
|
|
251
|
+
|
|
252
|
+
| Option | Type | Default | Description |
|
|
253
|
+
| ---------- | ------------------------------------------------------------------------------ | -------- | -------------------------------------------------------- |
|
|
254
|
+
| `position` | `"top"` `"top-left"` `"top-right"` `"bottom"` `"bottom-left"` `"bottom-right"` | `"top"` | Where the island sits. |
|
|
255
|
+
| `offset` | `number` | `12` | Pixels from the screen edge. Safe-area insets are added. |
|
|
256
|
+
| `theme` | `"dark" \| "light" \| "system" \| CustomTheme` | `"dark"` | See [Theming](#theming). |
|
|
257
|
+
| `queue` | `boolean` | `false` | Show toasts one after another. |
|
|
258
|
+
| `timer` | `boolean` | `true` | Thin time-remaining bar on timed toasts. |
|
|
259
|
+
| `haptics` | `boolean` | `true` | `navigator.vibrate` on show. A no-op where unsupported. |
|
|
260
|
+
|
|
261
|
+
Mount **one** Toaster per page.
|
|
262
|
+
|
|
263
|
+
### Entry points
|
|
264
|
+
|
|
265
|
+
| Import | Use |
|
|
266
|
+
| -------------------- | ------------------------------------ |
|
|
267
|
+
| `dynaura` | React: `Toaster`, `dynaura` |
|
|
268
|
+
| `dynaura/vue` | Vue 3: `Toaster`, `dynaura` |
|
|
269
|
+
| `dynaura/svelte` | Svelte: `toaster` action, `dynaura` |
|
|
270
|
+
| `dynaura/vanilla` | Plain JS: `createToaster`, `dynaura` |
|
|
271
|
+
| `dynaura/styles.css` | The stylesheet. Import it once. |
|
|
272
|
+
|
|
273
|
+
### TypeScript
|
|
274
|
+
|
|
275
|
+
Types ship with the package: `ToastOptions`, `ToastType`, `ToasterProps`, `CustomTheme`, `Position` and `Content`.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import type { ToastOptions, CustomTheme } from "dynaura";
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Gestures
|
|
282
|
+
|
|
283
|
+
| Gesture | Result |
|
|
284
|
+
| -------------------------------- | ----------------------------------------------------------------------- |
|
|
285
|
+
| **Hover** (mouse) | Expands and pauses the timer. Leaving resumes with the exact time left. |
|
|
286
|
+
| **Tap** | Pins an expandable toast open and paused. Tap outside to unpin. |
|
|
287
|
+
| **Long-press** | Pins, with haptic feedback. |
|
|
288
|
+
| **Swipe** toward the screen edge | Dismisses. A short swipe snaps back. |
|
|
289
|
+
|
|
290
|
+
## Theming
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<Toaster theme="light" />
|
|
294
|
+
<Toaster theme="system" /> {/* follows the OS */}
|
|
295
|
+
<Toaster theme={{ background: "#2b1055", foreground: "#ffffff", success: "#7cffcb" }} />
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`CustomTheme` keys take any CSS color, and all are optional: `background`, `foreground`, `muted`, `ring`, `button`, `success`, `error`, `warning`, `info`, `loading`, `live`. Missing keys fall back to the dark theme.
|
|
299
|
+
|
|
300
|
+
Everything is a CSS variable, so you can also override styles directly:
|
|
301
|
+
|
|
302
|
+
```css
|
|
303
|
+
.dynaura-root {
|
|
304
|
+
--dynaura-font: "Inter", system-ui, sans-serif;
|
|
305
|
+
--dynaura-duration: 500ms; /* morph speed */
|
|
306
|
+
--dynaura-success: #00c853;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Font
|
|
311
|
+
|
|
312
|
+
The default font is [Bricolage Grotesque](https://fonts.google.com/specimen/Bricolage+Grotesque). `dynaura/styles.css` loads it from Google Fonts with an `@import` on its first line. To self-host or skip the request, copy the stylesheet without that line and set `--dynaura-font`.
|
|
313
|
+
|
|
314
|
+
## Accessibility
|
|
315
|
+
|
|
316
|
+
- The island has `role="status"` and `aria-live="polite"`.
|
|
317
|
+
- Action buttons are keyboard focusable, with a visible focus ring.
|
|
318
|
+
- Under `prefers-reduced-motion: reduce`, motion is cut to near-instant. Loading spinners keep spinning.
|
|
319
|
+
- Rolling digits expose their value through `aria-label`.
|
|
320
|
+
- Text is rendered as text. Toast strings can never inject HTML.
|
|
321
|
+
|
|
322
|
+
## Browser support
|
|
323
|
+
|
|
324
|
+
Modern evergreen browsers. The morph uses CSS `linear()` easing (Chrome 113+, Safari 17.2+, Firefox 112+) and `color-mix()`. Older browsers fall back to a bounce curve. Without `color-mix()` support, tinted backgrounds fall back to a neutral tint.
|
|
325
|
+
|
|
326
|
+
Tested in Chrome and WebKit with Playwright. Firefox has not been verified yet.
|
|
327
|
+
|
|
328
|
+
## Troubleshooting
|
|
329
|
+
|
|
330
|
+
<details>
|
|
331
|
+
<summary><b>Nothing appears</b></summary>
|
|
332
|
+
|
|
333
|
+
Check that `<Toaster />` is mounted once and that `dynaura/styles.css` is imported. Without the stylesheet the island renders unstyled.
|
|
334
|
+
|
|
335
|
+
</details>
|
|
336
|
+
|
|
337
|
+
<details>
|
|
338
|
+
<summary><b>The font is wrong, or I need to avoid Google Fonts</b></summary>
|
|
339
|
+
|
|
340
|
+
The stylesheet's first line is an `@import` for Bricolage Grotesque. Copy the stylesheet without it, or set `--dynaura-font` to your own font. This is also the fix for a strict Content Security Policy.
|
|
341
|
+
|
|
342
|
+
</details>
|
|
343
|
+
|
|
344
|
+
<details>
|
|
345
|
+
<summary><b>Can I have more than one Toaster?</b></summary>
|
|
346
|
+
|
|
347
|
+
No. State is a single global, so mount one Toaster per page.
|
|
348
|
+
|
|
349
|
+
</details>
|
|
350
|
+
|
|
351
|
+
<details>
|
|
352
|
+
<summary><b>Does it work with SSR?</b></summary>
|
|
353
|
+
|
|
354
|
+
Yes. The Toaster renders nothing on the server and mounts on the client. Calling `dynaura.*` on the server only sets state and shows nothing.
|
|
355
|
+
|
|
356
|
+
</details>
|
|
357
|
+
|
|
358
|
+
<details>
|
|
359
|
+
<summary><b>The toast sits under the notch or home indicator on iOS</b></summary>
|
|
360
|
+
|
|
361
|
+
Add `viewport-fit=cover` to your viewport meta tag. dynaura adds `env(safe-area-inset-*)` to the offset.
|
|
362
|
+
|
|
363
|
+
</details>
|
|
364
|
+
|
|
365
|
+
<details>
|
|
366
|
+
<summary><b>My description is a React component but it does not show in Vue or Svelte</b></summary>
|
|
367
|
+
|
|
368
|
+
React nodes only work in the React adapter. In Vue, Svelte and vanilla, pass a string or a DOM `Node`.
|
|
369
|
+
|
|
370
|
+
</details>
|
|
371
|
+
|
|
372
|
+
## Development
|
|
373
|
+
|
|
374
|
+
```sh
|
|
375
|
+
git clone https://github.com/ashutoshswamy/dynaura.git
|
|
376
|
+
cd dynaura
|
|
377
|
+
npm install
|
|
378
|
+
npm run dev # local test playground (not the docs site): imports src/ directly
|
|
379
|
+
npm test # vitest + jsdom
|
|
380
|
+
npm run build # tsup, outputs dist/
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
The playground at `http://localhost:5173` exercises every feature: themes (with live color pickers for custom), positions, queue, the timer bar, rich cards, and copyable code for each example. The code shown there comes from the files that run, so it cannot drift.
|
|
384
|
+
|
|
385
|
+
<details>
|
|
386
|
+
<summary><b>How it works</b></summary>
|
|
387
|
+
|
|
388
|
+
- **One island.** A single DOM element holds the current toast. A new toast morphs it instead of stacking, unless `queue` is on.
|
|
389
|
+
- **Size morph.** The core measures the compact row and the expanded content, then transitions `width`, `height` and `border-radius` between them with a CSS `linear()` spring.
|
|
390
|
+
- **Card variant.** The header tab and body are drawn as one `clip-path`. The core recomputes the outline every frame while the size transitions.
|
|
391
|
+
- **Timers.** Remaining time is tracked, so hover, drag and pin pause and resume exactly. The timer bar is a CSS animation that pauses with them.
|
|
392
|
+
- **Adapters.** Vue, Svelte and vanilla are thin wrappers over `createToaster()`. React adds portals so `description`, `icon` and `trailing` can be React nodes.
|
|
393
|
+
|
|
394
|
+
</details>
|
|
395
|
+
|
|
396
|
+
<details>
|
|
397
|
+
<summary><b>Adding an adapter</b></summary>
|
|
398
|
+
|
|
399
|
+
Call `createToaster(options)` on mount and `.destroy()` on unmount. Forward option changes with `.set()`. If the framework has its own node type, pass a `render(el, node)` option: the core calls it with `node = null` to unmount.
|
|
400
|
+
|
|
401
|
+
</details>
|
|
402
|
+
|
|
403
|
+
<details>
|
|
404
|
+
<summary><b>Releasing</b></summary>
|
|
405
|
+
|
|
406
|
+
```sh
|
|
407
|
+
npm version patch # or minor / major
|
|
408
|
+
npm publish # prepublishOnly runs typecheck, build and tests first
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
</details>
|
|
412
|
+
|
|
413
|
+
## Author
|
|
414
|
+
|
|
415
|
+
Built and maintained by **Ashutosh Swamy**.
|
|
416
|
+
|
|
417
|
+
[Portfolio](https://ashutoshswamy.in) · [GitHub](https://github.com/ashutoshswamy) · [LinkedIn](https://www.linkedin.com/in/ashutoshswamy) · [X](https://x.com/ashutoshswamy_)
|
|
418
|
+
|
|
419
|
+
If dynaura is useful to you, a star on [GitHub](https://github.com/ashutoshswamy/dynaura) helps others find it.
|
|
420
|
+
|
|
421
|
+
## License
|
|
422
|
+
|
|
423
|
+
[MIT](./LICENSE) © 2026 dynaura
|