@jarve/bug-reporter 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -24,4 +24,4 @@ interface JarveBugReporterProps {
24
24
  }
25
25
  declare function JarveBugReporter({ apiUrl, apiKey, user, buttonPosition, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
26
26
 
27
- export { type BugReporterApiConfig, type BugReporterUser, JarveBugReporter };
27
+ export { type BugReporterApiConfig, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter };
package/dist/index.d.ts CHANGED
@@ -24,4 +24,4 @@ interface JarveBugReporterProps {
24
24
  }
25
25
  declare function JarveBugReporter({ apiUrl, apiKey, user, buttonPosition, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
26
26
 
27
- export { type BugReporterApiConfig, type BugReporterUser, JarveBugReporter };
27
+ export { type BugReporterApiConfig, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter };
package/dist/index.js CHANGED
@@ -43,9 +43,10 @@ __export(index_exports, {
43
43
  module.exports = __toCommonJS(index_exports);
44
44
 
45
45
  // src/bug-reporter.tsx
46
- var import_react3 = require("react");
46
+ var import_react4 = require("react");
47
47
 
48
48
  // src/floating-button.tsx
49
+ var import_react = require("react");
49
50
  var import_lucide_react = require("lucide-react");
50
51
 
51
52
  // src/cn.ts
@@ -57,31 +58,97 @@ function cn(...inputs) {
57
58
 
58
59
  // src/floating-button.tsx
59
60
  var import_jsx_runtime = require("react/jsx-runtime");
61
+ var STACK_OFFSET = 56;
60
62
  function FloatingButton({ isActive, onClick, position = "right" }) {
61
- const sideClasses = position === "left" ? "left-4 md:left-6" : "right-4 md:right-6";
62
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
63
- "button",
63
+ const [hovered, setHovered] = (0, import_react.useState)(false);
64
+ const [stackOffset, setStackOffset] = (0, import_react.useState)(0);
65
+ const ref = (0, import_react.useRef)(null);
66
+ const isLeft = position === "left";
67
+ const sideClasses = isLeft ? "left-4 md:left-6" : "right-4 md:right-6";
68
+ (0, import_react.useEffect)(() => {
69
+ const el = ref.current;
70
+ if (!el) return;
71
+ let rafId;
72
+ const recalculate = () => {
73
+ const widgets = Array.from(
74
+ document.querySelectorAll(`[data-jarve-widget][data-jarve-position="${position}"]`)
75
+ );
76
+ widgets.sort(
77
+ (a, b) => (a.getAttribute("data-jarve-widget") || "").localeCompare(
78
+ b.getAttribute("data-jarve-widget") || ""
79
+ )
80
+ );
81
+ const index = widgets.indexOf(el);
82
+ setStackOffset(index > 0 ? index * STACK_OFFSET : 0);
83
+ };
84
+ recalculate();
85
+ const observer = new MutationObserver(() => {
86
+ cancelAnimationFrame(rafId);
87
+ rafId = requestAnimationFrame(recalculate);
88
+ });
89
+ observer.observe(document.body, { childList: true, subtree: true });
90
+ return () => {
91
+ observer.disconnect();
92
+ cancelAnimationFrame(rafId);
93
+ };
94
+ }, [position]);
95
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
96
+ "div",
64
97
  {
65
- onClick,
66
- className: cn(
67
- "fixed z-[9999] flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
68
- "hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
69
- // size + vertical position
70
- "bottom-4 h-11 w-11 md:bottom-6 md:h-12 md:w-12",
71
- // horizontal side
72
- sideClasses,
73
- // active vs idle colors
74
- isActive ? "animate-pulse bg-red-500 text-white focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400"
75
- ),
76
- title: isActive ? "Cancel bug capture" : "Report a bug",
77
- "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
78
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
98
+ ref,
99
+ "data-jarve-widget": "bug-reporter",
100
+ "data-jarve-position": position,
101
+ className: cn("fixed z-[9999]", "bottom-4 md:bottom-6", sideClasses),
102
+ style: stackOffset > 0 ? { transform: `translateY(-${stackOffset}px)` } : void 0,
103
+ onMouseEnter: () => setHovered(true),
104
+ onMouseLeave: () => setHovered(false),
105
+ children: [
106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
107
+ "div",
108
+ {
109
+ className: cn(
110
+ "pointer-events-none absolute bottom-full mb-2 w-max max-w-[200px] rounded-lg bg-gray-900 px-3 py-2 text-xs leading-relaxed text-white shadow-lg transition-all duration-200",
111
+ isLeft ? "left-0" : "right-0",
112
+ hovered && !isActive ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0"
113
+ ),
114
+ children: [
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold", children: "Report a bug" }),
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
117
+ "Click to screenshot an issue and chat with AI to submit a bug report.",
118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
119
+ "div",
120
+ {
121
+ className: cn(
122
+ "absolute top-full h-0 w-0 border-x-[6px] border-t-[6px] border-x-transparent border-t-gray-900",
123
+ isLeft ? "left-4" : "right-4"
124
+ )
125
+ }
126
+ )
127
+ ]
128
+ }
129
+ ),
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ "button",
132
+ {
133
+ onClick,
134
+ className: cn(
135
+ "flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
136
+ "hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
137
+ "h-11 w-11 md:h-12 md:w-12",
138
+ isActive ? "animate-pulse bg-red-500 text-white focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400"
139
+ ),
140
+ title: isActive ? "Cancel bug capture" : "Report a bug",
141
+ "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
142
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
143
+ }
144
+ )
145
+ ]
79
146
  }
80
147
  );
81
148
  }
82
149
 
83
150
  // src/capture-overlay.tsx
84
- var import_react = require("react");
151
+ var import_react2 = require("react");
85
152
  var import_html_to_image = require("html-to-image");
86
153
 
87
154
  // src/utils.ts
@@ -410,23 +477,23 @@ function CaptureOverlay({
410
477
  onCapture,
411
478
  onCancel
412
479
  }) {
413
- const [hoveredElement, setHoveredElement] = (0, import_react.useState)(null);
414
- const [hoveredRect, setHoveredRect] = (0, import_react.useState)(null);
415
- const [isCapturing3, setIsCapturing] = (0, import_react.useState)(false);
416
- const [isTouchMode, setIsTouchMode] = (0, import_react.useState)(false);
417
- const [selectedSection, setSelectedSection] = (0, import_react.useState)(null);
418
- const [selectedRect, setSelectedRect] = (0, import_react.useState)(null);
419
- const [selectedTarget, setSelectedTarget] = (0, import_react.useState)(null);
420
- const overlayRef = (0, import_react.useRef)(null);
421
- const hoveredElementRef = (0, import_react.useRef)(null);
422
- const rafRef = (0, import_react.useRef)(null);
423
- const touchCoordsRef = (0, import_react.useRef)(null);
424
- (0, import_react.useEffect)(() => {
480
+ const [hoveredElement, setHoveredElement] = (0, import_react2.useState)(null);
481
+ const [hoveredRect, setHoveredRect] = (0, import_react2.useState)(null);
482
+ const [isCapturing3, setIsCapturing] = (0, import_react2.useState)(false);
483
+ const [isTouchMode, setIsTouchMode] = (0, import_react2.useState)(false);
484
+ const [selectedSection, setSelectedSection] = (0, import_react2.useState)(null);
485
+ const [selectedRect, setSelectedRect] = (0, import_react2.useState)(null);
486
+ const [selectedTarget, setSelectedTarget] = (0, import_react2.useState)(null);
487
+ const overlayRef = (0, import_react2.useRef)(null);
488
+ const hoveredElementRef = (0, import_react2.useRef)(null);
489
+ const rafRef = (0, import_react2.useRef)(null);
490
+ const touchCoordsRef = (0, import_react2.useRef)(null);
491
+ (0, import_react2.useEffect)(() => {
425
492
  if (isActive) {
426
493
  setIsTouchMode(isTouchCapable());
427
494
  }
428
495
  }, [isActive]);
429
- const captureScreenshot = (0, import_react.useCallback)(
496
+ const captureScreenshot = (0, import_react2.useCallback)(
430
497
  async (section, target, coords) => {
431
498
  const elementInfo = collectElementInfo(target, section, coords);
432
499
  setIsCapturing(true);
@@ -504,7 +571,7 @@ function CaptureOverlay({
504
571
  },
505
572
  [siteId, reporterName, reporterEmail, onCapture]
506
573
  );
507
- const handleMouseMove = (0, import_react.useCallback)(
574
+ const handleMouseMove = (0, import_react2.useCallback)(
508
575
  (e) => {
509
576
  if (!isActive || isCapturing3 || isTouchMode) return;
510
577
  if (rafRef.current) return;
@@ -526,7 +593,7 @@ function CaptureOverlay({
526
593
  },
527
594
  [isActive, isCapturing3, isTouchMode]
528
595
  );
529
- const handleClick = (0, import_react.useCallback)(
596
+ const handleClick = (0, import_react2.useCallback)(
530
597
  async (e) => {
531
598
  if (!isActive || isCapturing3 || isTouchMode) return;
532
599
  const target = e.target;
@@ -540,7 +607,7 @@ function CaptureOverlay({
540
607
  },
541
608
  [isActive, isCapturing3, isTouchMode, captureScreenshot]
542
609
  );
543
- const handleTouchEnd = (0, import_react.useCallback)(
610
+ const handleTouchEnd = (0, import_react2.useCallback)(
544
611
  (e) => {
545
612
  if (!isActive || isCapturing3) return;
546
613
  const touch = e.changedTouches[0];
@@ -557,11 +624,11 @@ function CaptureOverlay({
557
624
  },
558
625
  [isActive, isCapturing3]
559
626
  );
560
- const handleConfirmCapture = (0, import_react.useCallback)(async () => {
627
+ const handleConfirmCapture = (0, import_react2.useCallback)(async () => {
561
628
  if (!selectedSection || !selectedTarget || !touchCoordsRef.current) return;
562
629
  await captureScreenshot(selectedSection, selectedTarget, touchCoordsRef.current);
563
630
  }, [selectedSection, selectedTarget, captureScreenshot]);
564
- const handlePointerDown = (0, import_react.useCallback)(
631
+ const handlePointerDown = (0, import_react2.useCallback)(
565
632
  (e) => {
566
633
  if (!isActive) return;
567
634
  if (e.pointerType === "touch") {
@@ -572,7 +639,7 @@ function CaptureOverlay({
572
639
  },
573
640
  [isActive]
574
641
  );
575
- const handleKeyDown = (0, import_react.useCallback)(
642
+ const handleKeyDown = (0, import_react2.useCallback)(
576
643
  (e) => {
577
644
  if (e.key === "Escape" && isActive) {
578
645
  e.preventDefault();
@@ -582,7 +649,7 @@ function CaptureOverlay({
582
649
  },
583
650
  [isActive, onCancel]
584
651
  );
585
- const handleScroll = (0, import_react.useCallback)(() => {
652
+ const handleScroll = (0, import_react2.useCallback)(() => {
586
653
  if (hoveredElementRef.current) {
587
654
  setHoveredRect(hoveredElementRef.current.getBoundingClientRect());
588
655
  }
@@ -590,7 +657,7 @@ function CaptureOverlay({
590
657
  setSelectedRect(selectedSection.getBoundingClientRect());
591
658
  }
592
659
  }, [selectedSection]);
593
- (0, import_react.useEffect)(() => {
660
+ (0, import_react2.useEffect)(() => {
594
661
  if (!isActive) {
595
662
  setHoveredElement(null);
596
663
  setHoveredRect(null);
@@ -712,7 +779,7 @@ function CaptureOverlay({
712
779
  }
713
780
 
714
781
  // src/report-modal.tsx
715
- var import_react2 = require("react");
782
+ var import_react3 = require("react");
716
783
  var import_lucide_react2 = require("lucide-react");
717
784
  var import_jsx_runtime3 = require("react/jsx-runtime");
718
785
  function ReportModal({
@@ -723,24 +790,24 @@ function ReportModal({
723
790
  user,
724
791
  onClose
725
792
  }) {
726
- const [messages, setMessages] = (0, import_react2.useState)([]);
727
- const [input, setInput] = (0, import_react2.useState)("");
728
- const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
729
- const [modalState, setModalState] = (0, import_react2.useState)("chatting");
730
- const [reportId, setReportId] = (0, import_react2.useState)(null);
731
- const [screenshotUrl, setScreenshotUrl] = (0, import_react2.useState)(null);
732
- const [errorMessage, setErrorMessage] = (0, import_react2.useState)(null);
733
- const chatEndRef = (0, import_react2.useRef)(null);
734
- const inputRef = (0, import_react2.useRef)(null);
735
- const hasInitRef = (0, import_react2.useRef)(false);
736
- const apiHeaders = (0, import_react2.useMemo)(
793
+ const [messages, setMessages] = (0, import_react3.useState)([]);
794
+ const [input, setInput] = (0, import_react3.useState)("");
795
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
796
+ const [modalState, setModalState] = (0, import_react3.useState)("chatting");
797
+ const [reportId, setReportId] = (0, import_react3.useState)(null);
798
+ const [screenshotUrl, setScreenshotUrl] = (0, import_react3.useState)(null);
799
+ const [errorMessage, setErrorMessage] = (0, import_react3.useState)(null);
800
+ const chatEndRef = (0, import_react3.useRef)(null);
801
+ const inputRef = (0, import_react3.useRef)(null);
802
+ const hasInitRef = (0, import_react3.useRef)(false);
803
+ const apiHeaders = (0, import_react3.useMemo)(
737
804
  () => ({
738
805
  "Content-Type": "application/json",
739
806
  "X-Bug-Reporter-Key": apiConfig.apiKey
740
807
  }),
741
808
  [apiConfig.apiKey]
742
809
  );
743
- (0, import_react2.useEffect)(() => {
810
+ (0, import_react3.useEffect)(() => {
744
811
  if ((captureResult == null ? void 0 : captureResult.screenshot) && captureResult.screenshot.size > 0) {
745
812
  const url = URL.createObjectURL(captureResult.screenshot);
746
813
  setScreenshotUrl((prev) => {
@@ -757,7 +824,7 @@ function ReportModal({
757
824
  return null;
758
825
  });
759
826
  }, [captureResult]);
760
- const sendInitialMessage = (0, import_react2.useCallback)(async () => {
827
+ const sendInitialMessage = (0, import_react3.useCallback)(async () => {
761
828
  if (!captureResult) return;
762
829
  setIsLoading(true);
763
830
  try {
@@ -798,23 +865,23 @@ function ReportModal({
798
865
  setIsLoading(false);
799
866
  }
800
867
  }, [captureResult, apiConfig.apiUrl, apiHeaders]);
801
- (0, import_react2.useEffect)(() => {
868
+ (0, import_react3.useEffect)(() => {
802
869
  if (isOpen && captureResult && !hasInitRef.current) {
803
870
  hasInitRef.current = true;
804
871
  sendInitialMessage();
805
872
  }
806
873
  }, [isOpen, captureResult, sendInitialMessage]);
807
- (0, import_react2.useEffect)(() => {
874
+ (0, import_react3.useEffect)(() => {
808
875
  var _a;
809
876
  (_a = chatEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
810
877
  }, [messages]);
811
- (0, import_react2.useEffect)(() => {
878
+ (0, import_react3.useEffect)(() => {
812
879
  var _a;
813
880
  if (isOpen && !isLoading) {
814
881
  (_a = inputRef.current) == null ? void 0 : _a.focus();
815
882
  }
816
883
  }, [isOpen, isLoading, messages]);
817
- const submitReport = (0, import_react2.useCallback)(
884
+ const submitReport = (0, import_react3.useCallback)(
818
885
  async (conversation, structuredReport) => {
819
886
  if (!captureResult || modalState !== "chatting") return;
820
887
  setModalState("submitting");
@@ -878,7 +945,7 @@ function ReportModal({
878
945
  },
879
946
  [captureResult, apiConfig.apiUrl, apiHeaders, modalState]
880
947
  );
881
- const handleManualSubmit = (0, import_react2.useCallback)(() => {
948
+ const handleManualSubmit = (0, import_react3.useCallback)(() => {
882
949
  submitReport(messages);
883
950
  }, [submitReport, messages]);
884
951
  async function sendMessage() {
@@ -1113,10 +1180,10 @@ function JarveBugReporter({
1113
1180
  children
1114
1181
  }) {
1115
1182
  const safeApiKey = apiKey || "";
1116
- const [captureMode, setCaptureMode] = (0, import_react3.useState)(false);
1117
- const [captureResult, setCaptureResult] = (0, import_react3.useState)(null);
1118
- const [showModal, setShowModal] = (0, import_react3.useState)(false);
1119
- (0, import_react3.useEffect)(() => {
1183
+ const [captureMode, setCaptureMode] = (0, import_react4.useState)(false);
1184
+ const [captureResult, setCaptureResult] = (0, import_react4.useState)(null);
1185
+ const [showModal, setShowModal] = (0, import_react4.useState)(false);
1186
+ (0, import_react4.useEffect)(() => {
1120
1187
  startCapturing();
1121
1188
  startNetworkCapture();
1122
1189
  return () => {
@@ -1124,18 +1191,18 @@ function JarveBugReporter({
1124
1191
  stopNetworkCapture();
1125
1192
  };
1126
1193
  }, []);
1127
- const toggleCaptureMode = (0, import_react3.useCallback)(() => {
1194
+ const toggleCaptureMode = (0, import_react4.useCallback)(() => {
1128
1195
  setCaptureMode((prev) => !prev);
1129
1196
  }, []);
1130
- const handleCapture = (0, import_react3.useCallback)((result) => {
1197
+ const handleCapture = (0, import_react4.useCallback)((result) => {
1131
1198
  setCaptureResult(result);
1132
1199
  setCaptureMode(false);
1133
1200
  setShowModal(true);
1134
1201
  }, []);
1135
- const handleCancelCapture = (0, import_react3.useCallback)(() => {
1202
+ const handleCancelCapture = (0, import_react4.useCallback)(() => {
1136
1203
  setCaptureMode(false);
1137
1204
  }, []);
1138
- const handleCloseModal = (0, import_react3.useCallback)(() => {
1205
+ const handleCloseModal = (0, import_react4.useCallback)(() => {
1139
1206
  setShowModal(false);
1140
1207
  setCaptureResult(null);
1141
1208
  clearCapturedErrors();