@mushi-mushi/react-native 0.1.0 → 0.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mushi-mushi/react-native
2
2
 
3
- React Native SDK for Mushi Mushi. Shake-to-report, bottom sheet widget, offline queue.
3
+ React Native SDK for Mushi Mushi — built-in bottom-sheet widget, floating bug button, offline queue.
4
4
 
5
5
  ## Usage
6
6
 
@@ -16,20 +16,39 @@ export default function App() {
16
16
  }
17
17
  ```
18
18
 
19
+ The provider renders a floating 🐛 button and a slide-up bottom sheet by default. Control behavior via `widget.trigger`:
20
+
21
+ | Value | Behavior |
22
+ |-------|----------|
23
+ | `'button'` (default) | Shows floating action button |
24
+ | `'manual'` | No auto-UI — call `open()` programmatically |
25
+
19
26
  ### Hooks
20
27
 
21
28
  ```ts
22
- import { useMushi, useMushiReport } from '@mushi-mushi/react-native'
29
+ import { useMushi, useMushiReport, useMushiWidget } from '@mushi-mushi/react-native'
23
30
 
24
31
  const { submitReport } = useMushiReport()
32
+ const { open, close } = useMushiWidget()
33
+ ```
34
+
35
+ ### Standalone Components
36
+
37
+ Use the widget components directly if you need custom integration:
38
+
39
+ ```tsx
40
+ import { MushiBottomSheet, MushiFloatingButton } from '@mushi-mushi/react-native'
25
41
  ```
26
42
 
43
+ - `MushiBottomSheet` — Conversational bug report modal (category → description → submit). Supports dark/light theme, drag-to-dismiss.
44
+ - `MushiFloatingButton` — Positioned FAB with spring animation. Configurable via `buttonPosition`.
45
+
27
46
  ### Captures
28
47
 
29
48
  - Device info (platform, OS, screen dimensions)
30
49
  - Console logs (monkey-patched)
31
- - Network requests (fetch/XMLHttpRequest interceptor)
32
- - Navigation events (React Navigation integration, optional)
50
+ - Network requests (fetch interceptor)
51
+ - Navigation events (React Navigation, optional)
33
52
  - Offline queue via AsyncStorage
34
53
 
35
54
  ## Peer Dependencies
package/dist/index.cjs CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AsyncStorageQueue: () => AsyncStorageQueue,
34
+ MushiBottomSheet: () => MushiBottomSheet,
35
+ MushiFloatingButton: () => MushiFloatingButton,
34
36
  MushiProvider: () => MushiProvider,
35
37
  getDeviceInfo: () => getDeviceInfo,
36
38
  setupConsoleCapture: () => setupConsoleCapture,
@@ -43,7 +45,8 @@ __export(index_exports, {
43
45
  module.exports = __toCommonJS(index_exports);
44
46
 
45
47
  // src/provider.tsx
46
- var import_react = require("react");
48
+ var import_react3 = require("react");
49
+ var import_core = require("@mushi-mushi/core");
47
50
 
48
51
  // src/capture/console-capture.ts
49
52
  function setupConsoleCapture(maxEntries = 100) {
@@ -218,63 +221,472 @@ var AsyncStorageQueue = class {
218
221
  }
219
222
  };
220
223
 
221
- // src/provider.tsx
224
+ // src/components/MushiBottomSheet.tsx
225
+ var import_react = require("react");
226
+ var import_react_native2 = require("react-native");
222
227
  var import_jsx_runtime = require("react/jsx-runtime");
223
- var MushiContext = (0, import_react.createContext)(null);
224
- function MushiProvider({ children, ...config }) {
225
- const consoleRef = (0, import_react.useRef)(null);
226
- const networkRef = (0, import_react.useRef)(null);
227
- const queueRef = (0, import_react.useRef)(null);
228
- const apiEndpoint = config.endpoint ?? "https://api.mushimushi.dev";
228
+ var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
229
+ var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
230
+ var DISMISS_THRESHOLD = 80;
231
+ var CATEGORIES = [
232
+ { key: "bug", emoji: "\u{1F41B}", label: "Bug" },
233
+ { key: "slow", emoji: "\u{1F422}", label: "Slow" },
234
+ { key: "visual", emoji: "\u{1F3A8}", label: "Visual" },
235
+ { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
236
+ { key: "other", emoji: "\u{1F4AC}", label: "Other" }
237
+ ];
238
+ var MushiBottomSheet = ({ visible, onClose }) => {
239
+ const mushi = useMushiContext();
240
+ const scheme = (0, import_react_native2.useColorScheme)();
241
+ const dark = scheme === "dark";
242
+ const translateY = (0, import_react.useRef)(new import_react_native2.Animated.Value(SHEET_HEIGHT)).current;
243
+ const backdropOpacity = (0, import_react.useRef)(new import_react_native2.Animated.Value(0)).current;
244
+ const [category, setCategory] = (0, import_react.useState)(null);
245
+ const [description, setDescription] = (0, import_react.useState)("");
246
+ const [phase, setPhase] = (0, import_react.useState)("form");
247
+ const resetForm = (0, import_react.useCallback)(() => {
248
+ setCategory(null);
249
+ setDescription("");
250
+ setPhase("form");
251
+ }, []);
252
+ const animateIn = (0, import_react.useCallback)(() => {
253
+ import_react_native2.Animated.parallel([
254
+ import_react_native2.Animated.spring(translateY, {
255
+ toValue: 0,
256
+ useNativeDriver: true,
257
+ damping: 20,
258
+ stiffness: 200
259
+ }),
260
+ import_react_native2.Animated.timing(backdropOpacity, {
261
+ toValue: 1,
262
+ duration: 250,
263
+ useNativeDriver: true
264
+ })
265
+ ]).start();
266
+ }, [translateY, backdropOpacity]);
267
+ const animateOut = (0, import_react.useCallback)(
268
+ (cb) => {
269
+ import_react_native2.Animated.parallel([
270
+ import_react_native2.Animated.timing(translateY, {
271
+ toValue: SHEET_HEIGHT,
272
+ duration: 220,
273
+ useNativeDriver: true
274
+ }),
275
+ import_react_native2.Animated.timing(backdropOpacity, {
276
+ toValue: 0,
277
+ duration: 200,
278
+ useNativeDriver: true
279
+ })
280
+ ]).start(() => {
281
+ resetForm();
282
+ cb?.();
283
+ });
284
+ },
285
+ [translateY, backdropOpacity, resetForm]
286
+ );
229
287
  (0, import_react.useEffect)(() => {
288
+ if (visible) animateIn();
289
+ }, [visible, animateIn]);
290
+ const handleClose = (0, import_react.useCallback)(() => {
291
+ animateOut(onClose);
292
+ }, [animateOut, onClose]);
293
+ const panResponder = (0, import_react.useRef)(
294
+ import_react_native2.PanResponder.create({
295
+ onStartShouldSetPanResponder: () => true,
296
+ onMoveShouldSetPanResponder: (_, g) => g.dy > 4,
297
+ onPanResponderMove: (_, g) => {
298
+ if (g.dy > 0) translateY.setValue(g.dy);
299
+ },
300
+ onPanResponderRelease: (_, g) => {
301
+ if (g.dy > DISMISS_THRESHOLD) {
302
+ handleClose();
303
+ } else {
304
+ import_react_native2.Animated.spring(translateY, {
305
+ toValue: 0,
306
+ useNativeDriver: true,
307
+ damping: 20,
308
+ stiffness: 200
309
+ }).start();
310
+ }
311
+ }
312
+ })
313
+ ).current;
314
+ const handleSubmit = async () => {
315
+ if (!category || !description.trim() || !mushi) return;
316
+ setPhase("sending");
317
+ try {
318
+ await mushi.submitReport({ category, description: description.trim() });
319
+ setPhase("sent");
320
+ setTimeout(handleClose, 1400);
321
+ } catch {
322
+ setPhase("form");
323
+ }
324
+ };
325
+ const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)" };
326
+ const canSubmit = !!category && description.trim().length > 0 && phase === "form";
327
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
328
+ import_react_native2.KeyboardAvoidingView,
329
+ {
330
+ style: s.flex,
331
+ behavior: import_react_native2.Platform.OS === "ios" ? "padding" : void 0,
332
+ children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
334
+ import_react_native2.Animated.View,
335
+ {
336
+ style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
337
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: s.flex, activeOpacity: 1, onPress: handleClose })
338
+ }
339
+ ),
340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
341
+ import_react_native2.Animated.View,
342
+ {
343
+ style: [
344
+ s.sheet,
345
+ { backgroundColor: colors.bg, transform: [{ translateY }] }
346
+ ],
347
+ children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
349
+ phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
352
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.body, children: [
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.title, { color: colors.text }], children: "Report an issue" }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
355
+ const active = category === c.key;
356
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
357
+ import_react_native2.TouchableOpacity,
358
+ {
359
+ onPress: () => setCategory(c.key),
360
+ activeOpacity: 0.7,
361
+ style: [
362
+ s.catBtn,
363
+ {
364
+ backgroundColor: active ? colors.accent : colors.card,
365
+ borderColor: active ? colors.accent : colors.border
366
+ }
367
+ ],
368
+ children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: c.emoji }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
371
+ import_react_native2.Text,
372
+ {
373
+ style: [
374
+ s.catLabel,
375
+ { color: active ? "#fff" : colors.text }
376
+ ],
377
+ children: c.label
378
+ }
379
+ )
380
+ ]
381
+ },
382
+ c.key
383
+ );
384
+ }) }),
385
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
386
+ import_react_native2.TextInput,
387
+ {
388
+ style: [
389
+ s.input,
390
+ {
391
+ backgroundColor: colors.card,
392
+ color: colors.text,
393
+ borderColor: colors.border
394
+ }
395
+ ],
396
+ placeholder: "What happened?",
397
+ placeholderTextColor: colors.sub,
398
+ multiline: true,
399
+ textAlignVertical: "top",
400
+ value: description,
401
+ onChangeText: setDescription,
402
+ editable: phase === "form"
403
+ }
404
+ ),
405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
406
+ import_react_native2.TouchableOpacity,
407
+ {
408
+ onPress: handleSubmit,
409
+ disabled: !canSubmit,
410
+ activeOpacity: 0.8,
411
+ style: [
412
+ s.submitBtn,
413
+ { backgroundColor: canSubmit ? colors.accent : colors.border }
414
+ ],
415
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
416
+ }
417
+ )
418
+ ] })
419
+ ]
420
+ }
421
+ )
422
+ ]
423
+ }
424
+ ) });
425
+ };
426
+ var s = import_react_native2.StyleSheet.create({
427
+ flex: { flex: 1 },
428
+ backdrop: {
429
+ ...import_react_native2.StyleSheet.absoluteFillObject
430
+ },
431
+ sheet: {
432
+ position: "absolute",
433
+ bottom: 0,
434
+ left: 0,
435
+ right: 0,
436
+ minHeight: SHEET_HEIGHT,
437
+ borderTopLeftRadius: 20,
438
+ borderTopRightRadius: 20,
439
+ paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : 16,
440
+ shadowColor: "#000",
441
+ shadowOffset: { width: 0, height: -3 },
442
+ shadowOpacity: 0.12,
443
+ shadowRadius: 8,
444
+ elevation: 24
445
+ },
446
+ handleArea: {
447
+ alignItems: "center",
448
+ paddingVertical: 10
449
+ },
450
+ handle: {
451
+ width: 40,
452
+ height: 5,
453
+ borderRadius: 3,
454
+ opacity: 0.5
455
+ },
456
+ body: {
457
+ paddingHorizontal: 20,
458
+ paddingTop: 4
459
+ },
460
+ title: {
461
+ fontSize: 20,
462
+ fontWeight: "700",
463
+ marginBottom: 16
464
+ },
465
+ catRow: {
466
+ flexDirection: "row",
467
+ justifyContent: "space-between",
468
+ marginBottom: 16
469
+ },
470
+ catBtn: {
471
+ alignItems: "center",
472
+ paddingVertical: 10,
473
+ paddingHorizontal: 6,
474
+ borderRadius: 12,
475
+ borderWidth: 1,
476
+ flex: 1,
477
+ marginHorizontal: 3
478
+ },
479
+ catEmoji: {
480
+ fontSize: 22,
481
+ marginBottom: 4
482
+ },
483
+ catLabel: {
484
+ fontSize: 11,
485
+ fontWeight: "600"
486
+ },
487
+ input: {
488
+ borderWidth: 1,
489
+ borderRadius: 12,
490
+ padding: 14,
491
+ fontSize: 15,
492
+ minHeight: 100,
493
+ marginBottom: 16
494
+ },
495
+ submitBtn: {
496
+ borderRadius: 12,
497
+ paddingVertical: 14,
498
+ alignItems: "center"
499
+ },
500
+ submitText: {
501
+ color: "#fff",
502
+ fontSize: 16,
503
+ fontWeight: "700"
504
+ },
505
+ sentWrap: {
506
+ alignItems: "center",
507
+ justifyContent: "center",
508
+ paddingVertical: 60
509
+ },
510
+ sentEmoji: {
511
+ fontSize: 48,
512
+ marginBottom: 12
513
+ },
514
+ sentText: {
515
+ fontSize: 20,
516
+ fontWeight: "600"
517
+ }
518
+ });
519
+
520
+ // src/components/MushiFloatingButton.tsx
521
+ var import_react2 = require("react");
522
+ var import_react_native3 = require("react-native");
523
+ var import_jsx_runtime2 = require("react/jsx-runtime");
524
+ var MushiFloatingButton = ({
525
+ onPress,
526
+ position = "bottom-right"
527
+ }) => {
528
+ const scheme = (0, import_react_native3.useColorScheme)();
529
+ const dark = scheme === "dark";
530
+ const scale = (0, import_react2.useRef)(new import_react_native3.Animated.Value(1)).current;
531
+ const handlePressIn = (0, import_react2.useCallback)(() => {
532
+ import_react_native3.Animated.spring(scale, {
533
+ toValue: 0.85,
534
+ useNativeDriver: true,
535
+ damping: 15,
536
+ stiffness: 300
537
+ }).start();
538
+ }, [scale]);
539
+ const handlePressOut = (0, import_react2.useCallback)(() => {
540
+ import_react_native3.Animated.spring(scale, {
541
+ toValue: 1,
542
+ useNativeDriver: true,
543
+ damping: 10,
544
+ stiffness: 250
545
+ }).start();
546
+ }, [scale]);
547
+ const positionStyle = position === "bottom-left" ? { left: 20 } : { right: 20 };
548
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
549
+ import_react_native3.TouchableWithoutFeedback,
550
+ {
551
+ onPress,
552
+ onPressIn: handlePressIn,
553
+ onPressOut: handlePressOut,
554
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
555
+ import_react_native3.Animated.View,
556
+ {
557
+ style: [
558
+ s2.fab,
559
+ positionStyle,
560
+ {
561
+ backgroundColor: dark ? "#0a84ff" : "#007aff",
562
+ transform: [{ scale }],
563
+ shadowColor: dark ? "#0a84ff" : "#000"
564
+ }
565
+ ],
566
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: "\u{1F41B}" })
567
+ }
568
+ )
569
+ }
570
+ );
571
+ };
572
+ var s2 = import_react_native3.StyleSheet.create({
573
+ fab: {
574
+ position: "absolute",
575
+ bottom: import_react_native3.Platform.OS === "ios" ? 50 : 28,
576
+ width: 56,
577
+ height: 56,
578
+ borderRadius: 28,
579
+ alignItems: "center",
580
+ justifyContent: "center",
581
+ zIndex: 9999,
582
+ elevation: 8,
583
+ shadowOffset: { width: 0, height: 4 },
584
+ shadowOpacity: 0.3,
585
+ shadowRadius: 6
586
+ },
587
+ emoji: {
588
+ fontSize: 26
589
+ }
590
+ });
591
+
592
+ // src/provider.tsx
593
+ var import_jsx_runtime3 = require("react/jsx-runtime");
594
+ var MushiContext = (0, import_react3.createContext)(null);
595
+ function MushiProvider({ children, ...config }) {
596
+ const consoleRef = (0, import_react3.useRef)(null);
597
+ const networkRef = (0, import_react3.useRef)(null);
598
+ const queueRef = (0, import_react3.useRef)(null);
599
+ const apiClientRef = (0, import_react3.useRef)(null);
600
+ const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
601
+ const apiEndpoint = config.endpoint ?? import_core.DEFAULT_API_ENDPOINT;
602
+ (0, import_react3.useEffect)(() => {
230
603
  if (config.capture?.console !== false) {
231
604
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
232
605
  }
233
606
  if (config.capture?.network !== false) {
234
607
  networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
235
608
  }
609
+ apiClientRef.current = (0, import_core.createApiClient)({
610
+ projectId: config.projectId,
611
+ apiKey: config.apiKey,
612
+ apiEndpoint
613
+ });
236
614
  queueRef.current = new AsyncStorageQueue({
237
615
  maxSize: config.storage?.maxQueueSize,
238
616
  apiEndpoint,
239
617
  apiKey: config.apiKey
240
618
  });
619
+ queueRef.current.flush().catch(() => {
620
+ });
241
621
  return () => {
242
622
  consoleRef.current?.restore();
243
623
  networkRef.current?.restore();
244
624
  };
245
625
  }, []);
246
- const instance = {
247
- open: () => {
248
- },
249
- close: () => {
250
- },
251
- submitReport: async (data) => {
626
+ const open = (0, import_react3.useCallback)(() => setSheetVisible(true), []);
627
+ const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
628
+ const submitReport = (0, import_react3.useCallback)(
629
+ async (data) => {
630
+ const deviceInfo = getDeviceInfo();
252
631
  const report = {
632
+ id: `rn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
253
633
  projectId: config.projectId,
