rnnovaui 0.7.0 → 0.7.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -6,6 +6,7 @@ import {
6
6
  Pressable,
7
7
  StyleSheet,
8
8
  Text,
9
+ TextInput,
9
10
  View,
10
11
  } from "react-native";
11
12
 
@@ -3,35 +3,142 @@ import React, {
3
3
  useCallback,
4
4
  useContext,
5
5
  useMemo,
6
+ useRef,
6
7
  useState,
7
8
  } from "react";
8
9
 
10
+ import { StyleSheet, View } from "react-native";
11
+
9
12
  import { UIToast } from "./UIToast";
10
13
 
11
14
  const ToastContext = createContext(null);
12
15
 
13
- function UIToastProvider({ children, maxToasts = 3 }) {
16
+ const DEFAULT_MAX_TOASTS = 3;
17
+
18
+ let toastIdCounter = 0;
19
+
20
+ function createToastId() {
21
+ toastIdCounter += 1;
22
+
23
+ return `ui-toast-${Date.now()}-${toastIdCounter}`;
24
+ }
25
+
26
+ function UIToastProvider({
27
+ children,
28
+ maxToasts = DEFAULT_MAX_TOASTS,
29
+ defaultDuration = 3000,
30
+ }) {
14
31
  const [toasts, setToasts] = useState([]);
15
32
 
33
+ const mountedRef = useRef(true);
34
+
35
+ const timersRef = useRef(new Map());
36
+
16
37
  const removeToast = useCallback((id) => {
38
+ const timer = timersRef.current.get(id);
39
+
40
+ if (timer) {
41
+ clearTimeout(timer);
42
+
43
+ timersRef.current.delete(id);
44
+ }
45
+
46
+ if (!mountedRef.current) {
47
+ return;
48
+ }
49
+
17
50
  setToasts((previous) => previous.filter((toast) => toast.id !== id));
18
51
  }, []);
19
52
 
53
+ const scheduleDismiss = useCallback(
54
+ (id, duration) => {
55
+ if (!duration || duration <= 0) {
56
+ return;
57
+ }
58
+
59
+ const existingTimer = timersRef.current.get(id);
60
+
61
+ if (existingTimer) {
62
+ clearTimeout(existingTimer);
63
+ }
64
+
65
+ const timer = setTimeout(() => {
66
+ timersRef.current.delete(id);
67
+
68
+ removeToast(id);
69
+ }, duration);
70
+
71
+ timersRef.current.set(id, timer);
72
+ },
73
+ [removeToast],
74
+ );
75
+
20
76
  const showToast = useCallback(
21
- (options) => {
22
- const id = options?.id || `${Date.now()}-${Math.random()}`;
77
+ (options = {}) => {
78
+ const id = options.id || createToastId();
79
+
80
+ const duration =
81
+ options.duration !== undefined ? options.duration : defaultDuration;
23
82
 
24
83
  const toast = {
25
84
  ...options,
85
+
26
86
  id,
87
+
27
88
  visible: true,
89
+
90
+ duration,
28
91
  };
29
92
 
30
- setToasts((previous) => [...previous.slice(-(maxToasts - 1)), toast]);
93
+ setToasts((previous) => {
94
+ const filtered = previous.filter((item) => item.id !== id);
95
+
96
+ const next = [...filtered, toast];
97
+
98
+ if (next.length <= maxToasts) {
99
+ return next;
100
+ }
101
+
102
+ const removed = next.slice(0, next.length - maxToasts);
103
+
104
+ removed.forEach((item) => {
105
+ const timer = timersRef.current.get(item.id);
106
+
107
+ if (timer) {
108
+ clearTimeout(timer);
109
+
110
+ timersRef.current.delete(item.id);
111
+ }
112
+ });
113
+
114
+ return next.slice(-maxToasts);
115
+ });
116
+
117
+ scheduleDismiss(id, duration);
31
118
 
32
119
  return id;
33
120
  },
34
- [maxToasts],
121
+ [defaultDuration, maxToasts, scheduleDismiss],
122
+ );
123
+
124
+ const updateToast = useCallback(
125
+ (id, updates = {}) => {
126
+ setToasts((previous) =>
127
+ previous.map((toast) =>
128
+ toast.id === id
129
+ ? {
130
+ ...toast,
131
+ ...updates,
132
+ }
133
+ : toast,
134
+ ),
135
+ );
136
+
137
+ if (updates.duration !== undefined) {
138
+ scheduleDismiss(id, updates.duration);
139
+ }
140
+ },
141
+ [scheduleDismiss],
35
142
  );
36
143
 
37
144
  const dismissToast = useCallback(
@@ -41,75 +148,176 @@ function UIToastProvider({ children, maxToasts = 3 }) {
41
148
  [removeToast],
42
149
  );
43
150
 
151
+ const dismissAll = useCallback(() => {
152
+ timersRef.current.forEach((timer) => {
153
+ clearTimeout(timer);
154
+ });
155
+
156
+ timersRef.current.clear();
157
+
158
+ setToasts([]);
159
+ }, []);
160
+
44
161
  const success = useCallback(
45
- (message, options = {}) =>
46
- showToast({
162
+ (message, options = {}) => {
163
+ return showToast({
47
164
  ...options,
165
+
48
166
  message,
167
+
49
168
  variant: "success",
50
- }),
169
+ });
170
+ },
51
171
  [showToast],
52
172
  );
53
173
 
54
174
  const error = useCallback(
55
- (message, options = {}) =>
56
- showToast({
175
+ (message, options = {}) => {
176
+ return showToast({
57
177
  ...options,
178
+
58
179
  message,
180
+
59
181
  variant: "danger",
60
- }),
182
+ });
183
+ },
61
184
  [showToast],
62
185
  );
63
186
 
64
187
  const warning = useCallback(
65
- (message, options = {}) =>
66
- showToast({
188
+ (message, options = {}) => {
189
+ return showToast({
67
190
  ...options,
191
+
68
192
  message,
193
+
69
194
  variant: "warning",
70
- }),
195
+ });
196
+ },
71
197
  [showToast],
72
198
  );
73
199
 
74
200
  const info = useCallback(
75
- (message, options = {}) =>
76
- showToast({
201
+ (message, options = {}) => {
202
+ return showToast({
77
203
  ...options,
204
+
78
205
  message,
206
+
79
207
  variant: "info",
80
- }),
208
+ });
209
+ },
81
210
  [showToast],
82
211
  );
83
212
 
84
- const hideAll = useCallback(() => {
85
- setToasts([]);
213
+ const primary = useCallback(
214
+ (message, options = {}) => {
215
+ return showToast({
216
+ ...options,
217
+
218
+ message,
219
+
220
+ variant: "primary",
221
+ });
222
+ },
223
+ [showToast],
224
+ );
225
+
226
+ const toast = useCallback(
227
+ (message, options = {}) => {
228
+ return showToast({
229
+ ...options,
230
+
231
+ message,
232
+
233
+ variant: options.variant || "default",
234
+ });
235
+ },
236
+ [showToast],
237
+ );
238
+
239
+ /*
240
+ * Cleanup timers when
241
+ * provider unmounts.
242
+ */
243
+ React.useEffect(() => {
244
+ mountedRef.current = true;
245
+
246
+ return () => {
247
+ mountedRef.current = false;
248
+
249
+ timersRef.current.forEach((timer) => {
250
+ clearTimeout(timer);
251
+ });
252
+
253
+ timersRef.current.clear();
254
+ };
86
255
  }, []);
87
256
 
88
257
  const contextValue = useMemo(
89
258
  () => ({
259
+ toasts,
260
+
90
261
  showToast,
262
+
263
+ updateToast,
264
+
91
265
  dismissToast,
266
+
267
+ dismissAll,
268
+
269
+ toast,
270
+
92
271
  success,
272
+
93
273
  error,
274
+
94
275
  warning,
276
+
95
277
  info,
96
- hideAll,
278
+
279
+ primary,
97
280
  }),
98
- [showToast, dismissToast, success, error, warning, info, hideAll],
281
+ [
282
+ toasts,
283
+ showToast,
284
+ updateToast,
285
+ dismissToast,
286
+ dismissAll,
287
+ toast,
288
+ success,
289
+ error,
290
+ warning,
291
+ info,
292
+ primary,
293
+ ],
99
294
  );
100
295
 
101
296
  return (
102
297
  <ToastContext.Provider value={contextValue}>
103
298
  {children}
104
299
 
300
+ <ToastContainer toasts={toasts} onDismiss={dismissToast} />
301
+ </ToastContext.Provider>
302
+ );
303
+ }
304
+
305
+ function ToastContainer({ toasts, onDismiss }) {
306
+ /*
307
+ * Keep toast rendering
308
+ * outside the main application
309
+ * layout.
310
+ */
311
+ return (
312
+ <View pointerEvents="box-none" style={styles.container}>
105
313
  {toasts.map((toast) => (
106
314
  <UIToast
107
315
  key={toast.id}
108
316
  {...toast}
109
- onDismiss={() => removeToast(toast.id)}
317
+ onDismiss={() => onDismiss(toast.id)}
110
318
  />
111
319
  ))}
112
- </ToastContext.Provider>
320
+ </View>
113
321
  );
114
322
  }
115
323
 
@@ -123,4 +331,35 @@ export function useUIToast() {
123
331
  return context;
124
332
  }
125
333
 
334
+ /*
335
+ * IMPORTANT:
336
+ * Named export.
337
+ *
338
+ * This prevents:
339
+ *
340
+ * Cannot read property
341
+ * 'displayName' of undefined
342
+ */
343
+ export { UIToastProvider };
344
+
126
345
  export default UIToastProvider;
346
+
347
+ const styles = StyleSheet.create({
348
+ container: {
349
+ position: "absolute",
350
+
351
+ top: 0,
352
+
353
+ right: 0,
354
+
355
+ bottom: 0,
356
+
357
+ left: 0,
358
+
359
+ zIndex: 99999,
360
+
361
+ elevation: 99999,
362
+
363
+ pointerEvents: "box-none",
364
+ },
365
+ });