react-snackbar-glass 1.1.0 → 1.2.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 CHANGED
@@ -5,6 +5,22 @@ 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.2.0] - 2026-10-28
9
+
10
+ ### Added
11
+
12
+ - `compact` snackbars: single-line pill, no title or countdown hairline.
13
+ Available per toast (`compact: true`), on the provider
14
+ (`<SnackbarProvider compact />`) and in the store config.
15
+ - Minimize API: `toast.minimize()`, `toast.expand()`, `toast.isMinimized()`
16
+ (also on `useSnackbar()`), collapsing the whole stack into one summary pill
17
+ that expands on click.
18
+ - `minimizedLabel` host prop to customise the collapsed pill text.
19
+
20
+ ### Changed
21
+
22
+ - Demo homepage now defaults to the light theme.
23
+
8
24
  ## [1.1.0] - 2026-10-28
9
25
 
10
26
  ### Added
package/README.md CHANGED
@@ -8,6 +8,7 @@ dependencies**.
8
8
  - 🌍 **Global `toast`** — call it from anywhere, no hook required
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
+ - ðŸ“Ķ **Compact + minimize** — one-line pills, or collapse the whole stack
11
12
  - â™ŋ **Accessible** — polite/assertive live regions, keyboard reachable actions, `prefers-reduced-motion`
12
13
  - ðŸ§Đ **Typed** — written in TypeScript, ships ESM + CJS + `.d.ts`
13
14
  - ðŸŠķ **Tiny** — React is the only peer dependency