254
- ...data,
255
- environment: getDeviceInfo(),
256
- console_logs: consoleRef.current?.getEntries() ?? [],
257
- network_logs: networkRef.current?.getEntries() ?? []
634
+ category: data.category,
635
+ description: data.description,
636
+ environment: {
637
+ userAgent: deviceInfo.systemName ?? "ReactNative",
638
+ platform: deviceInfo.platform ?? "mobile",
639
+ language: deviceInfo.locale ?? "en",
640
+ viewport: { width: deviceInfo.screenWidth ?? 0, height: deviceInfo.screenHeight ?? 0 },
641
+ url: "",
642
+ referrer: "",
643
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
644
+ timezone: deviceInfo.timezone ?? "UTC"
645
+ },
646
+ consoleLogs: consoleRef.current?.getEntries() ?? [],
647
+ networkLogs: networkRef.current?.getEntries().map((entry) => ({
648
+ ...entry,
649
+ status: entry.status ?? 0
650
+ })) ?? [],
651
+ reporterToken: `rn-${config.projectId}-anon`,
652
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
258
653
  };
259
- try {
260
- const res = await fetch(`${apiEndpoint}/v1/reports`, {
261
- method: "POST",
262
- headers: { "Content-Type": "application/json", "X-Mushi-Api-Key": config.apiKey },
263
- body: JSON.stringify(report)
264
- });
265
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
266
- } catch {
654
+ const client = apiClientRef.current;
655
+ if (!client) return;
656
+ const result = await client.submitReport(report);
657
+ if (!result.ok) {
267
658
  await queueRef.current?.enqueue(report);
268
659
  }
269
660
  },
