react-native-toast-message-library 0.0.0-stage → 0.1.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 +20 -0
- package/LICENSE +21 -0
- package/README.md +333 -2
- package/package.json +82 -4
- package/src/animations/toastAnimations.js +85 -0
- package/src/components/ToastAction.js +39 -0
- package/src/components/ToastContainer.js +109 -0
- package/src/components/ToastIcon.js +65 -0
- package/src/components/ToastItem.js +357 -0
- package/src/components/ToastProgress.js +69 -0
- package/src/components/ToastProvider.js +107 -0
- package/src/context/ToastContext.js +5 -0
- package/src/global.js +38 -0
- package/src/hooks/useReducedMotion.js +28 -0
- package/src/hooks/useToast.js +13 -0
- package/src/index.js +4 -0
- package/src/manager/ToastManager.js +399 -0
- package/src/manager/createToastApi.js +33 -0
- package/src/themes/dark.js +64 -0
- package/src/themes/index.js +73 -0
- package/src/themes/light.js +64 -0
- package/src/utils/constants.js +40 -0
- package/src/utils/createAnnouncer.js +46 -0
- package/src/utils/generateToastId.js +10 -0
- package/src/utils/normalizeToastOptions.js +42 -0
- package/src/utils/safeArea.js +34 -0
- package/src/utils/validateToastOptions.js +127 -0
- package/types/index.d.ts +203 -0
package/src/global.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
let active = null;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Called by <ToastProvider global>. Returns an unregister function. If several
|
|
5
|
+
* global providers are mounted, the most recently mounted one wins and the
|
|
6
|
+
* previous one is restored when it unmounts.
|
|
7
|
+
*/
|
|
8
|
+
export function registerGlobalToast(api) {
|
|
9
|
+
const previous = active;
|
|
10
|
+
active = api;
|
|
11
|
+
return () => {
|
|
12
|
+
if (active === api) {
|
|
13
|
+
active = previous;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function current() {
|
|
19
|
+
if (!active) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'[react-native-toast-message-library] Toast.* was called but no <ToastProvider global> is mounted. ' +
|
|
22
|
+
'Render <ToastProvider global> near the root of your app, or use the useToast() hook.',
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return active;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Imperative API. Requires <ToastProvider global>. Throws if none is mounted. */
|
|
29
|
+
export const Toast = {
|
|
30
|
+
show: input => current().show(input),
|
|
31
|
+
success: (message, options) => current().success(message, options),
|
|
32
|
+
error: (message, options) => current().error(message, options),
|
|
33
|
+
warning: (message, options) => current().warning(message, options),
|
|
34
|
+
info: (message, options) => current().info(message, options),
|
|
35
|
+
hide: id => current().hide(id),
|
|
36
|
+
hideAll: () => current().hideAll(),
|
|
37
|
+
isVisible: id => current().isVisible(id),
|
|
38
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { AccessibilityInfo } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/** Tracks the system "reduce motion" accessibility setting. */
|
|
5
|
+
export default function useReducedMotion() {
|
|
6
|
+
const [reduced, setReduced] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
let mounted = true;
|
|
10
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
11
|
+
.then(value => {
|
|
12
|
+
if (mounted) {
|
|
13
|
+
setReduced(Boolean(value));
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
.catch(() => {});
|
|
17
|
+
const subscription = AccessibilityInfo.addEventListener(
|
|
18
|
+
'reduceMotionChanged',
|
|
19
|
+
value => setReduced(Boolean(value)),
|
|
20
|
+
);
|
|
21
|
+
return () => {
|
|
22
|
+
mounted = false;
|
|
23
|
+
subscription.remove();
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return reduced;
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import ToastContext from '../context/ToastContext';
|
|
3
|
+
|
|
4
|
+
export default function useToast() {
|
|
5
|
+
const api = useContext(ToastContext);
|
|
6
|
+
if (!api) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
'[react-native-toast-message-library] useToast() was called outside of a <ToastProvider>. ' +
|
|
9
|
+
'Wrap your app (usually in App.js) with <ToastProvider>...</ToastProvider>.',
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return api;
|
|
13
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG } from '../utils/constants';
|
|
2
|
+
import generateToastId from '../utils/generateToastId';
|
|
3
|
+
import normalizeToastOptions from '../utils/normalizeToastOptions';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ToastManager is a framework-agnostic store. It owns:
|
|
7
|
+
* - the list of toasts currently rendered ("entries", visible or exiting),
|
|
8
|
+
* - the FIFO queue of toasts waiting for a free slot,
|
|
9
|
+
* - the auto-dismiss timers (including pause / resume).
|
|
10
|
+
*
|
|
11
|
+
* React components only render snapshots and report animation completion via
|
|
12
|
+
* `remove(id)`. Keeping all timing logic here makes it deterministic and testable.
|
|
13
|
+
*
|
|
14
|
+
* Slot rules: a toast occupies one of the `maxVisible` slots from the moment it
|
|
15
|
+
* is activated until its exit animation has finished (`remove`). A queued toast
|
|
16
|
+
* is promoted as soon as a slot frees up.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Extra time (ms) after which an exiting toast is force-removed if the view
|
|
20
|
+
// never reported that its exit animation finished (e.g. the view was unmounted).
|
|
21
|
+
const EXIT_SAFETY_MARGIN = 400;
|
|
22
|
+
|
|
23
|
+
function reportError(error) {
|
|
24
|
+
// Errors thrown by user callbacks must not corrupt the manager state.
|
|
25
|
+
console.error('[react-native-toast-message-library] Error in toast callback:', error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function safeCall(fn, ...args) {
|
|
29
|
+
if (typeof fn !== 'function') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
fn(...args);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
reportError(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default class ToastManager {
|
|
40
|
+
constructor(config = {}) {
|
|
41
|
+
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
42
|
+
this._entries = [];
|
|
43
|
+
this._queue = [];
|
|
44
|
+
this._autoTimers = new Map();
|
|
45
|
+
this._exitTimers = new Map();
|
|
46
|
+
this._listeners = new Set();
|
|
47
|
+
this._destroyed = false;
|
|
48
|
+
this._snapshot = Object.freeze({ toasts: [], queued: 0 });
|
|
49
|
+
|
|
50
|
+
this.subscribe = this.subscribe.bind(this);
|
|
51
|
+
this.getSnapshot = this.getSnapshot.bind(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------- store API
|
|
55
|
+
|
|
56
|
+
subscribe(listener) {
|
|
57
|
+
this._listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
this._listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getSnapshot() {
|
|
64
|
+
return this._snapshot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_emit() {
|
|
68
|
+
this._snapshot = Object.freeze({
|
|
69
|
+
toasts: this._entries.slice(),
|
|
70
|
+
queued: this._queue.length,
|
|
71
|
+
});
|
|
72
|
+
Array.from(this._listeners).forEach(listener => listener());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ------------------------------------------------------------ configuration
|
|
76
|
+
|
|
77
|
+
configure(config = {}) {
|
|
78
|
+
const previousMax = this._config.maxVisible;
|
|
79
|
+
this._config = { ...this._config, ...config };
|
|
80
|
+
if (this._config.maxVisible > previousMax && this._promote()) {
|
|
81
|
+
this._emit();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getConfig() {
|
|
86
|
+
return this._config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -------------------------------------------------------------- public API
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Shows (or queues) a toast.
|
|
93
|
+
* @returns {string|null} the toast id, or null when the manager was destroyed
|
|
94
|
+
* @throws {TypeError} when the options are invalid
|
|
95
|
+
*/
|
|
96
|
+
show(input) {
|
|
97
|
+
const options = normalizeToastOptions(input, this._config);
|
|
98
|
+
|
|
99
|
+
if (this._destroyed) {
|
|
100
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
101
|
+
console.warn(
|
|
102
|
+
'[react-native-toast-message-library] A toast was requested after its ToastProvider unmounted. It was ignored.',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const id = this._resolveId(options.id);
|
|
109
|
+
options.id = id;
|
|
110
|
+
|
|
111
|
+
// Showing an id that already exists updates that toast in place.
|
|
112
|
+
const entryIndex = this._entries.findIndex(entry => entry.id === id);
|
|
113
|
+
if (entryIndex !== -1) {
|
|
114
|
+
this._clearExitTimer(id);
|
|
115
|
+
this._clearAutoTimer(id);
|
|
116
|
+
this._entries[entryIndex] = this._createEntry(id, options);
|
|
117
|
+
this._startAutoTimer(this._entries[entryIndex]);
|
|
118
|
+
this._emit();
|
|
119
|
+
return id;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const queueIndex = this._queue.findIndex(item => item.id === id);
|
|
123
|
+
if (queueIndex !== -1) {
|
|
124
|
+
this._queue[queueIndex] = { id, options };
|
|
125
|
+
return id;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this._entries.length < this._config.maxVisible) {
|
|
129
|
+
this._activate({ id, options });
|
|
130
|
+
this._emit();
|
|
131
|
+
} else {
|
|
132
|
+
this._queue.push({ id, options });
|
|
133
|
+
this._emit();
|
|
134
|
+
}
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Dismisses a toast by id. Works for visible and queued toasts.
|
|
140
|
+
* @returns {boolean} true if a toast was dismissed by this call
|
|
141
|
+
*/
|
|
142
|
+
hide(id, reason = 'manual') {
|
|
143
|
+
if (id == null) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const key = String(id);
|
|
147
|
+
|
|
148
|
+
const queueIndex = this._queue.findIndex(item => item.id === key);
|
|
149
|
+
if (queueIndex !== -1) {
|
|
150
|
+
const [removed] = this._queue.splice(queueIndex, 1);
|
|
151
|
+
this._emit();
|
|
152
|
+
safeCall(removed.options.onDismiss, reason, key);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const entry = this._entries.find(item => item.id === key);
|
|
157
|
+
if (!entry || entry.status !== 'visible') {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
this._beginExit(entry, reason);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Dismisses every visible and queued toast.
|
|
166
|
+
* @returns {number} how many toasts were dismissed
|
|
167
|
+
*/
|
|
168
|
+
hideAll() {
|
|
169
|
+
const queued = this._queue;
|
|
170
|
+
this._queue = [];
|
|
171
|
+
const visible = this._entries.filter(entry => entry.status === 'visible');
|
|
172
|
+
|
|
173
|
+
visible.forEach(entry => this._beginExit(entry, 'hideAll', false));
|
|
174
|
+
this._emit();
|
|
175
|
+
|
|
176
|
+
queued.forEach(item =>
|
|
177
|
+
safeCall(item.options.onDismiss, 'hideAll', item.id),
|
|
178
|
+
);
|
|
179
|
+
visible.forEach(entry =>
|
|
180
|
+
safeCall(entry.options.onDismiss, 'hideAll', entry.id),
|
|
181
|
+
);
|
|
182
|
+
return queued.length + visible.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** true if the toast `id` (or any toast, when omitted) is currently shown. */
|
|
186
|
+
isVisible(id) {
|
|
187
|
+
if (id == null) {
|
|
188
|
+
return this._entries.some(entry => entry.status === 'visible');
|
|
189
|
+
}
|
|
190
|
+
const key = String(id);
|
|
191
|
+
return this._entries.some(
|
|
192
|
+
entry => entry.id === key && entry.status === 'visible',
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Whether `id` is waiting in the queue. */
|
|
197
|
+
isQueued(id) {
|
|
198
|
+
const key = String(id);
|
|
199
|
+
return this._queue.some(item => item.id === key);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getQueueLength() {
|
|
203
|
+
return this._queue.length;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ------------------------------------------------- view <-> manager contract
|
|
207
|
+
|
|
208
|
+
/** Pauses the auto-dismiss countdown (user is interacting with the toast). */
|
|
209
|
+
pause(id) {
|
|
210
|
+
const key = String(id);
|
|
211
|
+
const index = this._entries.findIndex(entry => entry.id === key);
|
|
212
|
+
const entry = this._entries[index];
|
|
213
|
+
const timer = this._autoTimers.get(key);
|
|
214
|
+
if (!entry || entry.status !== 'visible' || entry.paused || !timer) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
clearTimeout(timer.handle);
|
|
218
|
+
timer.handle = null;
|
|
219
|
+
timer.remaining = Math.max(
|
|
220
|
+
0,
|
|
221
|
+
timer.remaining - (Date.now() - timer.startedAt),
|
|
222
|
+
);
|
|
223
|
+
this._entries[index] = {
|
|
224
|
+
...entry,
|
|
225
|
+
paused: true,
|
|
226
|
+
remaining: timer.remaining,
|
|
227
|
+
};
|
|
228
|
+
this._emit();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Resumes the countdown with the time that was left when it was paused. */
|
|
232
|
+
resume(id) {
|
|
233
|
+
const key = String(id);
|
|
234
|
+
const index = this._entries.findIndex(entry => entry.id === key);
|
|
235
|
+
const entry = this._entries[index];
|
|
236
|
+
const timer = this._autoTimers.get(key);
|
|
237
|
+
if (!entry || entry.status !== 'visible' || !entry.paused || !timer) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this._entries[index] = { ...entry, paused: false };
|
|
241
|
+
this._armTimer(key, timer);
|
|
242
|
+
this._emit();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Called by the view once the exit animation has finished. Frees the slot
|
|
247
|
+
* and promotes the next queued toast. Safe to call more than once.
|
|
248
|
+
*/
|
|
249
|
+
remove(id) {
|
|
250
|
+
const key = String(id);
|
|
251
|
+
const index = this._entries.findIndex(entry => entry.id === key);
|
|
252
|
+
if (index === -1 || this._entries[index].status !== 'exiting') {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this._clearExitTimer(key);
|
|
256
|
+
this._clearAutoTimer(key);
|
|
257
|
+
this._entries.splice(index, 1);
|
|
258
|
+
this._promote();
|
|
259
|
+
this._emit();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Re-enables a destroyed manager (React StrictMode re-runs effects). */
|
|
263
|
+
revive() {
|
|
264
|
+
this._destroyed = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Clears every timer and listener. Called when the provider unmounts. */
|
|
268
|
+
destroy() {
|
|
269
|
+
this._autoTimers.forEach(timer => clearTimeout(timer.handle));
|
|
270
|
+
this._exitTimers.forEach(handle => clearTimeout(handle));
|
|
271
|
+
this._autoTimers.clear();
|
|
272
|
+
this._exitTimers.clear();
|
|
273
|
+
this._entries = [];
|
|
274
|
+
this._queue = [];
|
|
275
|
+
this._listeners.clear();
|
|
276
|
+
this._destroyed = true;
|
|
277
|
+
this._snapshot = Object.freeze({ toasts: [], queued: 0 });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ------------------------------------------------------------------ internals
|
|
281
|
+
|
|
282
|
+
_resolveId(requested) {
|
|
283
|
+
if (requested != null) {
|
|
284
|
+
return String(requested);
|
|
285
|
+
}
|
|
286
|
+
let id = generateToastId();
|
|
287
|
+
while (this._hasId(id)) {
|
|
288
|
+
id = generateToastId();
|
|
289
|
+
}
|
|
290
|
+
return id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_hasId(id) {
|
|
294
|
+
return (
|
|
295
|
+
this._entries.some(entry => entry.id === id) ||
|
|
296
|
+
this._queue.some(item => item.id === id)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_createEntry(id, options) {
|
|
301
|
+
return {
|
|
302
|
+
id,
|
|
303
|
+
options,
|
|
304
|
+
status: 'visible',
|
|
305
|
+
paused: false,
|
|
306
|
+
remaining: options.duration,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_activate(item) {
|
|
311
|
+
const entry = this._createEntry(item.id, item.options);
|
|
312
|
+
this._entries.push(entry);
|
|
313
|
+
this._startAutoTimer(entry);
|
|
314
|
+
// onShow runs after the state is consistent.
|
|
315
|
+
safeCall(item.options.onShow, item.id);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
_promote() {
|
|
319
|
+
let promoted = false;
|
|
320
|
+
while (
|
|
321
|
+
this._queue.length > 0 &&
|
|
322
|
+
this._entries.length < this._config.maxVisible
|
|
323
|
+
) {
|
|
324
|
+
this._activate(this._queue.shift());
|
|
325
|
+
promoted = true;
|
|
326
|
+
}
|
|
327
|
+
return promoted;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_startAutoTimer(entry) {
|
|
331
|
+
if (!(entry.options.duration > 0)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const timer = {
|
|
335
|
+
handle: null,
|
|
336
|
+
remaining: entry.options.duration,
|
|
337
|
+
startedAt: 0,
|
|
338
|
+
};
|
|
339
|
+
this._autoTimers.set(entry.id, timer);
|
|
340
|
+
this._armTimer(entry.id, timer);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_armTimer(id, timer) {
|
|
344
|
+
timer.startedAt = Date.now();
|
|
345
|
+
timer.handle = setTimeout(() => {
|
|
346
|
+
// Ignore timers that were replaced or cleared in the meantime.
|
|
347
|
+
if (this._autoTimers.get(id) !== timer) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
this._autoTimers.delete(id);
|
|
351
|
+
this.hide(id, 'timeout');
|
|
352
|
+
}, timer.remaining);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_clearAutoTimer(id) {
|
|
356
|
+
const timer = this._autoTimers.get(id);
|
|
357
|
+
if (timer) {
|
|
358
|
+
clearTimeout(timer.handle);
|
|
359
|
+
this._autoTimers.delete(id);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_clearExitTimer(id) {
|
|
364
|
+
const handle = this._exitTimers.get(id);
|
|
365
|
+
if (handle != null) {
|
|
366
|
+
clearTimeout(handle);
|
|
367
|
+
this._exitTimers.delete(id);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_beginExit(entry, reason, notify = true) {
|
|
372
|
+
this._clearAutoTimer(entry.id);
|
|
373
|
+
const index = this._entries.indexOf(entry);
|
|
374
|
+
if (index === -1) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
this._entries[index] = {
|
|
378
|
+
...entry,
|
|
379
|
+
status: 'exiting',
|
|
380
|
+
paused: false,
|
|
381
|
+
reason,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const wait = (entry.options.animationDuration || 0) + EXIT_SAFETY_MARGIN;
|
|
385
|
+
this._clearExitTimer(entry.id);
|
|
386
|
+
this._exitTimers.set(
|
|
387
|
+
entry.id,
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
this._exitTimers.delete(entry.id);
|
|
390
|
+
this.remove(entry.id);
|
|
391
|
+
}, wait),
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
if (notify) {
|
|
395
|
+
this._emit();
|
|
396
|
+
safeCall(entry.options.onDismiss, reason, entry.id);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function isObject(value) {
|
|
2
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds the object returned by useToast() from a ToastManager. The returned
|
|
7
|
+
* object is stable for the lifetime of the manager, so consumers do not
|
|
8
|
+
* re-render when toasts appear or disappear.
|
|
9
|
+
*/
|
|
10
|
+
export default function createToastApi(manager) {
|
|
11
|
+
const typed = type => (message, options) => {
|
|
12
|
+
let input;
|
|
13
|
+
if (typeof message === 'string' || typeof message === 'number') {
|
|
14
|
+
input = { ...options, message: String(message) };
|
|
15
|
+
} else if (isObject(message)) {
|
|
16
|
+
input = { ...message, ...options };
|
|
17
|
+
} else {
|
|
18
|
+
input = { ...options };
|
|
19
|
+
}
|
|
20
|
+
return manager.show({ ...input, type });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
show: input => manager.show(input),
|
|
25
|
+
success: typed('success'),
|
|
26
|
+
error: typed('error'),
|
|
27
|
+
warning: typed('warning'),
|
|
28
|
+
info: typed('info'),
|
|
29
|
+
hide: id => manager.hide(id),
|
|
30
|
+
hideAll: () => manager.hideAll(),
|
|
31
|
+
isVisible: id => manager.isVisible(id),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const dark = {
|
|
2
|
+
name: 'dark',
|
|
3
|
+
colors: {
|
|
4
|
+
success: {
|
|
5
|
+
background: '#0B2E1F',
|
|
6
|
+
text: '#D1FADF',
|
|
7
|
+
border: '#05603A',
|
|
8
|
+
icon: '#32D583',
|
|
9
|
+
iconText: '#052E1B',
|
|
10
|
+
},
|
|
11
|
+
error: {
|
|
12
|
+
background: '#3A1210',
|
|
13
|
+
text: '#FEE4E2',
|
|
14
|
+
border: '#912018',
|
|
15
|
+
icon: '#F97066',
|
|
16
|
+
iconText: '#3A0C09',
|
|
17
|
+
},
|
|
18
|
+
warning: {
|
|
19
|
+
background: '#3B2008',
|
|
20
|
+
text: '#FEF0C7',
|
|
21
|
+
border: '#93370D',
|
|
22
|
+
icon: '#FDB022',
|
|
23
|
+
iconText: '#3B1E04',
|
|
24
|
+
},
|
|
25
|
+
info: {
|
|
26
|
+
background: '#0B2447',
|
|
27
|
+
text: '#D1E9FF',
|
|
28
|
+
border: '#175CD3',
|
|
29
|
+
icon: '#53B1FD',
|
|
30
|
+
iconText: '#06224A',
|
|
31
|
+
},
|
|
32
|
+
custom: {
|
|
33
|
+
background: '#1D2939',
|
|
34
|
+
text: '#F2F4F7',
|
|
35
|
+
border: '#344054',
|
|
36
|
+
icon: '#D0D5DD',
|
|
37
|
+
iconText: '#1D2939',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
actionColor: undefined,
|
|
41
|
+
closeColor: undefined,
|
|
42
|
+
progressTrack: 'rgba(255,255,255,0.14)',
|
|
43
|
+
borderRadius: 14,
|
|
44
|
+
borderWidth: 1,
|
|
45
|
+
padding: 14,
|
|
46
|
+
spacing: 10,
|
|
47
|
+
iconSize: 24,
|
|
48
|
+
typography: {
|
|
49
|
+
titleSize: 15,
|
|
50
|
+
titleWeight: '700',
|
|
51
|
+
messageSize: 14,
|
|
52
|
+
messageWeight: '400',
|
|
53
|
+
fontFamily: undefined,
|
|
54
|
+
},
|
|
55
|
+
shadow: {
|
|
56
|
+
color: '#000000',
|
|
57
|
+
opacity: 0.4,
|
|
58
|
+
radius: 12,
|
|
59
|
+
offsetY: 4,
|
|
60
|
+
elevation: 8,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default dark;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import light from './light';
|
|
2
|
+
import dark from './dark';
|
|
3
|
+
|
|
4
|
+
const builtIn = { light, dark };
|
|
5
|
+
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Recursively merges `override` into `base` without mutating either. */
|
|
11
|
+
export function deepMerge(base, override) {
|
|
12
|
+
if (!isPlainObject(override)) {
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
const result = { ...base };
|
|
16
|
+
Object.keys(override).forEach(key => {
|
|
17
|
+
const value = override[key];
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
result[key] =
|
|
22
|
+
isPlainObject(value) && isPlainObject(base[key])
|
|
23
|
+
? deepMerge(base[key], value)
|
|
24
|
+
: value;
|
|
25
|
+
});
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds a theme from a built-in base and partial overrides.
|
|
31
|
+
* createTheme('dark', { borderRadius: 4 })
|
|
32
|
+
*/
|
|
33
|
+
export function createTheme(base = 'light', overrides = {}) {
|
|
34
|
+
const baseTheme = builtIn[base];
|
|
35
|
+
if (!baseTheme) {
|
|
36
|
+
throw new TypeError(
|
|
37
|
+
`[react-native-toast-message-library] Unknown base theme "${String(
|
|
38
|
+
base,
|
|
39
|
+
)}". Expected "light" or "dark".`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return deepMerge(baseTheme, overrides);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolves the provider `theme` prop to a complete theme object.
|
|
47
|
+
* - 'light' | 'dark' -> built-in theme
|
|
48
|
+
* - 'system' -> built-in theme matching `colorScheme`
|
|
49
|
+
* - object -> merged over the light theme, or over the theme named
|
|
50
|
+
* by its optional `extends` key.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveTheme(theme = 'light', colorScheme = 'light') {
|
|
53
|
+
if (theme === 'system') {
|
|
54
|
+
return colorScheme === 'dark' ? dark : light;
|
|
55
|
+
}
|
|
56
|
+
if (typeof theme === 'string') {
|
|
57
|
+
if (!builtIn[theme]) {
|
|
58
|
+
throw new TypeError(
|
|
59
|
+
`[react-native-toast-message-library] Unknown theme "${theme}". Use "light", "dark", "system" or a theme object.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return builtIn[theme];
|
|
63
|
+
}
|
|
64
|
+
if (isPlainObject(theme)) {
|
|
65
|
+
const { extends: baseName, ...overrides } = theme;
|
|
66
|
+
return createTheme(baseName || 'light', overrides);
|
|
67
|
+
}
|
|
68
|
+
throw new TypeError(
|
|
69
|
+
'[react-native-toast-message-library] "theme" must be "light", "dark", "system" or an object.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { light, dark };
|