@@ -160,6 +161,8 @@ the snackbar only closes after the handler settles (`closeOnClick`, default
160
161
  | `maxVisible` | `number` | `3` | Simultaneously visible cards per corner. |
161
162
  | `dismissible` | `boolean` | `true` | Show the close button. |
162
163
  | `progress` | `boolean` | `true` | Show the countdown hairline. |
164
+ | `compact` | `boolean` | `false` | Single-line pill for every snackbar. |
165
+ | `minimizedLabel` | `(count: number) => ReactNode` | `"3 notifications"` | Text on the collapsed pill. |
163
166
  | `pauseOnHover` | `boolean` | `true` | Freeze the timer while hovered. |
164
167
  | `pauseOnFocus` | `boolean` | `true` | Freeze the timer while focus is inside. |
165
168
  | `pauseOnDocumentHidden` | `boolean` | `true` | Pause when the tab is hidden. |
@@ -175,12 +178,39 @@ the snackbar only closes after the handler settles (`closeOnClick`, default
175
178
  ## `SnackbarOptions`
176
179
 
177
180
  `id`, `title`, `message`, `variant`, `duration`, `position`, `icon`,
178
- `action`, `dismissible`, `progress`, `tone`, `size`, `className`, `style`,
179
- `onOpen`, `onClose`.
181
+ `action`, `dismissible`, `progress`, `compact`, `tone`, `size`, `className`,
182
+ `style`, `onOpen`, `onClose`.
180
183
 
181
184
  Reusing an `id` updates (or revives) the existing snackbar instead of stacking
182
185
  a duplicate — ideal for progress-style flows.
183
186
 
187
+ ## Compact and minimized stacks
188
+
189
+ Two ways to reclaim screen space when snackbars would otherwise pile up.
190
+
191
+ **Compact** — a single-line pill per snackbar (no title, no hairline):
192
+
193
+ ```ts
194
+ toast.success('Draft saved', { compact: true });
195
+ snackbar.show({ message: 'Synced', compact: true });
196
+ ```
197
+
198
+ Set it once for every snackbar with `<SnackbarProvider compact />` or
199
+ `createSnackbarStore({ compact: true })`.
200
+
201
+ **Minimized** — collapse the whole stack into one summary pill
202
+ (`"3 notifications"`) that expands on click:
203
+
204
+ ```ts
205
+ toast.minimize(); // stack → one pill
206
+ toast.expand(); // back to full cards
207
+ toast.isMinimized(); // boolean
208
+ ```
209
+
210
+ `useSnackbar()` exposes the same three methods, and the store has
211
+ `setMinimized(true | false)` + `getMinimized()`. Customize the pill text with
212
+ `minimizedLabel={(count) => `${count} neue Meldungen`}`.
213
+
184
214
  ## Theming
185
215
 
186
216
  Colors, radii, blur and motion are CSS custom properties on `.prs-root`
package/dist/index.cjs CHANGED
@@ -11,6 +11,7 @@ var DEFAULT_CONFIG = {
11
11
  duration: 5e3,
12
12
  dismissible: true,
13
13
  progress: true,
14
+ compact: false,
14
15
  tone: "glass",
15
16
  size: "md",
16
17
  maxVisible: 3
@@ -50,6 +51,7 @@ function createSnackbarStore(initialConfig = {}) {
50
51
  } : null,
51
52
  ...initialConfig.dismissible !== void 0 ? { dismissible: initialConfig.dismissible } : null,
52
53
  ...initialConfig.progress !== void 0 ? { progress: initialConfig.progress } : null,
54
+ ...initialConfig.compact !== void 0 ? { compact: initialConfig.compact } : null,
53
55
  ...initialConfig.tone !== void 0 ? { tone: initialConfig.tone } : null,
54
56
  ...initialConfig.size !== void 0 ? { size: initialConfig.size } : null,
55
57
  ...initialConfig.maxVisible !== void 0 ? { maxVisible: Math.max(1, Math.floor(initialConfig.maxVisible)) } : null
@@ -60,6 +62,7 @@ function createSnackbarStore(initialConfig = {}) {
60
62
  const shown = /* @__PURE__ */ new Set();
61
63
  const pauseReasons = /* @__PURE__ */ new Map();
62
64
  let pageHidden = false;
65
+ let minimized = false;
63
66
  const emit = () => {
64
67
  for (const listener of listeners) listener();
65
68
  };
@@ -233,6 +236,7 @@ function createSnackbarStore(initialConfig = {}) {
233
236
  action: options.action ?? null,
234
237
  dismissible: options.dismissible ?? config.dismissible,
235
238
  progress: options.progress ?? config.progress,
239
+ compact: options.compact ?? config.compact,
236
240
  className: options.className,
237
241
  style: options.style,
238
242
  createdAt: Date.now(),
@@ -261,6 +265,7 @@ function createSnackbarStore(initialConfig = {}) {
261
265
  ...options.action !== void 0 ? { action: options.action } : null,
262
266
  ...options.dismissible !== void 0 ? { dismissible: options.dismissible } : null,
263
267
  ...options.progress !== void 0 ? { progress: options.progress } : null,
268
+ ...options.compact !== void 0 ? { compact: options.compact } : null,
264
269
  ...options.className !== void 0 ? { className: options.className } : null,
265
270
  ...options.style !== void 0 ? { style: options.style } : null,
266
271
  ...options.onOpen !== void 0 ? { onOpen: options.onOpen } : null,
@@ -318,6 +323,7 @@ function createSnackbarStore(initialConfig = {}) {
318
323
  } : null,
319
324
  ...patch.dismissible !== void 0 ? { dismissible: patch.dismissible } : null,
320
325
  ...patch.progress !== void 0 ? { progress: patch.progress } : null,
326
+ ...patch.compact !== void 0 ? { compact: patch.compact } : null,
321
327
  ...patch.tone !== void 0 ? { tone: patch.tone } : null,
322
328
  ...patch.size !== void 0 ? { size: patch.size } : null,
323
329
  ...patch.maxVisible !== void 0 ? { maxVisible: Math.max(1, Math.floor(patch.maxVisible)) } : null
@@ -372,7 +378,14 @@ function createSnackbarStore(initialConfig = {}) {
372
378
  resume,
373
379
  notifyShown,
374
380
  notifyExited,
375
- setPageHidden
381
+ setPageHidden,
382
+ getMinimized: () => minimized,
383
+ getServerMinimized: () => false,
384
+ setMinimized(next) {
385
+ if (minimized === next) return;
386
+ minimized = next;
387
+ emit();
388
+ }
376
389
  };
377
390
  }
378
391
 
@@ -617,7 +630,7 @@ function SnackbarItem({
617
630
  };
618
631
  const resolvedIcon = record.icon === void 0 ? icons?.[variant] ?? defaultIcons[variant] : record.icon;
619
632
  const showIcon = record.icon !== false;
620
- const showProgress = record.progress && Number.isFinite(duration) && duration > 0;
633
+ const showProgress = !record.compact && record.progress && Number.isFinite(duration) && duration > 0;
621
634
  const style = {
622
635
  ...record.style,
623
636
  "--prs-drag-x": `${dragX}px`,
@@ -637,6 +650,7 @@ function SnackbarItem({
637
650
  "data-dragging": dragging ? "true" : "false",
638
651
  "data-has-icon": showIcon ? "true" : "false",
639
652
  "data-dismissible": record.dismissible ? "true" : "false",
653
+ "data-compact": record.compact ? "true" : "false",
640
654
  "data-prs-id": record.id,
641
655
  "data-position": record.position,
642
656
  style,
@@ -736,6 +750,9 @@ var POSITIONS = [
736
750
  "bottom-center",
737
751
  "bottom-right"
738
752
  ];
753
+ function defaultMinimizedLabel(count) {
754
+ return `${count} notification${count === 1 ? "" : "s"}`;
755
+ }
739
756
  function SnackbarHost({
740
757
  store,
741
758
  maxVisible = 3,
@@ -753,6 +770,7 @@ function SnackbarHost({
753
770
  closeIcon,
754
771
  closeLabel = "Dismiss notification",
755
772
  regionLabel = "Notifications",
773
+ minimizedLabel = defaultMinimizedLabel,
756
774
  className,
757
775
  style,
758
776
  zIndex = 2147483e3,
@@ -763,6 +781,11 @@ function SnackbarHost({
763
781
  store.getSnapshot,
764
782
  store.getServerSnapshot
765
783
  );
784
+ const minimized = react.useSyncExternalStore(
785
+ store.subscribe,
786
+ store.getMinimized,
787
+ store.getServerMinimized
788
+ );
766
789
  const [portalNode, setPortalNode] = react.useState(null);
767
790
  const regionRefs = react.useRef(/* @__PURE__ */ new Map());
768
791
  const topsRef = react.useRef(/* @__PURE__ */ new Map());
@@ -826,6 +849,7 @@ function SnackbarHost({
826
849
  else byPosition.set(record.position, [record]);
827
850
  }
828
851
  const limit = Math.max(1, Math.floor(maxVisible));
852
+ const minimizedPosition = records[0]?.position ?? null;
829
853
  const rootStyle = {
830
854
  "--prs-width": `${width}px`,
831
855
  "--prs-offset": `${offset}px`,
@@ -844,6 +868,32 @@ function SnackbarHost({
844
868
  children: POSITIONS.map((position) => {
845
869
  const items = byPosition.get(position);
846
870
  if (!items || items.length === 0) return null;
871
+ if (minimized) {
872
+ if (position !== minimizedPosition) return null;
873
+ return /* @__PURE__ */ jsxRuntime.jsx(
874
+ "section",
875
+ {
876
+ className: "prs-region prs-region--minimized",
877
+ "data-position": position,
878
+ "aria-label": regionLabel,
879
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
880
+ "button",
881
+ {
882
+ type: "button",
883
+ className: "prs-minimized",
884
+ "data-count": items.length,
885
+ onClick: () => store.setMinimized(false),
886
+ title: "Expand notifications",
887
+ children: [
888
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "prs-minimized__dot", "aria-hidden": "true" }),
889
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "prs-minimized__count", children: minimizedLabel(items.length) })
890
+ ]
891
+ }
892
+ )
893
+ },
894
+ position
895
+ );
896
+ }
847
897
  const visible = items.slice(0, limit);
848
898
  return /* @__PURE__ */ jsxRuntime.jsx(
849
899
  "section",
@@ -887,6 +937,7 @@ function SnackbarProvider({
887
937
  duration,
888
938
  dismissible,
889
939
  progress,
940
+ compact,
890
941
  tone,
891
942
  size,
892
943
  maxVisible,
@@ -908,11 +959,22 @@ function SnackbarProvider({
908
959
  duration,
909
960
  dismissible,
910
961
  progress,
962
+ compact,
911
963
  tone,
912
964
  size,
913
965
  maxVisible
914
966
  });
915
- }, [store, position, duration, dismissible, progress, tone, size, maxVisible]);
967
+ }, [
968
+ store,
969
+ position,
970
+ duration,
971
+ dismissible,
972
+ progress,
973
+ compact,
974
+ tone,
975
+ size,
976
+ maxVisible
977
+ ]);
916
978
  const api = react.useMemo(() => {
917
979
  const preset2 = (variant) => (message, options = {}) => store.show({ ...options, message, variant });
918
980
  return {
@@ -933,6 +995,9 @@ function SnackbarProvider({
933
995
  variant: "loading",
934
996
  duration: options.duration !== void 0 ? options.duration : null
935
997
  }),
998
+ minimize: () => store.setMinimized(true),
999
+ expand: () => store.setMinimized(false),
1000
+ isMinimized: () => store.getMinimized(),
936
1001
  store
937
1002
  };
938
1003
  }, [store]);
@@ -997,6 +1062,9 @@ function createToast() {
997
1062
  duration: options.duration !== void 0 ? options.duration : null
998
1063
  });
999
1064
  toast2.promise = (input, messages, options) => getDefaultStore().promise(input, messages, options);
1065
+ toast2.minimize = () => getDefaultStore().setMinimized(true);
1066
+ toast2.expand = () => getDefaultStore().setMinimized(false);
1067
+ toast2.isMinimized = () => getDefaultStore().getMinimized();
1000
1068
  Object.defineProperty(toast2, "store", {
1001
1069
  get: getDefaultStore,
1002
1070
  enumerable: true