react-snackbar-glass 1.2.0 → 1.3.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/CHANGELOG.md +17 -0
- package/README.md +50 -2
- package/dist/index.cjs +73 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +166 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +78 -2
- package/dist/index.d.ts +78 -2
- package/dist/index.js +73 -35
- package/dist/index.js.map +1 -1
- package/dist/styles.css +166 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-10-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `layout` option: `'card' | 'compact' | 'inline'`, available per snackbar,
|
|
13
|
+
on `<SnackbarProvider layout="…">` and in the store config. `compact: true`
|
|
14
|
+
keeps working as sugar for `layout: 'compact'`.
|
|
15
|
+
- `toast.single(message, options)` / `useSnackbar().single()`: message-only
|
|
16
|
+
status line with a hard one-line contract (ellipsis instead of wrapping),
|
|
17
|
+
no title, no hairline, and no close button while timed.
|
|
18
|
+
- Animation presets: `animation`, `enterAnimation` and `exitAnimation`
|
|
19
|
+
(`'slide' | 'fade' | 'scale' | 'pop' | 'flip' | 'none'`).
|
|
20
|
+
- `--prs-enter-duration` / `--prs-exit-duration` and `--prs-radius-inline`
|
|
21
|
+
CSS variables for pacing and shaping the new modes.
|
|
22
|
+
- Demo: layout and motion selectors in the Studio panel, `toast.single()`
|
|
23
|
+
example, and position buttons now confirm with a single-line toast.
|
|
24
|
+
|
|
8
25
|
## [1.2.0] - 2026-10-28
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ dependencies**.
|
|
|
9
9
|
- ⏳ **Promise aware** — one call moves a snackbar through loading → success → error
|
|
10
10
|
- 👆 **Swipe to dismiss**, hover/focus/document-hidden pause, countdown hairline
|
|
11
11
|
- 📦 **Compact + minimize** — one-line pills, or collapse the whole stack
|
|
12
|
+
- ✏️ **Single-line status** — `toast.single()` for instant, title-less feedback
|
|
13
|
+
- 🎞️ **Pick your motion** — slide, fade, scale, pop, flip or none
|
|
12
14
|
- ♿ **Accessible** — polite/assertive live regions, keyboard reachable actions, `prefers-reduced-motion`
|
|
13
15
|
- 🧩 **Typed** — written in TypeScript, ships ESM + CJS + `.d.ts`
|
|
14
16
|
- 🪶 **Tiny** — React is the only peer dependency
|
|
@@ -162,6 +164,9 @@ the snackbar only closes after the handler settles (`closeOnClick`, default
|
|
|
162
164
|
| `dismissible` | `boolean` | `true` | Show the close button. |
|
|
163
165
|
| `progress` | `boolean` | `true` | Show the countdown hairline. |
|
|
164
166
|
| `compact` | `boolean` | `false` | Single-line pill for every snackbar. |
|
|
167
|
+
| `layout` | `'card' \| 'compact' \| 'inline'` | `'card'` | Default layout; `compact` is sugar for `'compact'`. |
|
|
168
|
+
| `animation` | `'slide' \| 'fade' \| 'scale' \| 'pop' \| 'flip' \| 'none'` | `'slide'` | Enter + exit animation preset. |
|
|
169
|
+
| `enterAnimation` / `exitAnimation` | same as `animation` | — | Override a single phase. |
|
|
165
170
|
| `minimizedLabel` | `(count: number) => ReactNode` | `"3 notifications"` | Text on the collapsed pill. |
|
|
166
171
|
| `pauseOnHover` | `boolean` | `true` | Freeze the timer while hovered. |
|
|
167
172
|
| `pauseOnFocus` | `boolean` | `true` | Freeze the timer while focus is inside. |
|
|
@@ -178,12 +183,55 @@ the snackbar only closes after the handler settles (`closeOnClick`, default
|
|
|
178
183
|
## `SnackbarOptions`
|
|
179
184
|
|
|
180
185
|
`id`, `title`, `message`, `variant`, `duration`, `position`, `icon`,
|
|
181
|
-
`action`, `dismissible`, `progress`, `compact`, `
|
|
182
|
-
`
|
|
186
|
+
`action`, `dismissible`, `progress`, `compact`, `layout`, `animation`,
|
|
187
|
+
`enterAnimation`, `exitAnimation`, `tone`, `size`, `className`, `style`,
|
|
188
|
+
`onOpen`, `onClose`.
|
|
183
189
|
|
|
184
190
|
Reusing an `id` updates (or revives) the existing snackbar instead of stacking
|
|
185
191
|
a duplicate — ideal for progress-style flows.
|
|
186
192
|
|
|
193
|
+
## Single-line status notices
|
|
194
|
+
|
|
195
|
+
`toast.single()` is the "one line, no ceremony" preset: content width, a hard
|
|
196
|
+
one-line contract (long text truncates with an ellipsis instead of wrapping),
|
|
197
|
+
no title, no countdown hairline and no close button while it is timed.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
toast.single('Position set to top-center', { variant: 'success' });
|
|
201
|
+
toast.single('Could not reach the server', { variant: 'error', duration: 8000 });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
It is `layout: 'inline'` under the hood, so every entry point can do the same:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
snackbar.show({ message: 'Synced', layout: 'inline' });
|
|
208
|
+
useSnackbar().single('Copied to clipboard');
|
|
209
|
+
<SnackbarProvider layout="inline" />;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Animations
|
|
213
|
+
|
|
214
|
+
One preset drives both phases; `enterAnimation` / `exitAnimation` override a
|
|
215
|
+
single phase:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
toast.success('Saved', { animation: 'fade' });
|
|
219
|
+
toast.error('Failed', { animation: 'scale', exitAnimation: 'none' });
|
|
220
|
+
<SnackbarProvider animation="pop" />;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Preset | Feel |
|
|
224
|
+
| --- | --- |
|
|
225
|
+
| `slide` | Spring slide from the nearest edge with a soft blur (default). |
|
|
226
|
+
| `fade` | Pure opacity cross-fade. |
|
|
227
|
+
| `scale` | Zoom from the center. |
|
|
228
|
+
| `pop` | Bouncy overshoot. |
|
|
229
|
+
| `flip` | 3D flip on the X axis. |
|
|
230
|
+
| `none` | Instant, no motion. |
|
|
231
|
+
|
|
232
|
+
Pace them with `--prs-enter-duration` / `--prs-exit-duration` (defaults
|
|
233
|
+
`440ms` / `190ms`); `prefers-reduced-motion` still disables everything.
|
|
234
|
+
|
|
187
235
|
## Compact and minimized stacks
|
|
188
236
|
|
|
189
237
|
Two ways to reclaim screen space when snackbars would otherwise pile up.
|
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,10 @@ var DEFAULT_CONFIG = {
|
|
|
11
11
|
duration: 5e3,
|
|
12
12
|
dismissible: true,
|
|
13
13
|
progress: true,
|
|
14
|
-
|
|
14
|
+
layout: "card",
|
|
15
|
+
animation: "slide",
|
|
16
|
+
enterAnimation: "slide",
|
|
17
|
+
exitAnimation: "slide",
|
|
15
18
|
tone: "glass",
|
|
16
19
|
size: "md",
|
|
17
20
|
maxVisible: 3
|
|
@@ -39,23 +42,38 @@ function resolveContent(content, value) {
|
|
|
39
42
|
const resolved = typeof content === "function" ? content(value) : content;
|
|
40
43
|
return contentToOptions(resolved);
|
|
41
44
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
function resolveLayout(layout, compact, fallback) {
|
|
46
|
+
if (layout !== void 0) return layout;
|
|
47
|
+
if (compact !== void 0) return compact ? "compact" : "card";
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
function resolveLayoutPatch(record, options) {
|
|
51
|
+
if (options.layout === void 0 && options.compact === void 0) return {};
|
|
52
|
+
const layout = resolveLayout(options.layout, options.compact, record.layout);
|
|
53
|
+
return { layout, compact: layout === "compact" };
|
|
54
|
+
}
|
|
55
|
+
function mergeConfig(previous, patch) {
|
|
56
|
+
const layout = resolveLayout(patch.layout, patch.compact, previous.layout);
|
|
57
|
+
return {
|
|
58
|
+
position: patch.position ?? previous.position,
|
|
59
|
+
duration: patch.duration !== void 0 ? normalizeDuration(patch.duration, previous.duration) : previous.duration,
|
|
60
|
+
dismissible: patch.dismissible ?? previous.dismissible,
|
|
61
|
+
progress: patch.progress ?? previous.progress,
|
|
62
|
+
compact: layout === "compact",
|
|
63
|
+
layout,
|
|
64
|
+
animation: patch.animation ?? previous.animation,
|
|
65
|
+
enterAnimation: patch.enterAnimation ?? patch.animation ?? previous.enterAnimation,
|
|
66
|
+
exitAnimation: patch.exitAnimation ?? patch.animation ?? previous.exitAnimation,
|
|
67
|
+
tone: patch.tone ?? previous.tone,
|
|
68
|
+
size: patch.size ?? previous.size,
|
|
69
|
+
maxVisible: patch.maxVisible !== void 0 ? Math.max(1, Math.floor(patch.maxVisible)) : previous.maxVisible
|
|
58
70
|
};
|
|
71
|
+
}
|
|
72
|
+
function createSnackbarStore(initialConfig = {}) {
|
|
73
|
+
let config = mergeConfig(
|
|
74
|
+
DEFAULT_CONFIG,
|
|
75
|
+
initialConfig
|
|
76
|
+
);
|
|
59
77
|
let records = EMPTY;
|
|
60
78
|
const listeners = /* @__PURE__ */ new Set();
|
|
61
79
|
const timers = /* @__PURE__ */ new Map();
|
|
@@ -223,6 +241,7 @@ function createSnackbarStore(initialConfig = {}) {
|
|
|
223
241
|
}
|
|
224
242
|
return id;
|
|
225
243
|
}
|
|
244
|
+
const layout = resolveLayout(options.layout, options.compact, config.layout);
|
|
226
245
|
const record = {
|
|
227
246
|
id,
|
|
228
247
|
title: options.title,
|
|
@@ -236,7 +255,11 @@ function createSnackbarStore(initialConfig = {}) {
|
|
|
236
255
|
action: options.action ?? null,
|
|
237
256
|
dismissible: options.dismissible ?? config.dismissible,
|
|
238
257
|
progress: options.progress ?? config.progress,
|
|
239
|
-
compact:
|
|
258
|
+
compact: layout === "compact",
|
|
259
|
+
layout,
|
|
260
|
+
animation: options.animation ?? config.animation,
|
|
261
|
+
enterAnimation: options.enterAnimation ?? options.animation ?? config.enterAnimation,
|
|
262
|
+
exitAnimation: options.exitAnimation ?? options.animation ?? config.exitAnimation,
|
|
240
263
|
className: options.className,
|
|
241
264
|
style: options.style,
|
|
242
265
|
createdAt: Date.now(),
|
|
@@ -265,7 +288,14 @@ function createSnackbarStore(initialConfig = {}) {
|
|
|
265
288
|
...options.action !== void 0 ? { action: options.action } : null,
|
|
266
289
|
...options.dismissible !== void 0 ? { dismissible: options.dismissible } : null,
|
|
267
290
|
...options.progress !== void 0 ? { progress: options.progress } : null,
|
|
268
|
-
...
|
|
291
|
+
...resolveLayoutPatch(record, options),
|
|
292
|
+
...options.animation !== void 0 ? {
|
|
293
|
+
animation: options.animation,
|
|
294
|
+
enterAnimation: options.animation,
|
|
295
|
+
exitAnimation: options.animation
|
|
296
|
+
} : null,
|
|
297
|
+
...options.enterAnimation !== void 0 ? { enterAnimation: options.enterAnimation } : null,
|
|
298
|
+
...options.exitAnimation !== void 0 ? { exitAnimation: options.exitAnimation } : null,
|
|
269
299
|
...options.className !== void 0 ? { className: options.className } : null,
|
|
270
300
|
...options.style !== void 0 ? { style: options.style } : null,
|
|
271
301
|
...options.onOpen !== void 0 ? { onOpen: options.onOpen } : null,
|
|
@@ -315,19 +345,7 @@ function createSnackbarStore(initialConfig = {}) {
|
|
|
315
345
|
}
|
|
316
346
|
};
|
|
317
347
|
const setConfig = (patch) => {
|
|
318
|
-
config =
|
|
319
|
-
...config,
|
|
320
|
-
...patch.position !== void 0 ? { position: patch.position } : null,
|
|
321
|
-
...patch.duration !== void 0 ? {
|
|
322
|
-
duration: normalizeDuration(patch.duration, config.duration)
|
|
323
|
-
} : null,
|
|
324
|
-
...patch.dismissible !== void 0 ? { dismissible: patch.dismissible } : null,
|
|
325
|
-
...patch.progress !== void 0 ? { progress: patch.progress } : null,
|
|
326
|
-
...patch.compact !== void 0 ? { compact: patch.compact } : null,
|
|
327
|
-
...patch.tone !== void 0 ? { tone: patch.tone } : null,
|
|
328
|
-
...patch.size !== void 0 ? { size: patch.size } : null,
|
|
329
|
-
...patch.maxVisible !== void 0 ? { maxVisible: Math.max(1, Math.floor(patch.maxVisible)) } : null
|
|
330
|
-
};
|
|
348
|
+
config = mergeConfig(config, patch);
|
|
331
349
|
};
|
|
332
350
|
const promise = (input, messages, options = {}) => {
|
|
333
351
|
const loadingOptions = contentToOptions(messages.loading ?? "Loading\u2026");
|
|
@@ -630,7 +648,10 @@ function SnackbarItem({
|
|
|
630
648
|
};
|
|
631
649
|
const resolvedIcon = record.icon === void 0 ? icons?.[variant] ?? defaultIcons[variant] : record.icon;
|
|
632
650
|
const showIcon = record.icon !== false;
|
|
633
|
-
const
|
|
651
|
+
const isInline = record.layout === "inline";
|
|
652
|
+
const isTimed = Number.isFinite(duration) && duration > 0;
|
|
653
|
+
const showProgress = record.layout === "card" && record.progress && isTimed;
|
|
654
|
+
const showClose = record.dismissible && (!isInline || !isTimed);
|
|
634
655
|
const style = {
|
|
635
656
|
...record.style,
|
|
636
657
|
"--prs-drag-x": `${dragX}px`,
|
|
@@ -651,6 +672,9 @@ function SnackbarItem({
|
|
|
651
672
|
"data-has-icon": showIcon ? "true" : "false",
|
|
652
673
|
"data-dismissible": record.dismissible ? "true" : "false",
|
|
653
674
|
"data-compact": record.compact ? "true" : "false",
|
|
675
|
+
"data-layout": record.layout,
|
|
676
|
+
"data-enter": record.enterAnimation,
|
|
677
|
+
"data-exit": record.exitAnimation,
|
|
654
678
|
"data-prs-id": record.id,
|
|
655
679
|
"data-position": record.position,
|
|
656
680
|
style,
|
|
@@ -699,7 +723,7 @@ function SnackbarItem({
|
|
|
699
723
|
className: "prs-item__content",
|
|
700
724
|
"data-has-title": title !== void 0 && title !== null ? "true" : "false",
|
|
701
725
|
children: [
|
|
702
|
-
title !== void 0 && title !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prs-item__title", children: title }) : null,
|
|
726
|
+
title !== void 0 && title !== null && !isInline ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prs-item__title", children: title }) : null,
|
|
703
727
|
message !== void 0 && message !== null ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prs-item__message", children: message }) : null,
|
|
704
728
|
action ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prs-item__actions", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
705
729
|
"button",
|
|
@@ -718,7 +742,7 @@ function SnackbarItem({
|
|
|
718
742
|
]
|
|
719
743
|
}
|
|
720
744
|
),
|
|
721
|
-
|
|
745
|
+
showClose ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
722
746
|
"button",
|
|
723
747
|
{
|
|
724
748
|
type: "button",
|
|
@@ -938,6 +962,10 @@ function SnackbarProvider({
|
|
|
938
962
|
dismissible,
|
|
939
963
|
progress,
|
|
940
964
|
compact,
|
|
965
|
+
layout,
|
|
966
|
+
animation,
|
|
967
|
+
enterAnimation,
|
|
968
|
+
exitAnimation,
|
|
941
969
|
tone,
|
|
942
970
|
size,
|
|
943
971
|
maxVisible,
|
|
@@ -960,6 +988,10 @@ function SnackbarProvider({
|
|
|
960
988
|
dismissible,
|
|
961
989
|
progress,
|
|
962
990
|
compact,
|
|
991
|
+
layout,
|
|
992
|
+
animation,
|
|
993
|
+
enterAnimation,
|
|
994
|
+
exitAnimation,
|
|
963
995
|
tone,
|
|
964
996
|
size,
|
|
965
997
|
maxVisible
|
|
@@ -971,6 +1003,10 @@ function SnackbarProvider({
|
|
|
971
1003
|
dismissible,
|
|
972
1004
|
progress,
|
|
973
1005
|
compact,
|
|
1006
|
+
layout,
|
|
1007
|
+
animation,
|
|
1008
|
+
enterAnimation,
|
|
1009
|
+
exitAnimation,
|
|
974
1010
|
tone,
|
|
975
1011
|
size,
|
|
976
1012
|
maxVisible
|
|
@@ -989,6 +1025,7 @@ function SnackbarProvider({
|
|
|
989
1025
|
info: preset2("info"),
|
|
990
1026
|
neutral: preset2("neutral"),
|
|
991
1027
|
custom: preset2("custom"),
|
|
1028
|
+
single: (message, options = {}) => store.show({ ...options, message, layout: "inline" }),
|
|
992
1029
|
loading: (message, options = {}) => store.show({
|
|
993
1030
|
...options,
|
|
994
1031
|
message,
|
|
@@ -1055,6 +1092,7 @@ function createToast() {
|
|
|
1055
1092
|
toast2.info = preset("info");
|
|
1056
1093
|
toast2.neutral = preset("neutral");
|
|
1057
1094
|
toast2.custom = preset("custom");
|
|
1095
|
+
toast2.single = (message, options = {}) => getDefaultStore().show({ ...options, message, layout: "inline" });
|
|
1058
1096
|
toast2.loading = (message, options = {}) => getDefaultStore().show({
|
|
1059
1097
|
...options,
|
|
1060
1098
|
message,
|