@mushi-mushi/react-native 0.15.2 → 0.16.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 +1 -1
- package/dist/index.cjs +258 -118
- package/dist/index.d.cts +20 -20
- package/dist/index.d.ts +20 -20
- package/dist/index.js +269 -127
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -120,4 +120,4 @@ MIT
|
|
|
120
120
|
<!-- mushi-readme-stats-footer -->
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
<sub>Monorepo scale (June 2026):
|
|
123
|
+
<sub>Monorepo scale (June 2026): 47 edge functions · 256 SQL migrations · 13 outbound plugins · 11 inbound adapters · 18 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
|
package/dist/index.cjs
CHANGED
|
@@ -467,13 +467,14 @@ var MushiBottomSheet = ({
|
|
|
467
467
|
}
|
|
468
468
|
};
|
|
469
469
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
470
|
-
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)" };
|
|
470
|
+
const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)", disabled: "#3a3a3c", disabledText: "#636366" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: "#8e8e93" };
|
|
471
471
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
472
472
|
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)(
|
|
473
473
|
import_react_native2.KeyboardAvoidingView,
|
|
474
474
|
{
|
|
475
475
|
style: s.flex,
|
|
476
|
-
behavior: import_react_native2.Platform.OS === "ios" ? "padding" :
|
|
476
|
+
behavior: import_react_native2.Platform.OS === "ios" ? "padding" : "height",
|
|
477
|
+
keyboardVerticalOffset: import_react_native2.Platform.OS === "ios" ? 0 : 20,
|
|
477
478
|
children: [
|
|
478
479
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
479
480
|
import_react_native2.Animated.View,
|
|
@@ -491,7 +492,7 @@ var MushiBottomSheet = ({
|
|
|
491
492
|
],
|
|
492
493
|
children: [
|
|
493
494
|
/* @__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 }] }) }),
|
|
494
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: ["report", "inbox"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
495
496
|
import_react_native2.TouchableOpacity,
|
|
496
497
|
{
|
|
497
498
|
onPress: () => {
|
|
@@ -500,11 +501,11 @@ var MushiBottomSheet = ({
|
|
|
500
501
|
else setSelectedReportId(null);
|
|
501
502
|
},
|
|
502
503
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
503
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : "Inbox" })
|
|
504
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Inbox" : "\u{1F310}" })
|
|
504
505
|
},
|
|
505
506
|
tab
|
|
506
507
|
)) }),
|
|
507
|
-
sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.
|
|
508
|
+
sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
508
509
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
509
510
|
threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
510
511
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
@@ -530,102 +531,127 @@ var MushiBottomSheet = ({
|
|
|
530
531
|
] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
531
532
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
532
533
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
533
|
-
] }, r.id)) }) :
|
|
534
|
+
] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
535
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.title, { color: colors.text }], children: "\u{1F310} Mushi Community" }),
|
|
536
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 13, marginBottom: 16, lineHeight: 18 }, children: "Sign in to track your reports across apps and see the global leaderboard. No password needed." }),
|
|
537
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
538
|
+
import_react_native2.TextInput,
|
|
539
|
+
{
|
|
540
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
541
|
+
placeholder: "you@example.com",
|
|
542
|
+
placeholderTextColor: colors.sub,
|
|
543
|
+
keyboardType: "email-address",
|
|
544
|
+
autoCapitalize: "none",
|
|
545
|
+
autoCorrect: false,
|
|
546
|
+
textContentType: "emailAddress"
|
|
547
|
+
}
|
|
548
|
+
),
|
|
549
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Send sign-in link \u2192" }) }),
|
|
550
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11, textAlign: "center", marginTop: 12 }, children: "We'll email you a one-tap sign-in link. No password, ever." })
|
|
551
|
+
] }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
|
|
534
552
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
535
553
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
536
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
554
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
555
|
+
import_react_native2.ScrollView,
|
|
556
|
+
{
|
|
557
|
+
style: s.body,
|
|
558
|
+
keyboardShouldPersistTaps: "handled",
|
|
559
|
+
contentContainerStyle: { paddingBottom: 24 },
|
|
560
|
+
children: [
|
|
561
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.title, { color: colors.text }], children: "Report an issue" }),
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
563
|
+
const active = category === c.key;
|
|
564
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
565
|
+
import_react_native2.TouchableOpacity,
|
|
547
566
|
{
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
567
|
+
onPress: () => setCategory(c.key),
|
|
568
|
+
activeOpacity: 0.7,
|
|
569
|
+
style: [
|
|
570
|
+
s.catBtn,
|
|
571
|
+
{
|
|
572
|
+
backgroundColor: active ? colors.accent : colors.card,
|
|
573
|
+
borderColor: active ? colors.accent : colors.border
|
|
574
|
+
}
|
|
575
|
+
],
|
|
576
|
+
children: [
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: c.emoji }),
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
579
|
+
import_react_native2.Text,
|
|
580
|
+
{
|
|
581
|
+
style: [
|
|
582
|
+
s.catLabel,
|
|
583
|
+
{ color: active ? "#fff" : colors.text }
|
|
584
|
+
],
|
|
585
|
+
children: c.label
|
|
586
|
+
}
|
|
587
|
+
)
|
|
588
|
+
]
|
|
589
|
+
},
|
|
590
|
+
c.key
|
|
591
|
+
);
|
|
592
|
+
}) }),
|
|
593
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
594
|
+
import_react_native2.TextInput,
|
|
595
|
+
{
|
|
596
|
+
style: [
|
|
597
|
+
s.input,
|
|
556
598
|
{
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
],
|
|
561
|
-
children: c.label
|
|
599
|
+
backgroundColor: colors.card,
|
|
600
|
+
color: colors.text,
|
|
601
|
+
borderColor: colors.border
|
|
562
602
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
603
|
+
],
|
|
604
|
+
placeholder: "What happened?",
|
|
605
|
+
placeholderTextColor: colors.sub,
|
|
606
|
+
multiline: true,
|
|
607
|
+
textAlignVertical: "top",
|
|
608
|
+
value: description,
|
|
609
|
+
onChangeText: setDescription,
|
|
610
|
+
editable: phase === "form"
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
615
|
+
import_react_native2.Image,
|
|
616
|
+
{
|
|
617
|
+
source: { uri: activeScreenshot },
|
|
618
|
+
style: s.screenshotThumb,
|
|
619
|
+
accessibilityLabel: "Attached screenshot"
|
|
620
|
+
}
|
|
621
|
+
),
|
|
622
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
623
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
625
|
+
] }),
|
|
626
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
627
|
+
import_react_native2.TouchableOpacity,
|
|
628
|
+
{
|
|
629
|
+
onPress: () => {
|
|
630
|
+
setScreenshotAttached(false);
|
|
631
|
+
onClearScreenshot?.();
|
|
632
|
+
},
|
|
633
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
634
|
+
accessibilityLabel: "Remove screenshot",
|
|
635
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
] }) : null,
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
640
|
+
import_react_native2.TouchableOpacity,
|
|
574
641
|
{
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
642
|
+
onPress: handleSubmit,
|
|
643
|
+
disabled: !canSubmit,
|
|
644
|
+
activeOpacity: 0.8,
|
|
645
|
+
style: [
|
|
646
|
+
s.submitBtn,
|
|
647
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
648
|
+
],
|
|
649
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, !canSubmit && { color: colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
578
650
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
textAlignVertical: "top",
|
|
584
|
-
value: description,
|
|
585
|
-
onChangeText: setDescription,
|
|
586
|
-
editable: phase === "form"
|
|
587
|
-
}
|
|
588
|
-
),
|
|
589
|
-
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
590
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
591
|
-
import_react_native2.Image,
|
|
592
|
-
{
|
|
593
|
-
source: { uri: activeScreenshot },
|
|
594
|
-
style: s.screenshotThumb,
|
|
595
|
-
accessibilityLabel: "Attached screenshot"
|
|
596
|
-
}
|
|
597
|
-
),
|
|
598
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
599
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
600
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
601
|
-
] }),
|
|
602
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
603
|
-
import_react_native2.TouchableOpacity,
|
|
604
|
-
{
|
|
605
|
-
onPress: () => {
|
|
606
|
-
setScreenshotAttached(false);
|
|
607
|
-
onClearScreenshot?.();
|
|
608
|
-
},
|
|
609
|
-
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
610
|
-
accessibilityLabel: "Remove screenshot",
|
|
611
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
612
|
-
}
|
|
613
|
-
)
|
|
614
|
-
] }) : null,
|
|
615
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
616
|
-
import_react_native2.TouchableOpacity,
|
|
617
|
-
{
|
|
618
|
-
onPress: handleSubmit,
|
|
619
|
-
disabled: !canSubmit,
|
|
620
|
-
activeOpacity: 0.8,
|
|
621
|
-
style: [
|
|
622
|
-
s.submitBtn,
|
|
623
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.border }
|
|
624
|
-
],
|
|
625
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
626
|
-
}
|
|
627
|
-
)
|
|
628
|
-
] })
|
|
651
|
+
)
|
|
652
|
+
]
|
|
653
|
+
}
|
|
654
|
+
)
|
|
629
655
|
]
|
|
630
656
|
}
|
|
631
657
|
)
|
|
@@ -801,15 +827,133 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
801
827
|
var import_react2 = require("react");
|
|
802
828
|
var import_react_native3 = require("react-native");
|
|
803
829
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
830
|
+
var FAB_SIZE = 56;
|
|
831
|
+
var EDGE_PADDING = 12;
|
|
832
|
+
function getScreenBounds() {
|
|
833
|
+
const { width, height } = import_react_native3.Dimensions.get("window");
|
|
834
|
+
return { width, height };
|
|
835
|
+
}
|
|
836
|
+
function clamp(value, min, max) {
|
|
837
|
+
return Math.min(Math.max(value, min), max);
|
|
838
|
+
}
|
|
839
|
+
var STORAGE_KEY = "mushi:fab-pos";
|
|
840
|
+
async function loadStoredPos() {
|
|
841
|
+
try {
|
|
842
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
843
|
+
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
844
|
+
if (!raw) return null;
|
|
845
|
+
return JSON.parse(raw);
|
|
846
|
+
} catch {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function storePos(pos) {
|
|
851
|
+
try {
|
|
852
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
853
|
+
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(pos));
|
|
854
|
+
} catch {
|
|
855
|
+
}
|
|
856
|
+
}
|
|
804
857
|
var MushiFloatingButton = ({
|
|
805
858
|
onPress,
|
|
806
859
|
position = "bottom-right",
|
|
807
|
-
inset
|
|
860
|
+
inset,
|
|
861
|
+
config,
|
|
862
|
+
accent
|
|
808
863
|
}) => {
|
|
809
864
|
const scheme = (0, import_react_native3.useColorScheme)();
|
|
810
865
|
const dark = scheme === "dark";
|
|
866
|
+
const draggable = config?.draggable ?? false;
|
|
867
|
+
const snapToEdge = config?.snapToEdge ?? true;
|
|
868
|
+
const persist = config?.persist ?? false;
|
|
869
|
+
const baseBottom = inset?.bottom ?? (import_react_native3.Platform.OS === "ios" ? 50 : 28);
|
|
870
|
+
const baseX = position === "bottom-left" ? inset?.left ?? EDGE_PADDING : (() => {
|
|
871
|
+
const { width } = getScreenBounds();
|
|
872
|
+
return width - FAB_SIZE - (inset?.right ?? EDGE_PADDING);
|
|
873
|
+
})();
|
|
811
874
|
const scale = (0, import_react2.useRef)(new import_react_native3.Animated.Value(1)).current;
|
|
875
|
+
const posX = (0, import_react2.useRef)(new import_react_native3.Animated.Value(baseX)).current;
|
|
876
|
+
const posY = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
|
|
877
|
+
const currentX = (0, import_react2.useRef)(baseX);
|
|
878
|
+
const currentY = (0, import_react2.useRef)(0);
|
|
879
|
+
const isDragging = (0, import_react2.useRef)(false);
|
|
880
|
+
const accentColor = accent ?? (dark ? "#0a84ff" : "#007aff");
|
|
881
|
+
(0, import_react2.useEffect)(() => {
|
|
882
|
+
if (!persist) return;
|
|
883
|
+
void loadStoredPos().then((pos) => {
|
|
884
|
+
if (!pos) return;
|
|
885
|
+
currentX.current = pos.x;
|
|
886
|
+
currentY.current = pos.y;
|
|
887
|
+
posX.setValue(pos.x);
|
|
888
|
+
posY.setValue(pos.y);
|
|
889
|
+
});
|
|
890
|
+
}, [persist, posX, posY]);
|
|
891
|
+
const snapAndPersist = (0, import_react2.useCallback)(
|
|
892
|
+
(x, y) => {
|
|
893
|
+
const { width, height } = getScreenBounds();
|
|
894
|
+
let snappedX = x;
|
|
895
|
+
if (snapToEdge) {
|
|
896
|
+
const midX = width / 2;
|
|
897
|
+
snappedX = x + FAB_SIZE / 2 < midX ? EDGE_PADDING : width - FAB_SIZE - EDGE_PADDING;
|
|
898
|
+
}
|
|
899
|
+
const clampedX = clamp(snappedX, EDGE_PADDING, width - FAB_SIZE - EDGE_PADDING);
|
|
900
|
+
const clampedY = clamp(y, -(height - FAB_SIZE - baseBottom - 60), 0);
|
|
901
|
+
currentX.current = clampedX;
|
|
902
|
+
currentY.current = clampedY;
|
|
903
|
+
import_react_native3.Animated.spring(posX, {
|
|
904
|
+
toValue: clampedX,
|
|
905
|
+
useNativeDriver: false,
|
|
906
|
+
damping: 18,
|
|
907
|
+
stiffness: 220
|
|
908
|
+
}).start();
|
|
909
|
+
import_react_native3.Animated.spring(posY, {
|
|
910
|
+
toValue: clampedY,
|
|
911
|
+
useNativeDriver: false,
|
|
912
|
+
damping: 18,
|
|
913
|
+
stiffness: 220
|
|
914
|
+
}).start();
|
|
915
|
+
if (persist) void storePos({ x: clampedX, y: clampedY });
|
|
916
|
+
},
|
|
917
|
+
[snapToEdge, persist, posX, posY, baseBottom]
|
|
918
|
+
);
|
|
919
|
+
const panResponder = (0, import_react2.useRef)(
|
|
920
|
+
draggable ? import_react_native3.PanResponder.create({
|
|
921
|
+
onStartShouldSetPanResponder: () => true,
|
|
922
|
+
onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6,
|
|
923
|
+
onPanResponderGrant: () => {
|
|
924
|
+
isDragging.current = false;
|
|
925
|
+
posX.stopAnimation();
|
|
926
|
+
posY.stopAnimation();
|
|
927
|
+
},
|
|
928
|
+
onPanResponderMove: (_, g) => {
|
|
929
|
+
if (Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6) {
|
|
930
|
+
isDragging.current = true;
|
|
931
|
+
scale.setValue(1);
|
|
932
|
+
}
|
|
933
|
+
const { width, height } = getScreenBounds();
|
|
934
|
+
const newX = clamp(
|
|
935
|
+
currentX.current + g.dx,
|
|
936
|
+
EDGE_PADDING,
|
|
937
|
+
width - FAB_SIZE - EDGE_PADDING
|
|
938
|
+
);
|
|
939
|
+
const newY = clamp(
|
|
940
|
+
currentY.current + g.dy,
|
|
941
|
+
-(height - FAB_SIZE - baseBottom - 60),
|
|
942
|
+
0
|
|
943
|
+
);
|
|
944
|
+
posX.setValue(newX);
|
|
945
|
+
posY.setValue(newY);
|
|
946
|
+
},
|
|
947
|
+
onPanResponderRelease: (_, g) => {
|
|
948
|
+
if (isDragging.current) {
|
|
949
|
+
snapAndPersist(currentX.current + g.dx, currentY.current + g.dy);
|
|
950
|
+
}
|
|
951
|
+
isDragging.current = false;
|
|
952
|
+
}
|
|
953
|
+
}) : import_react_native3.PanResponder.create({})
|
|
954
|
+
).current;
|
|
812
955
|
const handlePressIn = (0, import_react2.useCallback)(() => {
|
|
956
|
+
if (isDragging.current) return;
|
|
813
957
|
import_react_native3.Animated.spring(scale, {
|
|
814
958
|
toValue: 0.85,
|
|
815
959
|
useNativeDriver: true,
|
|
@@ -824,39 +968,35 @@ var MushiFloatingButton = ({
|
|
|
824
968
|
damping: 10,
|
|
825
969
|
stiffness: 250
|
|
826
970
|
}).start();
|
|
827
|
-
|
|
828
|
-
|
|
971
|
+
if (!isDragging.current) onPress();
|
|
972
|
+
}, [scale, onPress]);
|
|
829
973
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
830
|
-
import_react_native3.
|
|
974
|
+
import_react_native3.Animated.View,
|
|
831
975
|
{
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
836
|
-
import_react_native3.Animated.View,
|
|
976
|
+
...draggable ? panResponder.panHandlers : {},
|
|
977
|
+
style: [
|
|
978
|
+
s2.fab,
|
|
837
979
|
{
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
844
|
-
transform: [{ scale }],
|
|
845
|
-
shadowColor: dark ? "#0a84ff" : "#000"
|
|
846
|
-
}
|
|
847
|
-
],
|
|
848
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
980
|
+
left: posX,
|
|
981
|
+
bottom: import_react_native3.Animated.add(new import_react_native3.Animated.Value(baseBottom), import_react_native3.Animated.multiply(posY, -1)),
|
|
982
|
+
backgroundColor: accentColor,
|
|
983
|
+
shadowColor: dark ? accentColor : "#000",
|
|
984
|
+
transform: [{ scale }]
|
|
849
985
|
}
|
|
850
|
-
|
|
986
|
+
],
|
|
987
|
+
onStartShouldSetResponder: () => !draggable,
|
|
988
|
+
onResponderGrant: handlePressIn,
|
|
989
|
+
onResponderRelease: handlePressOut,
|
|
990
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
851
991
|
}
|
|
852
992
|
);
|
|
853
993
|
};
|
|
854
994
|
var s2 = import_react_native3.StyleSheet.create({
|
|
855
995
|
fab: {
|
|
856
996
|
position: "absolute",
|
|
857
|
-
width:
|
|
858
|
-
height:
|
|
859
|
-
borderRadius:
|
|
997
|
+
width: FAB_SIZE,
|
|
998
|
+
height: FAB_SIZE,
|
|
999
|
+
borderRadius: FAB_SIZE / 2,
|
|
860
1000
|
alignItems: "center",
|
|
861
1001
|
justifyContent: "center",
|
|
862
1002
|
zIndex: 9999,
|
package/dist/index.d.cts
CHANGED
|
@@ -188,29 +188,25 @@ interface MushiBottomSheetProps {
|
|
|
188
188
|
declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
*
|
|
192
|
-
* PURPOSE: Floating action button (FAB) that triggers the Mushi bug-report bottom sheet
|
|
191
|
+
* MushiFloatingButton — draggable, repositionable FAB for the Mushi SDK.
|
|
193
192
|
*
|
|
194
|
-
*
|
|
195
|
-
* -
|
|
196
|
-
* -
|
|
197
|
-
* -
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* -
|
|
201
|
-
*
|
|
202
|
-
* USAGE:
|
|
203
|
-
* - Rendered automatically by MushiProvider when widget.trigger is 'button' or 'both'
|
|
204
|
-
* - Can be used standalone: <MushiFloatingButton onPress={…} />
|
|
205
|
-
*
|
|
206
|
-
* TECHNICAL DETAILS:
|
|
207
|
-
* - Uses Animated.Value for scale, driven by onPressIn / onPressOut
|
|
208
|
-
* - Safe-area aware: extra bottom padding on iOS
|
|
209
|
-
*
|
|
210
|
-
* NOTES:
|
|
211
|
-
* - Keeps zIndex high (9999) so it floats above app content
|
|
193
|
+
* Supports:
|
|
194
|
+
* - PanResponder drag with tap-vs-drag threshold (6 px)
|
|
195
|
+
* - Clamp to screen bounds with safe-area respect
|
|
196
|
+
* - Optional edge-snap on release (`snapToEdge`)
|
|
197
|
+
* - Persist position to AsyncStorage (opt-in via `persist` config)
|
|
198
|
+
* - Press-in / press-out spring scale animation
|
|
199
|
+
* - Dark/light adaptive colours
|
|
212
200
|
*/
|
|
213
201
|
|
|
202
|
+
interface MushiFloatingButtonConfig {
|
|
203
|
+
/** Allow the FAB to be dragged. Default: false. */
|
|
204
|
+
draggable?: boolean;
|
|
205
|
+
/** Snap FAB to the nearest vertical edge on release. Default: true. */
|
|
206
|
+
snapToEdge?: boolean;
|
|
207
|
+
/** Persist FAB position across app restarts via AsyncStorage. Default: false. */
|
|
208
|
+
persist?: boolean;
|
|
209
|
+
}
|
|
214
210
|
interface MushiFloatingButtonProps {
|
|
215
211
|
onPress: () => void;
|
|
216
212
|
position?: 'bottom-right' | 'bottom-left';
|
|
@@ -219,6 +215,10 @@ interface MushiFloatingButtonProps {
|
|
|
219
215
|
left?: number;
|
|
220
216
|
right?: number;
|
|
221
217
|
};
|
|
218
|
+
/** Draggable + persist config */
|
|
219
|
+
config?: MushiFloatingButtonConfig;
|
|
220
|
+
/** Accent color (hex) — falls back to platform blue */
|
|
221
|
+
accent?: string;
|
|
222
222
|
}
|
|
223
223
|
declare const MushiFloatingButton: FC<MushiFloatingButtonProps>;
|
|
224
224
|
|
package/dist/index.d.ts
CHANGED
|
@@ -188,29 +188,25 @@ interface MushiBottomSheetProps {
|
|
|
188
188
|
declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
*
|
|
192
|
-
* PURPOSE: Floating action button (FAB) that triggers the Mushi bug-report bottom sheet
|
|
191
|
+
* MushiFloatingButton — draggable, repositionable FAB for the Mushi SDK.
|
|
193
192
|
*
|
|
194
|
-
*
|
|
195
|
-
* -
|
|
196
|
-
* -
|
|
197
|
-
* -
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* -
|
|
201
|
-
*
|
|
202
|
-
* USAGE:
|
|
203
|
-
* - Rendered automatically by MushiProvider when widget.trigger is 'button' or 'both'
|
|
204
|
-
* - Can be used standalone: <MushiFloatingButton onPress={…} />
|
|
205
|
-
*
|
|
206
|
-
* TECHNICAL DETAILS:
|
|
207
|
-
* - Uses Animated.Value for scale, driven by onPressIn / onPressOut
|
|
208
|
-
* - Safe-area aware: extra bottom padding on iOS
|
|
209
|
-
*
|
|
210
|
-
* NOTES:
|
|
211
|
-
* - Keeps zIndex high (9999) so it floats above app content
|
|
193
|
+
* Supports:
|
|
194
|
+
* - PanResponder drag with tap-vs-drag threshold (6 px)
|
|
195
|
+
* - Clamp to screen bounds with safe-area respect
|
|
196
|
+
* - Optional edge-snap on release (`snapToEdge`)
|
|
197
|
+
* - Persist position to AsyncStorage (opt-in via `persist` config)
|
|
198
|
+
* - Press-in / press-out spring scale animation
|
|
199
|
+
* - Dark/light adaptive colours
|
|
212
200
|
*/
|
|
213
201
|
|
|
202
|
+
interface MushiFloatingButtonConfig {
|
|
203
|
+
/** Allow the FAB to be dragged. Default: false. */
|
|
204
|
+
draggable?: boolean;
|
|
205
|
+
/** Snap FAB to the nearest vertical edge on release. Default: true. */
|
|
206
|
+
snapToEdge?: boolean;
|
|
207
|
+
/** Persist FAB position across app restarts via AsyncStorage. Default: false. */
|
|
208
|
+
persist?: boolean;
|
|
209
|
+
}
|
|
214
210
|
interface MushiFloatingButtonProps {
|
|
215
211
|
onPress: () => void;
|
|
216
212
|
position?: 'bottom-right' | 'bottom-left';
|
|
@@ -219,6 +215,10 @@ interface MushiFloatingButtonProps {
|
|
|
219
215
|
left?: number;
|
|
220
216
|
right?: number;
|
|
221
217
|
};
|
|
218
|
+
/** Draggable + persist config */
|
|
219
|
+
config?: MushiFloatingButtonConfig;
|
|
220
|
+
/** Accent color (hex) — falls back to platform blue */
|
|
221
|
+
accent?: string;
|
|
222
222
|
}
|
|
223
223
|
declare const MushiFloatingButton: FC<MushiFloatingButtonProps>;
|
|
224
224
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
import {
|
|
10
10
|
createContext,
|
|
11
11
|
useContext,
|
|
12
|
-
useEffect as
|
|
12
|
+
useEffect as useEffect3,
|
|
13
13
|
useRef as useRef3,
|
|
14
14
|
useState as useState2,
|
|
15
15
|
useCallback as useCallback3,
|
|
@@ -306,6 +306,7 @@ import {
|
|
|
306
306
|
Dimensions as Dimensions2,
|
|
307
307
|
Platform as Platform2,
|
|
308
308
|
KeyboardAvoidingView,
|
|
309
|
+
ScrollView,
|
|
309
310
|
useColorScheme
|
|
310
311
|
} from "react-native";
|
|
311
312
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -458,13 +459,14 @@ var MushiBottomSheet = ({
|
|
|
458
459
|
}
|
|
459
460
|
};
|
|
460
461
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
461
|
-
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)" };
|
|
462
|
+
const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)", disabled: "#3a3a3c", disabledText: "#636366" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: "#8e8e93" };
|
|
462
463
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
463
464
|
return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
|
|
464
465
|
KeyboardAvoidingView,
|
|
465
466
|
{
|
|
466
467
|
style: s.flex,
|
|
467
|
-
behavior: Platform2.OS === "ios" ? "padding" :
|
|
468
|
+
behavior: Platform2.OS === "ios" ? "padding" : "height",
|
|
469
|
+
keyboardVerticalOffset: Platform2.OS === "ios" ? 0 : 20,
|
|
468
470
|
children: [
|
|
469
471
|
/* @__PURE__ */ jsx(
|
|
470
472
|
Animated.View,
|
|
@@ -482,7 +484,7 @@ var MushiBottomSheet = ({
|
|
|
482
484
|
],
|
|
483
485
|
children: [
|
|
484
486
|
/* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
485
|
-
/* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox"].map((tab) => /* @__PURE__ */ jsx(
|
|
487
|
+
/* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ jsx(
|
|
486
488
|
TouchableOpacity,
|
|
487
489
|
{
|
|
488
490
|
onPress: () => {
|
|
@@ -491,11 +493,11 @@ var MushiBottomSheet = ({
|
|
|
491
493
|
else setSelectedReportId(null);
|
|
492
494
|
},
|
|
493
495
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
494
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : "Inbox" })
|
|
496
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Inbox" : "\u{1F310}" })
|
|
495
497
|
},
|
|
496
498
|
tab
|
|
497
499
|
)) }),
|
|
498
|
-
sheetTab === "inbox" ? /* @__PURE__ */ jsx(
|
|
500
|
+
sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
499
501
|
/* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
500
502
|
threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
501
503
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
@@ -521,102 +523,127 @@ var MushiBottomSheet = ({
|
|
|
521
523
|
] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
522
524
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
523
525
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
524
|
-
] }, r.id)) }) :
|
|
526
|
+
] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
527
|
+
/* @__PURE__ */ jsx(Text, { style: [s.title, { color: colors.text }], children: "\u{1F310} Mushi Community" }),
|
|
528
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 13, marginBottom: 16, lineHeight: 18 }, children: "Sign in to track your reports across apps and see the global leaderboard. No password needed." }),
|
|
529
|
+
/* @__PURE__ */ jsx(
|
|
530
|
+
TextInput,
|
|
531
|
+
{
|
|
532
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
533
|
+
placeholder: "you@example.com",
|
|
534
|
+
placeholderTextColor: colors.sub,
|
|
535
|
+
keyboardType: "email-address",
|
|
536
|
+
autoCapitalize: "none",
|
|
537
|
+
autoCorrect: false,
|
|
538
|
+
textContentType: "emailAddress"
|
|
539
|
+
}
|
|
540
|
+
),
|
|
541
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Send sign-in link \u2192" }) }),
|
|
542
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11, textAlign: "center", marginTop: 12 }, children: "We'll email you a one-tap sign-in link. No password, ever." })
|
|
543
|
+
] }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
|
|
525
544
|
/* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
526
545
|
/* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
527
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
546
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
547
|
+
ScrollView,
|
|
548
|
+
{
|
|
549
|
+
style: s.body,
|
|
550
|
+
keyboardShouldPersistTaps: "handled",
|
|
551
|
+
contentContainerStyle: { paddingBottom: 24 },
|
|
552
|
+
children: [
|
|
553
|
+
/* @__PURE__ */ jsx(Text, { style: [s.title, { color: colors.text }], children: "Report an issue" }),
|
|
554
|
+
/* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
555
|
+
const active = category === c.key;
|
|
556
|
+
return /* @__PURE__ */ jsxs(
|
|
557
|
+
TouchableOpacity,
|
|
538
558
|
{
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
559
|
+
onPress: () => setCategory(c.key),
|
|
560
|
+
activeOpacity: 0.7,
|
|
561
|
+
style: [
|
|
562
|
+
s.catBtn,
|
|
563
|
+
{
|
|
564
|
+
backgroundColor: active ? colors.accent : colors.card,
|
|
565
|
+
borderColor: active ? colors.accent : colors.border
|
|
566
|
+
}
|
|
567
|
+
],
|
|
568
|
+
children: [
|
|
569
|
+
/* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: c.emoji }),
|
|
570
|
+
/* @__PURE__ */ jsx(
|
|
571
|
+
Text,
|
|
572
|
+
{
|
|
573
|
+
style: [
|
|
574
|
+
s.catLabel,
|
|
575
|
+
{ color: active ? "#fff" : colors.text }
|
|
576
|
+
],
|
|
577
|
+
children: c.label
|
|
578
|
+
}
|
|
579
|
+
)
|
|
580
|
+
]
|
|
581
|
+
},
|
|
582
|
+
c.key
|
|
583
|
+
);
|
|
584
|
+
}) }),
|
|
585
|
+
/* @__PURE__ */ jsx(
|
|
586
|
+
TextInput,
|
|
587
|
+
{
|
|
588
|
+
style: [
|
|
589
|
+
s.input,
|
|
547
590
|
{
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
],
|
|
552
|
-
children: c.label
|
|
591
|
+
backgroundColor: colors.card,
|
|
592
|
+
color: colors.text,
|
|
593
|
+
borderColor: colors.border
|
|
553
594
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
595
|
+
],
|
|
596
|
+
placeholder: "What happened?",
|
|
597
|
+
placeholderTextColor: colors.sub,
|
|
598
|
+
multiline: true,
|
|
599
|
+
textAlignVertical: "top",
|
|
600
|
+
value: description,
|
|
601
|
+
onChangeText: setDescription,
|
|
602
|
+
editable: phase === "form"
|
|
603
|
+
}
|
|
604
|
+
),
|
|
605
|
+
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
606
|
+
/* @__PURE__ */ jsx(
|
|
607
|
+
Image,
|
|
608
|
+
{
|
|
609
|
+
source: { uri: activeScreenshot },
|
|
610
|
+
style: s.screenshotThumb,
|
|
611
|
+
accessibilityLabel: "Attached screenshot"
|
|
612
|
+
}
|
|
613
|
+
),
|
|
614
|
+
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
615
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
616
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ jsx(
|
|
619
|
+
TouchableOpacity,
|
|
620
|
+
{
|
|
621
|
+
onPress: () => {
|
|
622
|
+
setScreenshotAttached(false);
|
|
623
|
+
onClearScreenshot?.();
|
|
624
|
+
},
|
|
625
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
626
|
+
accessibilityLabel: "Remove screenshot",
|
|
627
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
628
|
+
}
|
|
629
|
+
)
|
|
630
|
+
] }) : null,
|
|
631
|
+
/* @__PURE__ */ jsx(
|
|
632
|
+
TouchableOpacity,
|
|
565
633
|
{
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
634
|
+
onPress: handleSubmit,
|
|
635
|
+
disabled: !canSubmit,
|
|
636
|
+
activeOpacity: 0.8,
|
|
637
|
+
style: [
|
|
638
|
+
s.submitBtn,
|
|
639
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
640
|
+
],
|
|
641
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, !canSubmit && { color: colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
569
642
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
textAlignVertical: "top",
|
|
575
|
-
value: description,
|
|
576
|
-
onChangeText: setDescription,
|
|
577
|
-
editable: phase === "form"
|
|
578
|
-
}
|
|
579
|
-
),
|
|
580
|
-
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
581
|
-
/* @__PURE__ */ jsx(
|
|
582
|
-
Image,
|
|
583
|
-
{
|
|
584
|
-
source: { uri: activeScreenshot },
|
|
585
|
-
style: s.screenshotThumb,
|
|
586
|
-
accessibilityLabel: "Attached screenshot"
|
|
587
|
-
}
|
|
588
|
-
),
|
|
589
|
-
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
590
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
591
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
592
|
-
] }),
|
|
593
|
-
/* @__PURE__ */ jsx(
|
|
594
|
-
TouchableOpacity,
|
|
595
|
-
{
|
|
596
|
-
onPress: () => {
|
|
597
|
-
setScreenshotAttached(false);
|
|
598
|
-
onClearScreenshot?.();
|
|
599
|
-
},
|
|
600
|
-
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
601
|
-
accessibilityLabel: "Remove screenshot",
|
|
602
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
603
|
-
}
|
|
604
|
-
)
|
|
605
|
-
] }) : null,
|
|
606
|
-
/* @__PURE__ */ jsx(
|
|
607
|
-
TouchableOpacity,
|
|
608
|
-
{
|
|
609
|
-
onPress: handleSubmit,
|
|
610
|
-
disabled: !canSubmit,
|
|
611
|
-
activeOpacity: 0.8,
|
|
612
|
-
style: [
|
|
613
|
-
s.submitBtn,
|
|
614
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.border }
|
|
615
|
-
],
|
|
616
|
-
children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
617
|
-
}
|
|
618
|
-
)
|
|
619
|
-
] })
|
|
643
|
+
)
|
|
644
|
+
]
|
|
645
|
+
}
|
|
646
|
+
)
|
|
620
647
|
]
|
|
621
648
|
}
|
|
622
649
|
)
|
|
@@ -789,24 +816,143 @@ var s = StyleSheet.create({
|
|
|
789
816
|
});
|
|
790
817
|
|
|
791
818
|
// src/components/MushiFloatingButton.tsx
|
|
792
|
-
import { useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
819
|
+
import { useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
793
820
|
import {
|
|
794
821
|
Animated as Animated2,
|
|
795
|
-
|
|
822
|
+
PanResponder as PanResponder2,
|
|
796
823
|
StyleSheet as StyleSheet2,
|
|
797
824
|
Platform as Platform3,
|
|
825
|
+
Dimensions as Dimensions3,
|
|
798
826
|
useColorScheme as useColorScheme2
|
|
799
827
|
} from "react-native";
|
|
800
828
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
829
|
+
var FAB_SIZE = 56;
|
|
830
|
+
var EDGE_PADDING = 12;
|
|
831
|
+
function getScreenBounds() {
|
|
832
|
+
const { width, height } = Dimensions3.get("window");
|
|
833
|
+
return { width, height };
|
|
834
|
+
}
|
|
835
|
+
function clamp(value, min, max) {
|
|
836
|
+
return Math.min(Math.max(value, min), max);
|
|
837
|
+
}
|
|
838
|
+
var STORAGE_KEY = "mushi:fab-pos";
|
|
839
|
+
async function loadStoredPos() {
|
|
840
|
+
try {
|
|
841
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
842
|
+
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
843
|
+
if (!raw) return null;
|
|
844
|
+
return JSON.parse(raw);
|
|
845
|
+
} catch {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async function storePos(pos) {
|
|
850
|
+
try {
|
|
851
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
852
|
+
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(pos));
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
}
|
|
801
856
|
var MushiFloatingButton = ({
|
|
802
857
|
onPress,
|
|
803
858
|
position = "bottom-right",
|
|
804
|
-
inset
|
|
859
|
+
inset,
|
|
860
|
+
config,
|
|
861
|
+
accent
|
|
805
862
|
}) => {
|
|
806
863
|
const scheme = useColorScheme2();
|
|
807
864
|
const dark = scheme === "dark";
|
|
865
|
+
const draggable = config?.draggable ?? false;
|
|
866
|
+
const snapToEdge = config?.snapToEdge ?? true;
|
|
867
|
+
const persist = config?.persist ?? false;
|
|
868
|
+
const baseBottom = inset?.bottom ?? (Platform3.OS === "ios" ? 50 : 28);
|
|
869
|
+
const baseX = position === "bottom-left" ? inset?.left ?? EDGE_PADDING : (() => {
|
|
870
|
+
const { width } = getScreenBounds();
|
|
871
|
+
return width - FAB_SIZE - (inset?.right ?? EDGE_PADDING);
|
|
872
|
+
})();
|
|
808
873
|
const scale = useRef2(new Animated2.Value(1)).current;
|
|
874
|
+
const posX = useRef2(new Animated2.Value(baseX)).current;
|
|
875
|
+
const posY = useRef2(new Animated2.Value(0)).current;
|
|
876
|
+
const currentX = useRef2(baseX);
|
|
877
|
+
const currentY = useRef2(0);
|
|
878
|
+
const isDragging = useRef2(false);
|
|
879
|
+
const accentColor = accent ?? (dark ? "#0a84ff" : "#007aff");
|
|
880
|
+
useEffect2(() => {
|
|
881
|
+
if (!persist) return;
|
|
882
|
+
void loadStoredPos().then((pos) => {
|
|
883
|
+
if (!pos) return;
|
|
884
|
+
currentX.current = pos.x;
|
|
885
|
+
currentY.current = pos.y;
|
|
886
|
+
posX.setValue(pos.x);
|
|
887
|
+
posY.setValue(pos.y);
|
|
888
|
+
});
|
|
889
|
+
}, [persist, posX, posY]);
|
|
890
|
+
const snapAndPersist = useCallback2(
|
|
891
|
+
(x, y) => {
|
|
892
|
+
const { width, height } = getScreenBounds();
|
|
893
|
+
let snappedX = x;
|
|
894
|
+
if (snapToEdge) {
|
|
895
|
+
const midX = width / 2;
|
|
896
|
+
snappedX = x + FAB_SIZE / 2 < midX ? EDGE_PADDING : width - FAB_SIZE - EDGE_PADDING;
|
|
897
|
+
}
|
|
898
|
+
const clampedX = clamp(snappedX, EDGE_PADDING, width - FAB_SIZE - EDGE_PADDING);
|
|
899
|
+
const clampedY = clamp(y, -(height - FAB_SIZE - baseBottom - 60), 0);
|
|
900
|
+
currentX.current = clampedX;
|
|
901
|
+
currentY.current = clampedY;
|
|
902
|
+
Animated2.spring(posX, {
|
|
903
|
+
toValue: clampedX,
|
|
904
|
+
useNativeDriver: false,
|
|
905
|
+
damping: 18,
|
|
906
|
+
stiffness: 220
|
|
907
|
+
}).start();
|
|
908
|
+
Animated2.spring(posY, {
|
|
909
|
+
toValue: clampedY,
|
|
910
|
+
useNativeDriver: false,
|
|
911
|
+
damping: 18,
|
|
912
|
+
stiffness: 220
|
|
913
|
+
}).start();
|
|
914
|
+
if (persist) void storePos({ x: clampedX, y: clampedY });
|
|
915
|
+
},
|
|
916
|
+
[snapToEdge, persist, posX, posY, baseBottom]
|
|
917
|
+
);
|
|
918
|
+
const panResponder = useRef2(
|
|
919
|
+
draggable ? PanResponder2.create({
|
|
920
|
+
onStartShouldSetPanResponder: () => true,
|
|
921
|
+
onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6,
|
|
922
|
+
onPanResponderGrant: () => {
|
|
923
|
+
isDragging.current = false;
|
|
924
|
+
posX.stopAnimation();
|
|
925
|
+
posY.stopAnimation();
|
|
926
|
+
},
|
|
927
|
+
onPanResponderMove: (_, g) => {
|
|
928
|
+
if (Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6) {
|
|
929
|
+
isDragging.current = true;
|
|
930
|
+
scale.setValue(1);
|
|
931
|
+
}
|
|
932
|
+
const { width, height } = getScreenBounds();
|
|
933
|
+
const newX = clamp(
|
|
934
|
+
currentX.current + g.dx,
|
|
935
|
+
EDGE_PADDING,
|
|
936
|
+
width - FAB_SIZE - EDGE_PADDING
|
|
937
|
+
);
|
|
938
|
+
const newY = clamp(
|
|
939
|
+
currentY.current + g.dy,
|
|
940
|
+
-(height - FAB_SIZE - baseBottom - 60),
|
|
941
|
+
0
|
|
942
|
+
);
|
|
943
|
+
posX.setValue(newX);
|
|
944
|
+
posY.setValue(newY);
|
|
945
|
+
},
|
|
946
|
+
onPanResponderRelease: (_, g) => {
|
|
947
|
+
if (isDragging.current) {
|
|
948
|
+
snapAndPersist(currentX.current + g.dx, currentY.current + g.dy);
|
|
949
|
+
}
|
|
950
|
+
isDragging.current = false;
|
|
951
|
+
}
|
|
952
|
+
}) : PanResponder2.create({})
|
|
953
|
+
).current;
|
|
809
954
|
const handlePressIn = useCallback2(() => {
|
|
955
|
+
if (isDragging.current) return;
|
|
810
956
|
Animated2.spring(scale, {
|
|
811
957
|
toValue: 0.85,
|
|
812
958
|
useNativeDriver: true,
|
|
@@ -821,39 +967,35 @@ var MushiFloatingButton = ({
|
|
|
821
967
|
damping: 10,
|
|
822
968
|
stiffness: 250
|
|
823
969
|
}).start();
|
|
824
|
-
|
|
825
|
-
|
|
970
|
+
if (!isDragging.current) onPress();
|
|
971
|
+
}, [scale, onPress]);
|
|
826
972
|
return /* @__PURE__ */ jsx2(
|
|
827
|
-
|
|
973
|
+
Animated2.View,
|
|
828
974
|
{
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
children: /* @__PURE__ */ jsx2(
|
|
833
|
-
Animated2.View,
|
|
975
|
+
...draggable ? panResponder.panHandlers : {},
|
|
976
|
+
style: [
|
|
977
|
+
s2.fab,
|
|
834
978
|
{
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
841
|
-
transform: [{ scale }],
|
|
842
|
-
shadowColor: dark ? "#0a84ff" : "#000"
|
|
843
|
-
}
|
|
844
|
-
],
|
|
845
|
-
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
979
|
+
left: posX,
|
|
980
|
+
bottom: Animated2.add(new Animated2.Value(baseBottom), Animated2.multiply(posY, -1)),
|
|
981
|
+
backgroundColor: accentColor,
|
|
982
|
+
shadowColor: dark ? accentColor : "#000",
|
|
983
|
+
transform: [{ scale }]
|
|
846
984
|
}
|
|
847
|
-
|
|
985
|
+
],
|
|
986
|
+
onStartShouldSetResponder: () => !draggable,
|
|
987
|
+
onResponderGrant: handlePressIn,
|
|
988
|
+
onResponderRelease: handlePressOut,
|
|
989
|
+
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
848
990
|
}
|
|
849
991
|
);
|
|
850
992
|
};
|
|
851
993
|
var s2 = StyleSheet2.create({
|
|
852
994
|
fab: {
|
|
853
995
|
position: "absolute",
|
|
854
|
-
width:
|
|
855
|
-
height:
|
|
856
|
-
borderRadius:
|
|
996
|
+
width: FAB_SIZE,
|
|
997
|
+
height: FAB_SIZE,
|
|
998
|
+
borderRadius: FAB_SIZE / 2,
|
|
857
999
|
alignItems: "center",
|
|
858
1000
|
justifyContent: "center",
|
|
859
1001
|
zIndex: 9999,
|
|
@@ -895,7 +1037,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
895
1037
|
console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
|
|
896
1038
|
}
|
|
897
1039
|
const apiEndpoint = rawEndpoint ?? DEFAULT_API_ENDPOINT;
|
|
898
|
-
|
|
1040
|
+
useEffect3(() => {
|
|
899
1041
|
if (config.capture?.console !== false) {
|
|
900
1042
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
901
1043
|
}
|
|
@@ -944,7 +1086,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
944
1086
|
networkRef.current?.restore();
|
|
945
1087
|
};
|
|
946
1088
|
}, []);
|
|
947
|
-
|
|
1089
|
+
useEffect3(() => {
|
|
948
1090
|
let unsubscribe;
|
|
949
1091
|
try {
|
|
950
1092
|
const NetInfo = __require("@react-native-community/netinfo").default;
|
|
@@ -979,7 +1121,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
979
1121
|
}, []);
|
|
980
1122
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
981
1123
|
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
982
|
-
|
|
1124
|
+
useEffect3(() => {
|
|
983
1125
|
const trigger2 = config.widget?.trigger ?? "button";
|
|
984
1126
|
if (trigger2 !== "shake" && trigger2 !== "both") return;
|
|
985
1127
|
let disposed = false;
|
|
@@ -1060,7 +1202,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
1060
1202
|
const screenRef = useRef3(null);
|
|
1061
1203
|
const activityQueueRef = useRef3([]);
|
|
1062
1204
|
const rewardsFlushRef = useRef3(null);
|
|
1063
|
-
|
|
1205
|
+
useEffect3(() => {
|
|
1064
1206
|
const rewards = config.rewards;
|
|
1065
1207
|
if (!rewards?.enabled || !rewards.trackActivity) return;
|
|
1066
1208
|
const flushMs = Math.max(3e4, rewards.flushIntervalMs ?? 3e5);
|
|
@@ -1243,10 +1385,10 @@ function MushiTrigger({ children }) {
|
|
|
1243
1385
|
}
|
|
1244
1386
|
|
|
1245
1387
|
// src/capture/navigation-capture.ts
|
|
1246
|
-
import { useEffect as
|
|
1388
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
1247
1389
|
function useNavigationCapture(navigationRef) {
|
|
1248
1390
|
const historyRef = useRef4([]);
|
|
1249
|
-
|
|
1391
|
+
useEffect4(() => {
|
|
1250
1392
|
if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
|
|
1251
1393
|
const unsubscribe = navigationRef.addListener("state", () => {
|
|
1252
1394
|
const route = navigationRef.getCurrentRoute?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native SDK for Mushi Mushi — shake-to-report, bottom sheet widget, full context capture",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mushi-mushi/core": "^1.
|
|
36
|
+
"@mushi-mushi/core": "^1.14.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^19.2.14",
|