react-snackbar-glass 1.2.0 → 1.4.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 +40 -0
- package/README.md +243 -44
- package/dist/index.cjs +73 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +266 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +88 -4
- package/dist/index.d.ts +88 -4
- package/dist/index.js +73 -35
- package/dist/index.js.map +1 -1
- package/dist/styles.css +266 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ 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.4.0] - 2026-10-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Surface presets via `tone`: `paper` (opaque matte, light/dark aware),
|
|
13
|
+
`graphite` (dark charcoal in both themes) and `neon` (near-black with an
|
|
14
|
+
accent glow), next to the existing `glass` and `solid`. Per-snackbar, on the
|
|
15
|
+
provider and in store config; no new prop to learn.
|
|
16
|
+
- Demo: the playground's tone switch is now a five-way surface picker with
|
|
17
|
+
swatches.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- README is now a full API reference: layouts, surfaces, sizes, animations,
|
|
22
|
+
every `SnackbarOptions` field, close reasons, the store surface and the
|
|
23
|
+
theming tokens.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Title-less cards no longer pin their message to the top edge: content is
|
|
28
|
+
centred against the icon when there is no title.
|
|
29
|
+
|
|
30
|
+
## [1.3.0] - 2026-10-28
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `layout` option: `'card' | 'compact' | 'inline'`, available per snackbar,
|
|
35
|
+
on `<SnackbarProvider layout="…">` and in the store config. `compact: true`
|
|
36
|
+
keeps working as sugar for `layout: 'compact'`.
|
|
37
|
+
- `toast.single(message, options)` / `useSnackbar().single()`: message-only
|
|
38
|
+
status line with a hard one-line contract (ellipsis instead of wrapping),
|
|
39
|
+
no title, no hairline, and no close button while timed.
|
|
40
|
+
- Animation presets: `animation`, `enterAnimation` and `exitAnimation`
|
|
41
|
+
(`'slide' | 'fade' | 'scale' | 'pop' | 'flip' | 'none'`).
|
|
42
|
+
- `--prs-enter-duration` / `--prs-exit-duration` and `--prs-radius-inline`
|
|
43
|
+
CSS variables for pacing and shaping the new modes.
|
|
44
|
+
- Demo: replaced the showcase page with an API-first homepage — hero install
|
|
45
|
+
command, 3-step quickstart, live playground, method/option tables and theming
|
|
46
|
+
notes (`demo/src/ApiHome.tsx`).
|
|
47
|
+
|
|
8
48
|
## [1.2.0] - 2026-10-28
|
|
9
49
|
|
|
10
50
|
### Added
|
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# react-snackbar-glass
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Queueing, promise-aware snackbars for React — five ready-made surfaces, three
|
|
4
|
+
densities, swipe-to-dismiss and token-driven theming with **zero runtime
|
|
5
5
|
dependencies**.
|
|
6
6
|
|
|
7
|
-
- 🪟 **
|
|
7
|
+
- 🪟 **Five surfaces** — `glass`, `paper`, `solid`, `graphite`, `neon`
|
|
8
|
+
- 📐 **Three layouts** — `card`, `compact`, `inline`; `toast.single()` for one-liners
|
|
8
9
|
- 🌍 **Global `toast`** — call it from anywhere, no hook required
|
|
9
10
|
- ⏳ **Promise aware** — one call moves a snackbar through loading → success → error
|
|
10
11
|
- 👆 **Swipe to dismiss**, hover/focus/document-hidden pause, countdown hairline
|
|
11
|
-
- 📦 **Compact + minimize** — one-line pills, or collapse the whole stack
|
|
12
|
+
- 📦 **Compact + minimize** — one-line pills, or collapse the whole stack into one
|
|
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
|
|
@@ -34,7 +36,7 @@ import 'react-snackbar-glass/styles.css';
|
|
|
34
36
|
|
|
35
37
|
export default function App() {
|
|
36
38
|
return (
|
|
37
|
-
<SnackbarProvider position="bottom-right" theme="auto">
|
|
39
|
+
<SnackbarProvider position="bottom-right" theme="auto" tone="glass">
|
|
38
40
|
<Dashboard />
|
|
39
41
|
</SnackbarProvider>
|
|
40
42
|
);
|
|
@@ -51,25 +53,43 @@ function Dashboard() {
|
|
|
51
53
|
}
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
That is the whole setup: one provider, one stylesheet, one hook. Everything else
|
|
57
|
+
below is optional.
|
|
58
|
+
|
|
54
59
|
## API
|
|
55
60
|
|
|
61
|
+
Two entry points render the same store, so pick whichever fits the call site:
|
|
62
|
+
|
|
63
|
+
| Import | Use it when |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `useSnackbar()` | Inside a component — the idiomatic path. |
|
|
66
|
+
| `toast` | Anywhere else: data layers, API clients, event handlers, `setTimeout`. |
|
|
67
|
+
|
|
56
68
|
### `useSnackbar()`
|
|
57
69
|
|
|
70
|
+
```ts
|
|
71
|
+
const snackbar = useSnackbar();
|
|
72
|
+
```
|
|
73
|
+
|
|
58
74
|
| Method | Description |
|
|
59
75
|
| --- | --- |
|
|
60
76
|
| `show(options)` | Queue a fully customised snackbar, returns its `id`. |
|
|
61
|
-
| `update(id, options)` | Patch an existing snackbar in place; timers restart
|
|
77
|
+
| `update(id, options)` | Patch an existing snackbar in place; timers restart when `duration` changes. |
|
|
62
78
|
| `dismiss(id?, reason?)` | Close one snackbar (or the oldest) and report a close reason. |
|
|
63
79
|
| `dismissAll(reason?)` | Close every visible snackbar. |
|
|
64
80
|
| `success / error / warning / info / neutral / loading / custom(message, options?)` | Variant presets. |
|
|
65
|
-
| `
|
|
81
|
+
| `single(message, options?)` | Message-only status line (`layout: 'inline'`). |
|
|
82
|
+
| `promise(promise, messages, options?)` | Drive one snackbar through loading → success → error. |
|
|
83
|
+
| `minimize()` / `expand()` / `isMinimized()` | Collapse the stack into a summary pill and bring it back. |
|
|
66
84
|
| `store` | Escape hatch: the underlying store for subscriptions and advanced updates. |
|
|
67
85
|
|
|
86
|
+
Every method returns the snackbar `id` (except `update`, `dismiss*` and the
|
|
87
|
+
minimize helpers), so you can update or dismiss it later.
|
|
88
|
+
|
|
68
89
|
### `toast` — global API
|
|
69
90
|
|
|
70
|
-
Import `toast` when you are outside a component
|
|
71
|
-
|
|
72
|
-
that `<SnackbarProvider>` renders, so no hook is needed.
|
|
91
|
+
Import `toast` when you are outside a component. It talks to the same store that
|
|
92
|
+
`<SnackbarProvider>` renders, so no hook is needed.
|
|
73
93
|
|
|
74
94
|
```ts
|
|
75
95
|
import { toast } from 'react-snackbar-glass';
|
|
@@ -77,22 +97,115 @@ import { toast } from 'react-snackbar-glass';
|
|
|
77
97
|
toast('Draft saved');
|
|
78
98
|
toast.success('Deployment complete.', { title: 'v1.4.0 is live' });
|
|
79
99
|
toast.error('Payment declined', { action: { label: 'Retry', onClick: retry } });
|
|
80
|
-
|
|
100
|
+
toast.single('Position set to top-center');
|
|
81
101
|
|
|
82
|
-
It mirrors `useSnackbar()` one-to-one — `show`, `update`, `dismiss`,
|
|
83
|
-
`dismissAll`, every variant preset, `promise`, and `store`:
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
102
|
const id = toast.loading('Saving…');
|
|
87
103
|
await save();
|
|
88
104
|
toast.update(id, { variant: 'success', message: 'Saved', duration: 3000 });
|
|
89
105
|
```
|
|
90
106
|
|
|
107
|
+
It mirrors `useSnackbar()` one-to-one — `show`, `update`, `dismiss`,
|
|
108
|
+
`dismissAll`, every variant preset, `single`, `promise`, `minimize`, `expand`,
|
|
109
|
+
`isMinimized` and `store`.
|
|
110
|
+
|
|
91
111
|
`<SnackbarProvider>` renders the shared store by default. Mount
|
|
92
112
|
`<SnackbarHost store={getDefaultStore()} />` if you need the host without a
|
|
93
|
-
provider, and `setDefaultStore(store)` to point `toast` at a store of your
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
provider, and `setDefaultStore(store)` to point `toast` at a store of your own
|
|
114
|
+
(handy in tests or multi-app setups). On the server, pass an explicit `store` so
|
|
115
|
+
requests stay isolated.
|
|
116
|
+
|
|
117
|
+
### Layouts
|
|
118
|
+
|
|
119
|
+
`layout` is the density axis. It can be set per snackbar, on the provider or in
|
|
120
|
+
the store config, and it is the only thing that changes between the three shapes:
|
|
121
|
+
|
|
122
|
+
| `layout` | Shape | Use it for |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `card` | Full card: title, message, action, hairline. Default. | Results that deserve a headline and an action. |
|
|
125
|
+
| `compact` | Fully rounded single-line pill. | High-frequency messages that should stay quiet. |
|
|
126
|
+
| `inline` | Message-only status line, content width, ~38 px tall. | Instant feedback such as “Copied” or “Position set”. |
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
toast.success('Draft saved', { layout: 'compact' });
|
|
130
|
+
snackbar.show({ message: 'Synced', layout: 'inline' });
|
|
131
|
+
<SnackbarProvider layout="compact" />;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`compact: true` still works and is sugar for `layout: 'compact'`.
|
|
135
|
+
|
|
136
|
+
**Single-line status** — `toast.single()` is the `inline` preset with a hard
|
|
137
|
+
one-line contract: long text truncates with an ellipsis instead of wrapping, and
|
|
138
|
+
a timed notice has no title, no countdown hairline and no close button.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
toast.single('Position set to top-center', { variant: 'success' });
|
|
142
|
+
toast.single('Could not reach the server', { variant: 'error', duration: 8000 });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Surfaces
|
|
146
|
+
|
|
147
|
+
Five presets ship with the stylesheet. Pick one per snackbar or as the default:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<SnackbarProvider tone="paper" />
|
|
151
|
+
|
|
152
|
+
// or per snackbar
|
|
153
|
+
toast.info('Backup finished.', { tone: 'graphite' });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| `tone` | Look |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `glass` | Frosted and translucent — the page shows through. Default. |
|
|
159
|
+
| `paper` | Opaque, matte, hairline border, no blur. Follows light/dark. |
|
|
160
|
+
| `solid` | The variant accent painted as the surface, white text. |
|
|
161
|
+
| `graphite` | Dark matte charcoal with light text, in both themes. |
|
|
162
|
+
| `neon` | Near-black with an accent glow ring, in both themes. |
|
|
163
|
+
|
|
164
|
+
All five keep the same layout, icon tinting, hairline and motion, and every one
|
|
165
|
+
is driven by the `--prs-*` tokens, so you can restyle a preset without forking
|
|
166
|
+
the CSS.
|
|
167
|
+
|
|
168
|
+
### Sizes and positions
|
|
169
|
+
|
|
170
|
+
| `size` | Width (relative to `--prs-width`, default `384px`) |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `xs` | 72 % |
|
|
173
|
+
| `sm` | 86 % |
|
|
174
|
+
| `md` | 100 % — default |
|
|
175
|
+
| `lg` | 116 % |
|
|
176
|
+
|
|
177
|
+
Positions are the six screen corners: `top-left`, `top-center`, `top-right`,
|
|
178
|
+
`bottom-left`, `bottom-center`, `bottom-right` (default).
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
toast.success('Saved', { size: 'sm', position: 'top-center' });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`maxVisible` (default `3`) caps how many cards a corner shows at once; older
|
|
185
|
+
ones stay queued and take the next free slot.
|
|
186
|
+
|
|
187
|
+
### Animations
|
|
188
|
+
|
|
189
|
+
One preset drives both phases; `enterAnimation` / `exitAnimation` override a
|
|
190
|
+
single phase:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
toast.success('Saved', { animation: 'fade' });
|
|
194
|
+
toast.error('Failed', { animation: 'scale', exitAnimation: 'none' });
|
|
195
|
+
<SnackbarProvider animation="pop" />;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
| Preset | Feel |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `slide` | Spring slide from the nearest edge with a soft blur (default). |
|
|
201
|
+
| `fade` | Pure opacity cross-fade. |
|
|
202
|
+
| `scale` | Zoom from the center. |
|
|
203
|
+
| `pop` | Bouncy overshoot. |
|
|
204
|
+
| `flip` | 3D flip on the X axis. |
|
|
205
|
+
| `none` | Instant, no motion. |
|
|
206
|
+
|
|
207
|
+
Pace them with `--prs-enter-duration` / `--prs-exit-duration` (defaults
|
|
208
|
+
`440ms` / `190ms`); `prefers-reduced-motion` still disables everything.
|
|
96
209
|
|
|
97
210
|
### Promise-aware flows
|
|
98
211
|
|
|
@@ -110,12 +223,13 @@ snackbar.promise(deploy(), {
|
|
|
110
223
|
});
|
|
111
224
|
```
|
|
112
225
|
|
|
113
|
-
`success` and `error`
|
|
114
|
-
receives the resolved value / rejection reason.
|
|
226
|
+
`loading`, `success` and `error` accept plain content, full options, or a mapper
|
|
227
|
+
that receives the resolved value / rejection reason. The returned promise
|
|
228
|
+
resolves with the original value, so it can be awaited inline.
|
|
115
229
|
|
|
116
230
|
### Sticky snackbars with in-place updates
|
|
117
231
|
|
|
118
|
-
```
|
|
232
|
+
```ts
|
|
119
233
|
const id = snackbar.show({
|
|
120
234
|
variant: 'loading',
|
|
121
235
|
title: 'Uploading assets',
|
|
@@ -132,9 +246,12 @@ snackbar.update(id, {
|
|
|
132
246
|
});
|
|
133
247
|
```
|
|
134
248
|
|
|
249
|
+
Reusing an `id` (in `show()` or `update()`) updates or revives the existing
|
|
250
|
+
snackbar instead of stacking a duplicate — ideal for progress-style flows.
|
|
251
|
+
|
|
135
252
|
### Actions and undo windows
|
|
136
253
|
|
|
137
|
-
```
|
|
254
|
+
```ts
|
|
138
255
|
snackbar.show({
|
|
139
256
|
message: 'Project archived.',
|
|
140
257
|
duration: 7000,
|
|
@@ -145,23 +262,76 @@ snackbar.show({
|
|
|
145
262
|
});
|
|
146
263
|
```
|
|
147
264
|
|
|
148
|
-
Action handlers may return a promise: the button shows a pending state and
|
|
149
|
-
|
|
150
|
-
`
|
|
265
|
+
Action handlers may return a promise: the button shows a pending state and the
|
|
266
|
+
snackbar only closes after the handler settles (`closeOnClick`, default `true`).
|
|
267
|
+
Set `closeOnClick: false` to keep the snackbar open for a second step.
|
|
268
|
+
|
|
269
|
+
### Close reasons and lifecycle
|
|
270
|
+
|
|
271
|
+
`onClose` receives **why** a snackbar went away, plus its id:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
snackbar.show({
|
|
275
|
+
message: 'Draft saved',
|
|
276
|
+
onOpen: (id) => track('shown', id),
|
|
277
|
+
onClose: (reason) => track('closed', reason),
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
| Reason | Fired by |
|
|
282
|
+
| --- | --- |
|
|
283
|
+
| `timeout` | The duration elapsed. |
|
|
284
|
+
| `manual` | The close button (or `dismiss()` without a reason). |
|
|
285
|
+
| `action` | The action button, after its handler settled. |
|
|
286
|
+
| `swipe` | A drag past `swipeThreshold`. |
|
|
287
|
+
| `escape` | The user pressed <kbd>Esc</kbd> while a snackbar had focus. |
|
|
288
|
+
| `dismissAll` | `dismissAll()` cleared the stack. |
|
|
289
|
+
|
|
290
|
+
`onOpen` runs once the snackbar is actually mounted in a visible slot — not when
|
|
291
|
+
it is queued.
|
|
292
|
+
|
|
293
|
+
## `SnackbarOptions`
|
|
294
|
+
|
|
295
|
+
| Option | Type | Default | Notes |
|
|
296
|
+
| --- | --- | --- | --- |
|
|
297
|
+
| `id` | `string` | generated | Stable key; reusing it updates instead of stacking. |
|
|
298
|
+
| `title` | `ReactNode` | — | Small, strong headline above the message. |
|
|
299
|
+
| `message` | `ReactNode` | — | Main content. |
|
|
300
|
+
| `variant` | `'neutral' \| 'success' \| 'error' \| 'warning' \| 'info' \| 'loading' \| 'custom'` | `'neutral'` | Drives accent, icon and live-region politeness. |
|
|
301
|
+
| `duration` | `number \| null` | `5000` | `null`, `0` or `Infinity` = persistent. |
|
|
302
|
+
| `position` | `SnackbarPosition` | provider | Corner for this snackbar. |
|
|
303
|
+
| `icon` | `ReactNode \| false` | variant icon | `false` hides the icon. |
|
|
304
|
+
| `action` | `{ label, onClick?, closeOnClick? } \| null` | `null` | One call-to-action. |
|
|
305
|
+
| `dismissible` | `boolean` | `true` | Show the close button. |
|
|
306
|
+
| `progress` | `boolean` | `true` | Countdown hairline. |
|
|
307
|
+
| `layout` | `'card' \| 'compact' \| 'inline'` | `'card'` | Density. |
|
|
308
|
+
| `compact` | `boolean` | `false` | Sugar for `layout: 'compact'`. |
|
|
309
|
+
| `animation` | `SnackbarAnimation` | `'slide'` | Both phases. |
|
|
310
|
+
| `enterAnimation` / `exitAnimation` | `SnackbarAnimation` | — | Per-phase override. |
|
|
311
|
+
| `tone` | `'glass' \| 'paper' \| 'solid' \| 'graphite' \| 'neon'` | `'glass'` | Surface preset. |
|
|
312
|
+
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Density/width. |
|
|
313
|
+
| `className` / `style` | — | — | Per-snackbar styling hooks. |
|
|
314
|
+
| `onOpen` | `(id) => void` | — | Mounted and visible. |
|
|
315
|
+
| `onClose` | `(reason, id) => void` | — | Dismissal started. |
|
|
151
316
|
|
|
152
317
|
## `<SnackbarProvider>` props
|
|
153
318
|
|
|
319
|
+
Every default below can be overridden per snackbar in `SnackbarOptions`.
|
|
320
|
+
|
|
154
321
|
| Prop | Type | Default | Description |
|
|
155
322
|
| --- | --- | --- | --- |
|
|
156
323
|
| `position` | `SnackbarPosition` | `'bottom-right'` | Default corner for new snackbars. |
|
|
157
324
|
| `duration` | `number \| null` | `5000` | Auto-dismiss delay in ms. `null`/`0`/`Infinity` = persistent. |
|
|
158
|
-
| `tone` | `'glass' \| 'solid'` | `'glass'` | Surface
|
|
325
|
+
| `tone` | `'glass' \| 'paper' \| 'solid' \| 'graphite' \| 'neon'` | `'glass'` | Surface preset. |
|
|
159
326
|
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Card density/width. |
|
|
327
|
+
| `layout` | `'card' \| 'compact' \| 'inline'` | `'card'` | Default layout. |
|
|
328
|
+
| `compact` | `boolean` | `false` | Sugar for `layout: 'compact'`. |
|
|
329
|
+
| `animation` | `SnackbarAnimation` | `'slide'` | Enter + exit preset. |
|
|
330
|
+
| `enterAnimation` / `exitAnimation` | `SnackbarAnimation` | — | Per-phase override. |
|
|
160
331
|
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color scheme. |
|
|
161
332
|
| `maxVisible` | `number` | `3` | Simultaneously visible cards per corner. |
|
|
162
333
|
| `dismissible` | `boolean` | `true` | Show the close button. |
|
|
163
334
|
| `progress` | `boolean` | `true` | Show the countdown hairline. |
|
|
164
|
-
| `compact` | `boolean` | `false` | Single-line pill for every snackbar. |
|
|
165
335
|
| `minimizedLabel` | `(count: number) => ReactNode` | `"3 notifications"` | Text on the collapsed pill. |
|
|
166
336
|
| `pauseOnHover` | `boolean` | `true` | Freeze the timer while hovered. |
|
|
167
337
|
| `pauseOnFocus` | `boolean` | `true` | Freeze the timer while focus is inside. |
|
|
@@ -174,15 +344,7 @@ the snackbar only closes after the handler settles (`closeOnClick`, default
|
|
|
174
344
|
| `container` | `HTMLElement \| () => HTMLElement \| null` | `document.body` | Portal target. |
|
|
175
345
|
| `dir` | `'ltr' \| 'rtl'` | inherited | Text direction. |
|
|
176
346
|
| `zIndex` | `number` | `2147483000` | Host stacking context. |
|
|
177
|
-
|
|
178
|
-
## `SnackbarOptions`
|
|
179
|
-
|
|
180
|
-
`id`, `title`, `message`, `variant`, `duration`, `position`, `icon`,
|
|
181
|
-
`action`, `dismissible`, `progress`, `compact`, `tone`, `size`, `className`,
|
|
182
|
-
`style`, `onOpen`, `onClose`.
|
|
183
|
-
|
|
184
|
-
Reusing an `id` updates (or revives) the existing snackbar instead of stacking
|
|
185
|
-
a duplicate — ideal for progress-style flows.
|
|
347
|
+
| `className` / `style` | — | — | Classes/styles for the host root. |
|
|
186
348
|
|
|
187
349
|
## Compact and minimized stacks
|
|
188
350
|
|
|
@@ -202,9 +364,9 @@ Set it once for every snackbar with `<SnackbarProvider compact />` or
|
|
|
202
364
|
(`"3 notifications"`) that expands on click:
|
|
203
365
|
|
|
204
366
|
```ts
|
|
205
|
-
toast.minimize();
|
|
206
|
-
toast.expand();
|
|
207
|
-
toast.isMinimized();
|
|
367
|
+
toast.minimize(); // stack → one pill
|
|
368
|
+
toast.expand(); // back to full cards
|
|
369
|
+
toast.isMinimized(); // boolean
|
|
208
370
|
```
|
|
209
371
|
|
|
210
372
|
`useSnackbar()` exposes the same three methods, and the store has
|
|
@@ -213,13 +375,14 @@ toast.isMinimized(); // boolean
|
|
|
213
375
|
|
|
214
376
|
## Theming
|
|
215
377
|
|
|
216
|
-
Colors, radii, blur and motion are CSS custom properties on `.prs-root`
|
|
217
|
-
|
|
378
|
+
Colors, radii, blur and motion are CSS custom properties on `.prs-root` (and
|
|
379
|
+
per-card inline styles).
|
|
218
380
|
|
|
219
381
|
```css
|
|
220
382
|
:root {
|
|
221
383
|
--prs-accent: #8b5cf6;
|
|
222
384
|
--prs-radius: 16px;
|
|
385
|
+
--prs-width: 400px;
|
|
223
386
|
}
|
|
224
387
|
```
|
|
225
388
|
|
|
@@ -231,12 +394,32 @@ snackbar.custom('Added to collection.', {
|
|
|
231
394
|
});
|
|
232
395
|
```
|
|
233
396
|
|
|
397
|
+
| Token | Default | Controls |
|
|
398
|
+
| --- | --- | --- |
|
|
399
|
+
| `--prs-accent` | variant color | Accent for icons, actions and links. |
|
|
400
|
+
| `--prs-accent-soft` | 13 % accent | Icon chip / action background. |
|
|
401
|
+
| `--prs-accent-ring` | 30 % accent | Icon ring and hover border. |
|
|
402
|
+
| `--prs-solid` | accent | Surface color of the `solid` tone. |
|
|
403
|
+
| `--prs-radius` | `18px` | Card radius. |
|
|
404
|
+
| `--prs-width` | `384px` | Base card width; `size` scales it. |
|
|
405
|
+
| `--prs-offset` | `20px` | Distance from the viewport edge. |
|
|
406
|
+
| `--prs-gap` | `12px` | Space between stacked cards. |
|
|
407
|
+
| `--prs-font` | Inter/system stack | Font family. |
|
|
408
|
+
| `--prs-blur` | `blur(22px) saturate(1.55)` | Glass backdrop filter. |
|
|
409
|
+
| `--prs-shadow` / `--prs-shadow-hover` | layered | Card shadow. |
|
|
410
|
+
| `--prs-enter-duration` / `--prs-exit-duration` | `440ms` / `190ms` | Motion pacing. |
|
|
411
|
+
| `--prs-radius-inline` | pill radius | Radius of `inline` notices. |
|
|
412
|
+
| `--prs-z-index` | `2147483000` | Host stacking context. |
|
|
413
|
+
|
|
234
414
|
## Store-only usage
|
|
235
415
|
|
|
416
|
+
If you want to own the store — or render the host somewhere specific — skip the
|
|
417
|
+
provider:
|
|
418
|
+
|
|
236
419
|
```tsx
|
|
237
420
|
import { createSnackbarStore, SnackbarHost } from 'react-snackbar-glass';
|
|
238
421
|
|
|
239
|
-
const store = createSnackbarStore({ position: 'top-center' });
|
|
422
|
+
const store = createSnackbarStore({ position: 'top-center', tone: 'paper' });
|
|
240
423
|
|
|
241
424
|
function App() {
|
|
242
425
|
return (
|
|
@@ -248,11 +431,27 @@ function App() {
|
|
|
248
431
|
}
|
|
249
432
|
```
|
|
250
433
|
|
|
434
|
+
```ts
|
|
435
|
+
store.setConfig({ duration: 3000, layout: 'compact' }); // live default changes
|
|
436
|
+
const stop = store.subscribe(() => console.log(store.getSnapshot()));
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
| Member | Description |
|
|
440
|
+
| --- | --- |
|
|
441
|
+
| `show / update / dismiss / dismissAll / promise` | Same semantics as the hook. |
|
|
442
|
+
| `subscribe(fn)` | Listen for any change; returns an unsubscribe function. |
|
|
443
|
+
| `getSnapshot()` / `getServerSnapshot()` | Current records (stable for `useSyncExternalStore`). |
|
|
444
|
+
| `getConfig()` / `setConfig(partial)` | Read or change defaults at runtime. |
|
|
445
|
+
| `pause(id, reason?)` / `resume(id, reason?)` | Reference-counted timer pause (hover, drag, hidden tab). |
|
|
446
|
+
| `minimize / expand / getMinimized` | Stack collapse state. |
|
|
447
|
+
| `notifyShown(id)` / `notifyExited(id)` | Lifecycle hooks the viewport calls. |
|
|
448
|
+
| `setPageHidden(hidden)` | Forward `document.visibilityState` changes. |
|
|
449
|
+
|
|
251
450
|
## Server-side rendering
|
|
252
451
|
|
|
253
452
|
`SnackbarProvider` and `SnackbarHost` render nothing until mounted, and the
|
|
254
|
-
store returns a stable server snapshot. No `window` access during render, so
|
|
255
|
-
|
|
453
|
+
store returns a stable server snapshot. No `window` access during render, so the
|
|
454
|
+
components are safe in Next.js, Remix and other SSR frameworks.
|
|
256
455
|
|
|
257
456
|
## Requirements
|
|
258
457
|
|
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,
|