@diegotsi/flint-react 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +210 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +211 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/FlintWidget.tsx
|
|
2
|
-
import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
2
|
+
import { useState as useState3, useEffect as useEffect3, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
3
3
|
import { I18nextProvider, useTranslation as useTranslation2 } from "react-i18next";
|
|
4
4
|
|
|
5
5
|
// src/FlintModal.tsx
|
|
@@ -293,7 +293,8 @@ function FlintModal({
|
|
|
293
293
|
getConsoleLogs,
|
|
294
294
|
getNetworkErrors,
|
|
295
295
|
getReplayEvents,
|
|
296
|
-
getExternalReplayUrl
|
|
296
|
+
getExternalReplayUrl,
|
|
297
|
+
initialSelection = ""
|
|
297
298
|
}) {
|
|
298
299
|
const { t } = useTranslation();
|
|
299
300
|
const colors = resolveTheme(theme);
|
|
@@ -306,6 +307,12 @@ function FlintModal({
|
|
|
306
307
|
const [status, setStatus] = useState2("idle");
|
|
307
308
|
const [result, setResult] = useState2(null);
|
|
308
309
|
const [errorMsg, setErrorMsg] = useState2("");
|
|
310
|
+
const [mode, setMode] = useState2(initialSelection ? "text" : "bug");
|
|
311
|
+
const [textOriginal, setTextOriginal] = useState2(initialSelection);
|
|
312
|
+
const [textSuggested, setTextSuggested] = useState2("");
|
|
313
|
+
const [textLang, setTextLang] = useState2(
|
|
314
|
+
typeof document !== "undefined" ? document.documentElement.lang?.split("-")[0] || "en" : "en"
|
|
315
|
+
);
|
|
309
316
|
const fileRef = useRef2(null);
|
|
310
317
|
const overlayRef = useRef2(null);
|
|
311
318
|
useEffect2(() => {
|
|
@@ -326,7 +333,12 @@ function FlintModal({
|
|
|
326
333
|
);
|
|
327
334
|
const handleSubmit = async (e) => {
|
|
328
335
|
e.preventDefault();
|
|
329
|
-
|
|
336
|
+
const isText = mode === "text";
|
|
337
|
+
if (isText) {
|
|
338
|
+
if (!textOriginal.trim() || !textSuggested.trim()) return;
|
|
339
|
+
} else {
|
|
340
|
+
if (!description.trim()) return;
|
|
341
|
+
}
|
|
330
342
|
setStatus("submitting");
|
|
331
343
|
setErrorMsg("");
|
|
332
344
|
const collectedMeta = {
|
|
@@ -335,6 +347,13 @@ function FlintModal({
|
|
|
335
347
|
consoleLogs: getConsoleLogs(),
|
|
336
348
|
networkErrors: getNetworkErrors()
|
|
337
349
|
};
|
|
350
|
+
if (isText) {
|
|
351
|
+
collectedMeta.textIssue = {
|
|
352
|
+
original: textOriginal.trim(),
|
|
353
|
+
suggested: textSuggested.trim(),
|
|
354
|
+
lang: textLang
|
|
355
|
+
};
|
|
356
|
+
}
|
|
338
357
|
try {
|
|
339
358
|
const res = await submitReport(
|
|
340
359
|
serverUrl,
|
|
@@ -342,14 +361,15 @@ function FlintModal({
|
|
|
342
361
|
{
|
|
343
362
|
reporterId: user?.id ?? "anonymous",
|
|
344
363
|
reporterName: user?.name ?? "Anonymous",
|
|
345
|
-
description: description.trim(),
|
|
346
|
-
expectedBehavior: expectedBehavior.trim() || void 0,
|
|
364
|
+
description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
|
|
365
|
+
expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
|
|
347
366
|
externalReplayUrl: getExternalReplayUrl() || void 0,
|
|
348
|
-
severity,
|
|
367
|
+
severity: isText ? "P3" : severity,
|
|
349
368
|
url: window.location.href,
|
|
350
|
-
meta: collectedMeta
|
|
369
|
+
meta: collectedMeta,
|
|
370
|
+
label: isText ? "TEXT" : void 0
|
|
351
371
|
},
|
|
352
|
-
screenshot ?? void 0
|
|
372
|
+
!isText ? screenshot ?? void 0 : void 0
|
|
353
373
|
);
|
|
354
374
|
setResult(res);
|
|
355
375
|
setStatus("success");
|
|
@@ -584,7 +604,88 @@ function FlintModal({
|
|
|
584
604
|
}
|
|
585
605
|
),
|
|
586
606
|
/* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
|
|
587
|
-
/* @__PURE__ */
|
|
607
|
+
/* @__PURE__ */ jsx2("div", { style: {
|
|
608
|
+
display: "flex",
|
|
609
|
+
gap: 4,
|
|
610
|
+
marginBottom: 20,
|
|
611
|
+
background: colors.backgroundSecondary,
|
|
612
|
+
borderRadius: 12,
|
|
613
|
+
padding: 4,
|
|
614
|
+
border: `1px solid ${inputBorder}`
|
|
615
|
+
}, children: ["bug", "text"].map((m) => /* @__PURE__ */ jsx2(
|
|
616
|
+
"button",
|
|
617
|
+
{
|
|
618
|
+
type: "button",
|
|
619
|
+
onClick: () => setMode(m),
|
|
620
|
+
style: {
|
|
621
|
+
flex: 1,
|
|
622
|
+
padding: "8px 10px",
|
|
623
|
+
borderRadius: 9,
|
|
624
|
+
border: "none",
|
|
625
|
+
cursor: "pointer",
|
|
626
|
+
fontSize: 13,
|
|
627
|
+
fontWeight: mode === m ? 700 : 500,
|
|
628
|
+
fontFamily: "inherit",
|
|
629
|
+
transition: "background 0.15s, color 0.15s",
|
|
630
|
+
background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
|
|
631
|
+
color: mode === m ? colors.buttonText : colors.textMuted,
|
|
632
|
+
boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
|
|
633
|
+
},
|
|
634
|
+
children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
|
|
635
|
+
},
|
|
636
|
+
m
|
|
637
|
+
)) }),
|
|
638
|
+
mode === "text" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
639
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
640
|
+
/* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
|
|
641
|
+
/* @__PURE__ */ jsx2(
|
|
642
|
+
"textarea",
|
|
643
|
+
{
|
|
644
|
+
id: "flint-text-original",
|
|
645
|
+
style: { ...inputStyle, resize: "vertical", minHeight: 60 },
|
|
646
|
+
value: textOriginal,
|
|
647
|
+
onChange: (e) => setTextOriginal(e.target.value),
|
|
648
|
+
placeholder: "Text that is wrong on screen\u2026",
|
|
649
|
+
required: true
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
] }),
|
|
653
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
654
|
+
/* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-text-suggested", children: "Suggested correction" }),
|
|
655
|
+
/* @__PURE__ */ jsx2(
|
|
656
|
+
"textarea",
|
|
657
|
+
{
|
|
658
|
+
id: "flint-text-suggested",
|
|
659
|
+
style: { ...inputStyle, resize: "vertical", minHeight: 60 },
|
|
660
|
+
value: textSuggested,
|
|
661
|
+
onChange: (e) => setTextSuggested(e.target.value),
|
|
662
|
+
placeholder: "How it should read\u2026",
|
|
663
|
+
required: true
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20 }, children: [
|
|
668
|
+
/* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-text-lang", children: "Language" }),
|
|
669
|
+
/* @__PURE__ */ jsxs2(
|
|
670
|
+
"select",
|
|
671
|
+
{
|
|
672
|
+
id: "flint-text-lang",
|
|
673
|
+
value: textLang,
|
|
674
|
+
onChange: (e) => setTextLang(e.target.value),
|
|
675
|
+
style: {
|
|
676
|
+
...inputStyle,
|
|
677
|
+
appearance: "none",
|
|
678
|
+
cursor: "pointer"
|
|
679
|
+
},
|
|
680
|
+
children: [
|
|
681
|
+
/* @__PURE__ */ jsx2("option", { value: "en", children: "English (en)" }),
|
|
682
|
+
/* @__PURE__ */ jsx2("option", { value: "he", children: "\u05E2\u05D1\u05E8\u05D9\u05EA (he)" })
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
] })
|
|
687
|
+
] }),
|
|
688
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 18, display: mode === "text" ? "none" : void 0 }, children: [
|
|
588
689
|
/* @__PURE__ */ jsx2(FieldLabel, { colors, children: t("severityLabel") }),
|
|
589
690
|
/* @__PURE__ */ jsx2("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }, children: SEVERITIES.map((sev) => /* @__PURE__ */ jsx2(
|
|
590
691
|
SeverityButton,
|
|
@@ -603,7 +704,7 @@ function FlintModal({
|
|
|
603
704
|
sev
|
|
604
705
|
)) })
|
|
605
706
|
] }),
|
|
606
|
-
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
707
|
+
mode === "bug" && /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
607
708
|
/* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-description", children: t("whatIsBrokenLabel") }),
|
|
608
709
|
/* @__PURE__ */ jsx2(
|
|
609
710
|
"textarea",
|
|
@@ -617,7 +718,7 @@ function FlintModal({
|
|
|
617
718
|
}
|
|
618
719
|
)
|
|
619
720
|
] }),
|
|
620
|
-
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
721
|
+
mode === "bug" && /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
|
|
621
722
|
/* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-expected", children: t("expectedBehaviorLabel") }),
|
|
622
723
|
/* @__PURE__ */ jsx2(
|
|
623
724
|
"textarea",
|
|
@@ -630,7 +731,7 @@ function FlintModal({
|
|
|
630
731
|
}
|
|
631
732
|
)
|
|
632
733
|
] }),
|
|
633
|
-
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20 }, children: [
|
|
734
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20, display: mode === "text" ? "none" : void 0 }, children: [
|
|
634
735
|
/* @__PURE__ */ jsx2(FieldLabel, { colors, children: t("screenshotLabel") }),
|
|
635
736
|
screenshot ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
|
|
636
737
|
/* @__PURE__ */ jsx2(
|
|
@@ -1253,7 +1354,49 @@ function WidgetContent({
|
|
|
1253
1354
|
const { t } = useTranslation2();
|
|
1254
1355
|
const [open, setOpen] = useState3(false);
|
|
1255
1356
|
const [hovered, setHovered] = useState3(false);
|
|
1357
|
+
const pendingSelection = useRef3("");
|
|
1256
1358
|
const colors = resolveTheme(theme);
|
|
1359
|
+
const [selectionTooltip, setSelectionTooltip] = useState3(null);
|
|
1360
|
+
const tooltipRef = useRef3(null);
|
|
1361
|
+
const triggerRef = useRef3(null);
|
|
1362
|
+
const handleMouseUp = useCallback2((e) => {
|
|
1363
|
+
const target = e.target;
|
|
1364
|
+
if (tooltipRef.current?.contains(target)) return;
|
|
1365
|
+
if (triggerRef.current?.contains(target)) return;
|
|
1366
|
+
requestAnimationFrame(() => {
|
|
1367
|
+
const sel = window.getSelection();
|
|
1368
|
+
const text = sel?.toString().trim() ?? "";
|
|
1369
|
+
if (text.length < 2) {
|
|
1370
|
+
setSelectionTooltip(null);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const range = sel?.getRangeAt(0);
|
|
1374
|
+
if (!range) return;
|
|
1375
|
+
const rect = range.getBoundingClientRect();
|
|
1376
|
+
setSelectionTooltip({
|
|
1377
|
+
text,
|
|
1378
|
+
x: rect.left + rect.width / 2,
|
|
1379
|
+
y: rect.top - 8
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
}, []);
|
|
1383
|
+
const handleSelectionChange = useCallback2(() => {
|
|
1384
|
+
const text = window.getSelection()?.toString().trim() ?? "";
|
|
1385
|
+
if (text.length < 2) setSelectionTooltip(null);
|
|
1386
|
+
}, []);
|
|
1387
|
+
useEffect3(() => {
|
|
1388
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1389
|
+
document.addEventListener("selectionchange", handleSelectionChange);
|
|
1390
|
+
return () => {
|
|
1391
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1392
|
+
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
1393
|
+
};
|
|
1394
|
+
}, [handleMouseUp, handleSelectionChange]);
|
|
1395
|
+
const openWithSelection = (text) => {
|
|
1396
|
+
pendingSelection.current = text;
|
|
1397
|
+
setSelectionTooltip(null);
|
|
1398
|
+
setOpen(true);
|
|
1399
|
+
};
|
|
1257
1400
|
const consoleCollector = useRef3(null);
|
|
1258
1401
|
const networkCollector = useRef3(null);
|
|
1259
1402
|
const replayEvents = useRef3([]);
|
|
@@ -1295,6 +1438,10 @@ function WidgetContent({
|
|
|
1295
1438
|
/* @__PURE__ */ jsxs3(
|
|
1296
1439
|
"button",
|
|
1297
1440
|
{
|
|
1441
|
+
ref: triggerRef,
|
|
1442
|
+
onMouseDown: () => {
|
|
1443
|
+
pendingSelection.current = window.getSelection()?.toString().trim() ?? "";
|
|
1444
|
+
},
|
|
1298
1445
|
onClick: () => setOpen(true),
|
|
1299
1446
|
onMouseEnter: () => setHovered(true),
|
|
1300
1447
|
onMouseLeave: () => setHovered(false),
|
|
@@ -1327,6 +1474,44 @@ function WidgetContent({
|
|
|
1327
1474
|
]
|
|
1328
1475
|
}
|
|
1329
1476
|
),
|
|
1477
|
+
selectionTooltip && !open && /* @__PURE__ */ jsxs3(
|
|
1478
|
+
"button",
|
|
1479
|
+
{
|
|
1480
|
+
ref: tooltipRef,
|
|
1481
|
+
onClick: () => openWithSelection(selectionTooltip.text),
|
|
1482
|
+
style: {
|
|
1483
|
+
position: "fixed",
|
|
1484
|
+
left: Math.max(8, Math.min(selectionTooltip.x - 70, window.innerWidth - 148)),
|
|
1485
|
+
top: Math.max(8, selectionTooltip.y - 36),
|
|
1486
|
+
zIndex: zIndex + 1,
|
|
1487
|
+
display: "flex",
|
|
1488
|
+
alignItems: "center",
|
|
1489
|
+
gap: 6,
|
|
1490
|
+
padding: "6px 12px",
|
|
1491
|
+
borderRadius: 10,
|
|
1492
|
+
border: "none",
|
|
1493
|
+
background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
|
|
1494
|
+
color: colors.buttonText,
|
|
1495
|
+
fontSize: 12,
|
|
1496
|
+
fontWeight: 600,
|
|
1497
|
+
cursor: "pointer",
|
|
1498
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1499
|
+
boxShadow: `0 4px 20px rgba(0,0,0,0.25), 0 0 12px ${colors.accent}40`,
|
|
1500
|
+
animation: "flint-tooltip-in 0.15s ease-out",
|
|
1501
|
+
whiteSpace: "nowrap"
|
|
1502
|
+
},
|
|
1503
|
+
children: [
|
|
1504
|
+
/* @__PURE__ */ jsx3(TextIcon, {}),
|
|
1505
|
+
"Report text issue"
|
|
1506
|
+
]
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
/* @__PURE__ */ jsx3("style", { children: `
|
|
1510
|
+
@keyframes flint-tooltip-in {
|
|
1511
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
1512
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1513
|
+
}
|
|
1514
|
+
` }),
|
|
1330
1515
|
open && /* @__PURE__ */ jsx3(
|
|
1331
1516
|
FlintModal,
|
|
1332
1517
|
{
|
|
@@ -1336,16 +1521,28 @@ function WidgetContent({
|
|
|
1336
1521
|
meta,
|
|
1337
1522
|
theme,
|
|
1338
1523
|
zIndex,
|
|
1339
|
-
onClose: () =>
|
|
1524
|
+
onClose: () => {
|
|
1525
|
+
setOpen(false);
|
|
1526
|
+
pendingSelection.current = "";
|
|
1527
|
+
},
|
|
1340
1528
|
getEnvironment: collectEnvironment,
|
|
1341
1529
|
getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
|
|
1342
1530
|
getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
|
|
1343
1531
|
getReplayEvents: () => [...replayEvents.current],
|
|
1344
|
-
getExternalReplayUrl
|
|
1532
|
+
getExternalReplayUrl,
|
|
1533
|
+
initialSelection: pendingSelection.current
|
|
1345
1534
|
}
|
|
1346
1535
|
)
|
|
1347
1536
|
] });
|
|
1348
1537
|
}
|
|
1538
|
+
function TextIcon() {
|
|
1539
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1540
|
+
/* @__PURE__ */ jsx3("path", { d: "M17 10H3" }),
|
|
1541
|
+
/* @__PURE__ */ jsx3("path", { d: "M21 6H3" }),
|
|
1542
|
+
/* @__PURE__ */ jsx3("path", { d: "M21 14H3" }),
|
|
1543
|
+
/* @__PURE__ */ jsx3("path", { d: "M17 18H3" })
|
|
1544
|
+
] });
|
|
1545
|
+
}
|
|
1349
1546
|
function SparkIcon2() {
|
|
1350
1547
|
return /* @__PURE__ */ jsx3(
|
|
1351
1548
|
"svg",
|