@xsolla/xui-toast 0.148.0 → 0.148.2

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.
Files changed (2) hide show
  1. package/README.md +543 -0
  2. package/package.json +4 -4
package/README.md ADDED
@@ -0,0 +1,543 @@
1
+ # Toast
2
+
3
+ A cross-platform React toast notification system for displaying brief, auto-dismissing messages. Built on a provider/context architecture with a hook-based API, the Toast package supports multiple variants, configurable positioning, custom durations, custom icons, and programmatic dismiss — on both React (web) and React Native.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @xsolla/xui-toast @xsolla/xui-core
9
+ # or
10
+ yarn add @xsolla/xui-toast @xsolla/xui-core
11
+ ```
12
+
13
+ **Peer dependencies:** `react >= 16.8.0`, `styled-components >= 4`
14
+
15
+ ## Architecture
16
+
17
+ The toast system uses a **Provider → Context → Hook** pattern:
18
+
19
+ ```
20
+ ┌─────────────────────────────────────────────────┐
21
+ │ ToastProvider │
22
+ │ ┌────────────┐ ┌───────────────────────────┐ │
23
+ │ │ ToastContext│ │ ToastGroup (portal / abs) │ │
24
+ │ │ - toasts │ │ ┌───────┐ ┌───────┐ │ │
25
+ │ │ - addToast│──▶ │ Toast │ │ Toast │ ... │ │
26
+ │ │ - dismiss │ │ └───────┘ └───────┘ │ │
27
+ │ └────────────┘ └───────────────────────────┘ │
28
+ │ ▲ │
29
+ │ │ useToast() │
30
+ │ ┌─────┴──────┐ │
31
+ │ │ Your App │ │
32
+ │ └────────────┘ │
33
+ └─────────────────────────────────────────────────┘
34
+ ```
35
+
36
+ 1. **`ToastProvider`** — Wraps your application. Manages toast state (an array of `ToastData`), auto-dismiss timers, and renders the `ToastGroup` container.
37
+ 2. **`ToastContext`** — React context that exposes `addToast`, `dismissToast`, and `dismissAllToasts` to descendants.
38
+ 3. **`useToast`** — Consumer hook that provides convenience methods (`success`, `info`, `warning`, `error`, `dismiss`, `dismissAll`).
39
+ 4. **`ToastGroup`** — Renders the stack of visible toasts. On web, uses `ReactDOM.createPortal` to escape z-index stacking contexts. On React Native, uses absolute positioning.
40
+ 5. **`Toast`** — The individual notification component with icon, message, and optional close button.
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Wrap your app with `ToastProvider`
45
+
46
+ **React (Web):**
47
+
48
+ ```tsx
49
+ import { XUIProvider } from '@xsolla/xui-core';
50
+ import { ToastProvider } from '@xsolla/xui-toast';
51
+
52
+ export default function App() {
53
+ return (
54
+ <XUIProvider initialMode="dark" initialProductContext="b2b">
55
+ <ToastProvider>
56
+ <YourApp />
57
+ </ToastProvider>
58
+ </XUIProvider>
59
+ );
60
+ }
61
+ ```
62
+
63
+ **React Native:**
64
+
65
+ ```tsx
66
+ import React from 'react';
67
+ import { View } from 'react-native';
68
+ import { XUIProvider } from '@xsolla/xui-core';
69
+ import { ToastProvider } from '@xsolla/xui-toast';
70
+ import { MainNavigator } from './navigation';
71
+
72
+ export default function App() {
73
+ return (
74
+ <XUIProvider mode="dark">
75
+ <ToastProvider position="top">
76
+ <View style={{ flex: 1 }}>
77
+ <MainNavigator />
78
+ </View>
79
+ </ToastProvider>
80
+ </XUIProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ > **Important for React Native:** The `ToastProvider` renders toasts using absolute positioning. Ensure your app's root container has `flex: 1` so toasts position correctly relative to the screen.
86
+
87
+ ### 2. Show toasts with `useToast`
88
+
89
+ ```tsx
90
+ import { useToast } from '@xsolla/xui-toast';
91
+
92
+ function SaveButton() {
93
+ const toast = useToast();
94
+
95
+ const handleSave = async () => {
96
+ try {
97
+ await saveData();
98
+ toast.success('Changes saved successfully');
99
+ } catch {
100
+ toast.error('Failed to save changes');
101
+ }
102
+ };
103
+
104
+ return <button onClick={handleSave}>Save</button>;
105
+ }
106
+ ```
107
+
108
+ ## Demo
109
+
110
+ ### Showing Toast Variants
111
+
112
+ Each variant conveys a different semantic meaning and displays a corresponding icon:
113
+
114
+ ```tsx
115
+ import { useToast } from '@xsolla/xui-toast';
116
+
117
+ export default function ToastExample() {
118
+ const toast = useToast();
119
+
120
+ return (
121
+ <div style={{ display: 'flex', gap: 8 }}>
122
+ <button onClick={() => toast.success('Quest has been activated')}>
123
+ Success
124
+ </button>
125
+ <button onClick={() => toast.info('You received 50 Xsolla Points')}>
126
+ Info
127
+ </button>
128
+ <button onClick={() => toast.warning('Your session is about to expire')}>
129
+ Warning
130
+ </button>
131
+ <button onClick={() => toast.error('Failed to save changes')}>
132
+ Error
133
+ </button>
134
+ </div>
135
+ );
136
+ }
137
+ ```
138
+
139
+ ### Showing Toasts (React Native)
140
+
141
+ The `useToast` hook works identically in React Native. Use `onPress` instead of `onClick`:
142
+
143
+ ```tsx
144
+ import React from 'react';
145
+ import { View } from 'react-native';
146
+ import { Button } from '@xsolla/xui-button';
147
+ import { useToast } from '@xsolla/xui-toast';
148
+
149
+ export default function ToastExample() {
150
+ const toast = useToast();
151
+
152
+ return (
153
+ <View style={{ gap: 8 }}>
154
+ <Button onPress={() => toast.success('Quest has been activated')}>
155
+ Success
156
+ </Button>
157
+ <Button onPress={() => toast.info('You received 50 Xsolla Points')}>
158
+ Info
159
+ </Button>
160
+ <Button onPress={() => toast.warning('Your session is about to expire')}>
161
+ Warning
162
+ </Button>
163
+ <Button onPress={() => toast.error('Failed to save changes')}>
164
+ Error
165
+ </Button>
166
+ </View>
167
+ );
168
+ }
169
+ ```
170
+
171
+ ### Toast Variants (Standalone `Toast` Component)
172
+
173
+ The `Toast` component can be used standalone outside the provider for static display or custom layouts:
174
+
175
+ ```tsx
176
+ import { Toast } from '@xsolla/xui-toast';
177
+
178
+ export default function ToastVariants() {
179
+ return (
180
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 400 }}>
181
+ <Toast id="1" variant="success" message="Quest has been activated" onClose={() => {}} />
182
+ <Toast id="2" variant="info" message="You received 50 Xsolla Points" onClose={() => {}} />
183
+ <Toast id="3" variant="warning" message="Your session is about to expire" onClose={() => {}} />
184
+ <Toast id="4" variant="error" message="Failed to save changes" onClose={() => {}} />
185
+ </div>
186
+ );
187
+ }
188
+ ```
189
+
190
+ ### Custom Duration
191
+
192
+ Control how long toasts remain visible. The default is 5000ms. Set `duration: 0` to disable auto-dismiss entirely:
193
+
194
+ ```tsx
195
+ import { useToast } from '@xsolla/xui-toast';
196
+
197
+ export default function CustomDuration() {
198
+ const toast = useToast();
199
+
200
+ return (
201
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
202
+ <button onClick={() => toast.toast({ message: 'Stays for 10 seconds', duration: 10000 })}>
203
+ Long Duration (10s)
204
+ </button>
205
+ <button onClick={() => toast.toast({ message: 'Stays for 2 seconds', duration: 2000 })}>
206
+ Short Duration (2s)
207
+ </button>
208
+ <button onClick={() => toast.toast({ message: 'Dismiss me manually', duration: 0 })}>
209
+ No Auto-Dismiss
210
+ </button>
211
+ </div>
212
+ );
213
+ }
214
+ ```
215
+
216
+ You can also set a global default duration on the provider:
217
+
218
+ ```tsx
219
+ <ToastProvider defaultDuration={3000}>
220
+ <App />
221
+ </ToastProvider>
222
+ ```
223
+
224
+ ### Custom Icons
225
+
226
+ Override the default variant icon by passing a `ReactNode` via the `icon` option:
227
+
228
+ ```tsx
229
+ import { useToast } from '@xsolla/xui-toast';
230
+ import { Star } from '@xsolla/xui-icons';
231
+
232
+ function CustomIconExample() {
233
+ const toast = useToast();
234
+
235
+ return (
236
+ <button
237
+ onClick={() =>
238
+ toast.toast({
239
+ message: 'You earned a gold star!',
240
+ variant: 'success',
241
+ icon: <Star />,
242
+ })
243
+ }
244
+ >
245
+ Show Custom Icon Toast
246
+ </button>
247
+ );
248
+ }
249
+ ```
250
+
251
+ ### Dismissing Toasts Programmatically
252
+
253
+ Every `toast.*()` call returns a unique ID. Use it to dismiss a specific toast:
254
+
255
+ ```tsx
256
+ import * as React from 'react';
257
+ import { useToast } from '@xsolla/xui-toast';
258
+
259
+ export default function DismissExample() {
260
+ const toast = useToast();
261
+ const [lastToastId, setLastToastId] = React.useState<string | null>(null);
262
+
263
+ const showToast = () => {
264
+ const id = toast.info('New notification');
265
+ setLastToastId(id);
266
+ };
267
+
268
+ return (
269
+ <div style={{ display: 'flex', gap: 8 }}>
270
+ <button onClick={showToast}>Show Toast</button>
271
+ <button onClick={() => lastToastId && toast.dismiss(lastToastId)}>
272
+ Dismiss Last
273
+ </button>
274
+ <button onClick={() => toast.dismissAll()}>Dismiss All</button>
275
+ </div>
276
+ );
277
+ }
278
+ ```
279
+
280
+ ### Position Configuration
281
+
282
+ Toasts can be anchored to the top or bottom of the viewport:
283
+
284
+ ```tsx
285
+ import { ToastProvider, useToast } from '@xsolla/xui-toast';
286
+
287
+ function ToastTrigger() {
288
+ const toast = useToast();
289
+ return <button onClick={() => toast.info('Toast at the bottom!')}>Show Toast</button>;
290
+ }
291
+
292
+ export default function BottomPosition() {
293
+ return (
294
+ <ToastProvider position="bottom">
295
+ <ToastTrigger />
296
+ </ToastProvider>
297
+ );
298
+ }
299
+ ```
300
+
301
+ ### Multiple Toasts
302
+
303
+ Multiple toasts stack vertically in the order they are created:
304
+
305
+ ```tsx
306
+ import { useToast } from '@xsolla/xui-toast';
307
+
308
+ function MultipleToastsExample() {
309
+ const toast = useToast();
310
+
311
+ const showMultiple = () => {
312
+ toast.success('First toast - Success');
313
+ setTimeout(() => toast.info('Second toast - Info'), 200);
314
+ setTimeout(() => toast.warning('Third toast - Warning'), 400);
315
+ };
316
+
317
+ return <button onClick={showMultiple}>Show Multiple Toasts</button>;
318
+ }
319
+ ```
320
+
321
+ ## API Reference
322
+
323
+ ### ToastProvider
324
+
325
+ The root provider component that manages toast state and renders the toast container.
326
+
327
+ | Prop | Type | Default | Description |
328
+ | :--- | :--- | :------ | :---------- |
329
+ | `children` | `ReactNode` | — | Application content. |
330
+ | `position` | `"top" \| "bottom"` | `"top"` | Position of the toast container on screen. |
331
+ | `defaultDuration` | `number` | `5000` | Default auto-dismiss duration in milliseconds. Set to `0` to disable auto-dismiss globally. |
332
+ | `maxWidth` | `number` | `100%` | Maximum width of the toast container in pixels. If not set, toasts stretch to full viewport width. |
333
+
334
+ ### useToast
335
+
336
+ A hook that returns methods to show and dismiss toasts. Must be called within a `ToastProvider` — throws an error otherwise.
337
+
338
+ **Return type: `UseToastReturn`**
339
+
340
+ | Method | Signature | Description |
341
+ | :----- | :-------- | :---------- |
342
+ | `toast` | `(options: ToastOptions) => string` | Show a toast with full control over options. Returns the toast ID. |
343
+ | `success` | `(message: string, options?) => string` | Show a success toast with a check icon. |
344
+ | `info` | `(message: string, options?) => string` | Show an info toast with an info icon. |
345
+ | `warning` | `(message: string, options?) => string` | Show a warning toast with an alert icon. |
346
+ | `error` | `(message: string, options?) => string` | Show an error toast with an alert icon. |
347
+ | `dismiss` | `(id: string) => void` | Dismiss a specific toast by its ID. |
348
+ | `dismissAll` | `() => void` | Dismiss all visible toasts at once. |
349
+
350
+ ### Toast
351
+
352
+ The individual toast notification component. Used internally by `ToastGroup`, but can be rendered standalone for static display.
353
+
354
+ | Prop | Type | Default | Description |
355
+ | :--- | :--- | :------ | :---------- |
356
+ | `id` | `string` | — | Unique identifier for the toast (required). |
357
+ | `message` | `string` | — | Toast message text (required). |
358
+ | `variant` | `"success" \| "info" \| "warning" \| "error"` | `"info"` | Visual variant that determines the icon color. |
359
+ | `icon` | `ReactNode` | — | Custom icon element. When omitted, a default icon for the variant is used. |
360
+ | `onClose` | `() => void` | — | Callback fired when the close button is clicked. If omitted, the close button is hidden. |
361
+
362
+ ### ToastOptions
363
+
364
+ Options object passed to the `toast()` method or as the second argument to shorthand methods.
365
+
366
+ | Property | Type | Default | Description |
367
+ | :------- | :--- | :------ | :---------- |
368
+ | `message` | `string` | — | Toast message text (required). |
369
+ | `variant` | `"success" \| "info" \| "warning" \| "error"` | `"info"` | Toast variant. |
370
+ | `icon` | `ReactNode` | — | Custom icon element. |
371
+ | `duration` | `number` | Provider's `defaultDuration` | Auto-dismiss duration in ms. Use `0` to keep the toast until manually dismissed. |
372
+
373
+ ### ToastData
374
+
375
+ Internal data structure for a toast instance (available via `ToastContext`).
376
+
377
+ | Property | Type | Description |
378
+ | :------- | :--- | :---------- |
379
+ | `id` | `string` | Unique identifier generated by the provider. |
380
+ | `variant` | `ToastVariant` | The toast variant. |
381
+ | `message` | `string` | Toast message text. |
382
+ | `icon` | `ReactNode \| undefined` | Custom icon, if provided. |
383
+ | `duration` | `number \| undefined` | Auto-dismiss duration. |
384
+
385
+ ### ToastContext
386
+
387
+ The React context object. Typically consumed via `useToast`, but available for advanced use cases:
388
+
389
+ ```tsx
390
+ import { useContext } from 'react';
391
+ import { ToastContext } from '@xsolla/xui-toast';
392
+
393
+ function CustomToastConsumer() {
394
+ const ctx = useContext(ToastContext);
395
+ if (!ctx) throw new Error('Must be within ToastProvider');
396
+
397
+ return <span>Active toasts: {ctx.toasts.length}</span>;
398
+ }
399
+ ```
400
+
401
+ ### Variant Reference
402
+
403
+ | Variant | Use Case | Default Icon | Icon Color Theme Path |
404
+ | :------ | :------- | :----------- | :-------------------- |
405
+ | `success` | Positive outcomes, confirmations | `<Check />` | `theme.colors.content.success.primary` |
406
+ | `info` | General information, neutral messages | `<AlertCircle />` | `theme.colors.content.inverse` |
407
+ | `warning` | Caution, requires user attention | `<AlertCircle />` | `theme.colors.content.warning.primary` |
408
+ | `error` | Errors, failures, critical issues | `<AlertCircle />` | `theme.colors.content.alert.primary` |
409
+
410
+ ## Theming
411
+
412
+ Toast uses the **inverse** color scheme (dark background with light text) to stand out from the application content:
413
+
414
+ ### Colors
415
+
416
+ ```typescript
417
+ theme.colors.background.inverse // Toast background
418
+ theme.colors.content.inverse // Toast text and info icon color
419
+
420
+ theme.colors.content.success.primary // Success icon color
421
+ theme.colors.content.warning.primary // Warning icon color
422
+ theme.colors.content.alert.primary // Error icon color
423
+ ```
424
+
425
+ ### Sizing
426
+
427
+ Toast dimensions come from `theme.sizing.toast()`:
428
+
429
+ | Token | Value | Description |
430
+ | :---- | :---- | :---------- |
431
+ | `minHeight` | `64` | Minimum toast height |
432
+ | `paddingHorizontal` | `12` | Left/right padding |
433
+ | `paddingVertical` | `8` | Top/bottom padding |
434
+ | `borderRadius` | `4` | Corner radius |
435
+ | `gap` | `12` | Space between icon, text, and close button |
436
+ | `iconSize` | `24` | Icon dimensions |
437
+ | `closeButtonSize` | `24` | Close button tap target |
438
+ | `closeIconSize` | `20` | Close icon dimensions |
439
+ | `fontSize` | `16` | Message text size |
440
+ | `lineHeight` | `20` | Message line height |
441
+ | `maxWidth` | `400` | Maximum toast width (theme default; overridden by `ToastProvider maxWidth` prop) |
442
+ | `containerPadding` | `12` | Padding between toast container and screen edges |
443
+ | `groupGap` | `4` | Vertical gap between stacked toasts |
444
+
445
+ ## Platform Support
446
+
447
+ This package supports both React (web) and React Native with an identical API.
448
+
449
+ ### React (Web)
450
+
451
+ - Uses `ReactDOM.createPortal` to render toasts at the document body level
452
+ - Toasts appear above all other content regardless of z-index stacking contexts
453
+ - Works with any React web framework (Next.js, Vite, Create React App, Remix, etc.)
454
+
455
+ ### React Native
456
+
457
+ - Uses absolute positioning within the app container
458
+ - Toasts render within the component tree (no portal equivalent in RN)
459
+ - Works with React Navigation, Expo, and bare React Native projects
460
+
461
+ ### Cross-Platform Comparison
462
+
463
+ | Feature | Web | React Native |
464
+ | :------ | :-- | :----------- |
465
+ | Provider setup | `ToastProvider` | `XUIProvider` + `ToastProvider` |
466
+ | Hook API | `useToast()` | `useToast()` (identical) |
467
+ | Event handlers | `onClick` | `onPress` |
468
+ | Root container | Any element | Must have `flex: 1` |
469
+ | Position: top | Fixed to viewport top | Absolute to container top |
470
+ | Position: bottom | Fixed to viewport bottom | Absolute to container bottom |
471
+ | Rendering | `ReactDOM.createPortal` into `document.body` | Absolute-positioned `Box` in tree |
472
+
473
+ ### React Native Example with Navigation
474
+
475
+ ```tsx
476
+ import React from 'react';
477
+ import { NavigationContainer } from '@react-navigation/native';
478
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
479
+ import { XUIProvider } from '@xsolla/xui-core';
480
+ import { ToastProvider } from '@xsolla/xui-toast';
481
+ import { RootNavigator } from './navigation';
482
+
483
+ export default function App() {
484
+ return (
485
+ <SafeAreaProvider>
486
+ <XUIProvider mode="dark">
487
+ <ToastProvider position="top">
488
+ <NavigationContainer>
489
+ <RootNavigator />
490
+ </NavigationContainer>
491
+ </ToastProvider>
492
+ </XUIProvider>
493
+ </SafeAreaProvider>
494
+ );
495
+ }
496
+ ```
497
+
498
+ ## Accessibility
499
+
500
+ - Each toast renders with `role="alert"` and `aria-live="polite"` for screen reader announcements
501
+ - The close button includes `aria-label="Dismiss toast"` for assistive technology
502
+ - Icons are decorative and do not interfere with accessibility
503
+ - Keyboard users can Tab to the close button and press Enter to dismiss
504
+ - Toast messages are limited to 2 lines (`numberOfLines={2}`) for readability
505
+
506
+ ## Troubleshooting
507
+
508
+ ### "useToast must be used within a ToastProvider"
509
+
510
+ The `useToast` hook was called in a component that is not a descendant of `ToastProvider`. Ensure your component tree has a `ToastProvider` ancestor:
511
+
512
+ ```tsx
513
+ // Correct — hook is inside the provider tree
514
+ <ToastProvider>
515
+ <ComponentThatCallsUseToast />
516
+ </ToastProvider>
517
+
518
+ // Wrong — hook is outside the provider
519
+ <ComponentThatCallsUseToast />
520
+ <ToastProvider>
521
+ <OtherContent />
522
+ </ToastProvider>
523
+ ```
524
+
525
+ ### Toasts not visible on React Native
526
+
527
+ Make sure the container inside `ToastProvider` has `flex: 1`. Without this, the absolute-positioned toast group has no reference height:
528
+
529
+ ```tsx
530
+ <ToastProvider>
531
+ <View style={{ flex: 1 }}> {/* Required */}
532
+ <App />
533
+ </View>
534
+ </ToastProvider>
535
+ ```
536
+
537
+ ### Toasts appear behind a modal or overlay
538
+
539
+ On web, `ToastGroup` uses `z-index: 9999`. If your modal or overlay uses a higher z-index, the toasts may be hidden. Consider placing the `ToastProvider` above your modal provider in the tree, or adjusting z-index values.
540
+
541
+ ### Auto-dismiss not working
542
+
543
+ Ensure `duration` is not set to `0`. A duration of `0` disables auto-dismiss. Check both the per-toast `duration` option and the provider's `defaultDuration` prop.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsolla/xui-toast",
3
- "version": "0.148.0",
3
+ "version": "0.148.2",
4
4
  "main": "./web/index.js",
5
5
  "module": "./web/index.mjs",
6
6
  "types": "./web/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  "test:coverage": "vitest run --coverage"
15
15
  },
16
16
  "dependencies": {
17
- "@xsolla/xui-core": "0.148.0",
18
- "@xsolla/xui-icons": "0.148.0",
19
- "@xsolla/xui-primitives-core": "0.148.0"
17
+ "@xsolla/xui-core": "0.148.2",
18
+ "@xsolla/xui-icons": "0.148.2",
19
+ "@xsolla/xui-primitives-core": "0.148.2"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "react": ">=16.8.0",