270
- getDeviceInfo,
271
- getConsoleEntries: () => consoleRef.current?.getEntries() ?? [],
272
- getNetworkEntries: () => networkRef.current?.getEntries() ?? []
273
- };
274
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MushiContext.Provider, { value: instance, children });
661
+ [config.projectId, config.apiKey, apiEndpoint]
662
+ );
663
+ const instance = (0, import_react3.useMemo)(
664
+ () => ({
665
+ open,
666
+ close,
667
+ submitReport,
668
+ getDeviceInfo,
669
+ getConsoleEntries: () => consoleRef.current?.getEntries() ?? [],
670
+ getNetworkEntries: () => networkRef.current?.getEntries() ?? []
671
+ }),
672
+ [open, close, submitReport]
673
+ );
674
+ const trigger = config.widget?.trigger ?? "button";
675
+ const showFab = trigger === "button" || trigger === "both";
676
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MushiContext.Provider, { value: instance, children: [
677
+ children,
678
+ showFab && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
679
+ MushiFloatingButton,
680
+ {
681
+ onPress: open,
682
+ position: config.widget?.buttonPosition
683
+ }
684
+ ),
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MushiBottomSheet, { visible: sheetVisible, onClose: close })
686
+ ] });
275
687
  }
276
688
  function useMushiContext() {
277
- return (0, import_react.useContext)(MushiContext);
689
+ return (0, import_react3.useContext)(MushiContext);
278
690
  }
279
691
 
280
692
  // src/hooks/useMushi.ts
@@ -283,10 +695,10 @@ function useMushi() {
283
695
  }
284
696
 
285
697
  // src/hooks/useMushiReport.ts
286
- var import_react2 = require("react");
698
+ var import_react4 = require("react");
287
699
  function useMushiReport() {
288
700
  const mushi = useMushiContext();
289
- const submitReport = (0, import_react2.useCallback)(
701
+ const submitReport = (0, import_react4.useCallback)(
290
702
  async (data) => {
291
703
  if (!mushi) throw new Error("MushiProvider not found");
292
704
  await mushi.submitReport(data);
@@ -297,20 +709,20 @@ function useMushiReport() {
297
709
  }
298
710
 
299
711
  // src/hooks/useMushiWidget.ts
300
- var import_react3 = require("react");
712
+ var import_react5 = require("react");
301
713
  function useMushiWidget() {
302
714
  const mushi = useMushiContext();
303
715
  return {
304
- open: (0, import_react3.useCallback)(() => mushi?.open(), [mushi]),
305
- close: (0, import_react3.useCallback)(() => mushi?.close(), [mushi])
716
+ open: (0, import_react5.useCallback)(() => mushi?.open(), [mushi]),
717
+ close: (0, import_react5.useCallback)(() => mushi?.close(), [mushi])
306
718
  };
307
719
  }
308
720
 
309
721
  // src/capture/navigation-capture.ts
310
- var import_react4 = require("react");
722
+ var import_react6 = require("react");
311
723
  function useNavigationCapture(navigationRef) {
312
- const historyRef = (0, import_react4.useRef)([]);
313
- (0, import_react4.useEffect)(() => {
724
+ const historyRef = (0, import_react6.useRef)([]);
725
+ (0, import_react6.useEffect)(() => {
314
726
  if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
315
727
  const unsubscribe = navigationRef.addListener("state", () => {
316
728
  const route = navigationRef.getCurrentRoute?.();
@@ -326,6 +738,8 @@ function useNavigationCapture(navigationRef) {
326
738
  // Annotate the CommonJS export names for ESM import in node:
327
739
  0 && (module.exports = {
328
740
  AsyncStorageQueue,
741
+ MushiBottomSheet,
742
+ MushiFloatingButton,
329
743
  MushiProvider,
330
744
  getDeviceInfo,
331
745
  setupConsoleCapture,