@diegotsi/flint-react 2.4.7 → 2.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 CHANGED
@@ -239,6 +239,7 @@ function FlintModal({
239
239
  initialSelection = "",
240
240
  enableScreenshot = true,
241
241
  enableTextIssues = true,
242
+ enableFeatureRequests = false,
242
243
  statusPageUrl,
243
244
  onBeforeSubmit,
244
245
  onSuccess,
@@ -274,9 +275,14 @@ function FlintModal({
274
275
  const [status, setStatus] = (0, import_react2.useState)("idle");
275
276
  const [result, setResult] = (0, import_react2.useState)(null);
276
277
  const [errorMsg, setErrorMsg] = (0, import_react2.useState)("");
277
- const [mode, setMode] = (0, import_react2.useState)(enableTextIssues && initialSelection ? "text" : "bug");
278
+ const hasInitialSelection = enableTextIssues && !!initialSelection;
279
+ const hasMultipleModes = enableTextIssues || enableFeatureRequests;
280
+ const [step, setStep] = (0, import_react2.useState)(hasInitialSelection || !hasMultipleModes ? "form" : "selector");
281
+ const [mode, setMode] = (0, import_react2.useState)(hasInitialSelection ? "text" : "bug");
278
282
  const [textOriginal, setTextOriginal] = (0, import_react2.useState)(initialSelection);
279
283
  const [textSuggested, setTextSuggested] = (0, import_react2.useState)("");
284
+ const [featureDescription, setFeatureDescription] = (0, import_react2.useState)("");
285
+ const [featureUseCase, setFeatureUseCase] = (0, import_react2.useState)("");
280
286
  const [textLang, setTextLang] = (0, import_react2.useState)(
281
287
  typeof document !== "undefined" ? document.documentElement.lang?.split("-")[0] || "en" : "en"
282
288
  );
@@ -301,8 +307,11 @@ function FlintModal({
301
307
  const handleSubmit = async (e) => {
302
308
  e.preventDefault();
303
309
  const isText = mode === "text";
310
+ const isFeature = mode === "feature";
304
311
  if (isText) {
305
312
  if (!textOriginal.trim() || !textSuggested.trim()) return;
313
+ } else if (isFeature) {
314
+ if (!featureDescription.trim()) return;
306
315
  } else {
307
316
  if (!description.trim()) return;
308
317
  }
@@ -322,6 +331,11 @@ function FlintModal({
322
331
  lang: textLang
323
332
  };
324
333
  }
334
+ if (isFeature) {
335
+ collectedMeta.featureRequest = {
336
+ useCase: featureUseCase.trim()
337
+ };
338
+ }
325
339
  if (needsIdentity) {
326
340
  try {
327
341
  if (reporterName.trim()) localStorage.setItem("flint_reporter_name", reporterName.trim());
@@ -333,14 +347,15 @@ function FlintModal({
333
347
  reporterId: user?.id ?? (reporterEmail.trim() || "anonymous"),
334
348
  reporterName: user?.name ?? (reporterName.trim() || "Anonymous"),
335
349
  reporterEmail: user?.email ?? (reporterEmail.trim() || void 0),
336
- description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
337
- expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
350
+ description: isFeature ? featureDescription.trim() : isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
351
+ expectedBehavior: !isText && !isFeature ? expectedBehavior.trim() || void 0 : void 0,
338
352
  externalReplayUrl: getExternalReplayUrl() || void 0,
339
- severity: isText ? "P3" : severity,
353
+ severity: isText || isFeature ? "P3" : severity,
340
354
  url: window.location.href,
341
355
  meta: collectedMeta,
342
- label: isText ? "TEXT" : void 0,
343
- source: isText ? "text_issue" : "widget"
356
+ label: isText ? "TEXT" : isFeature ? "FEATURE" : void 0,
357
+ source: isFeature ? "feature_request" : isText ? "text_issue" : "widget",
358
+ type: isFeature ? "FEATURE_REQUEST" : "BUG"
344
359
  };
345
360
  if (onBeforeSubmit) {
346
361
  const proceed = await onBeforeSubmit(payload);
@@ -350,7 +365,12 @@ function FlintModal({
350
365
  }
351
366
  }
352
367
  try {
353
- const res = await (0, import_flint_core.submitReport)(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
368
+ const res = await (0, import_flint_core.submitReport)(
369
+ serverUrl,
370
+ projectKey,
371
+ payload,
372
+ !isText && !isFeature ? screenshot ?? void 0 : void 0
373
+ );
354
374
  setResult(res);
355
375
  setStatus("success");
356
376
  onSuccess?.(res);
@@ -679,365 +699,470 @@ function FlintModal({
679
699
  }
680
700
  ),
681
701
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
682
- enableTextIssues && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
683
- "div",
684
- {
685
- style: {
686
- display: "flex",
687
- gap: 4,
688
- marginBottom: 20,
689
- background: colors.backgroundSecondary,
690
- borderRadius: 12,
691
- padding: 4,
692
- border: `1px solid ${inputBorder}`
693
- },
694
- children: ["bug", "text"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
695
- "button",
696
- {
697
- type: "button",
698
- onClick: () => setMode(m),
699
- style: {
700
- flex: 1,
701
- padding: "8px 10px",
702
- borderRadius: 9,
703
- border: "none",
704
- cursor: "pointer",
705
- fontSize: 13,
706
- fontWeight: mode === m ? 700 : 500,
707
- fontFamily: "inherit",
708
- transition: "background 0.15s, color 0.15s",
709
- background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
710
- color: mode === m ? colors.buttonText : colors.textMuted,
711
- boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
712
- },
713
- children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
714
- },
715
- m
716
- ))
717
- }
718
- ),
719
- mode === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
720
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
721
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
722
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
723
- "textarea",
724
- {
725
- id: "flint-text-original",
726
- style: { ...inputStyle, resize: "vertical", minHeight: 60 },
727
- value: textOriginal,
728
- onChange: (e) => setTextOriginal(e.target.value),
729
- placeholder: "Text that is wrong on screen\u2026",
730
- required: true
731
- }
732
- )
702
+ step === "selector" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
703
+ needsIdentity && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, marginBottom: 18 }, children: [
704
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
705
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-reporter-name", children: t("nameLabel", "Your name") }),
706
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
707
+ "input",
708
+ {
709
+ id: "flint-reporter-name",
710
+ type: "text",
711
+ style: inputStyle,
712
+ value: reporterName,
713
+ onChange: (e) => setReporterName(e.target.value),
714
+ placeholder: "Jane Doe"
715
+ }
716
+ )
717
+ ] }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-reporter-email", children: t("emailLabel", "Email") }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
721
+ "input",
722
+ {
723
+ id: "flint-reporter-email",
724
+ type: "email",
725
+ style: inputStyle,
726
+ value: reporterEmail,
727
+ onChange: (e) => setReporterEmail(e.target.value),
728
+ placeholder: "jane@company.com"
729
+ }
730
+ )
731
+ ] })
733
732
  ] }),
734
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
735
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-suggested", children: "Suggested correction" }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: "What would you like to report?" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8, marginBottom: 16 }, children: [
736
735
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
737
- "textarea",
736
+ TypeSelectorButton,
738
737
  {
739
- id: "flint-text-suggested",
740
- style: { ...inputStyle, resize: "vertical", minHeight: 60 },
741
- value: textSuggested,
742
- onChange: (e) => setTextSuggested(e.target.value),
743
- placeholder: "How it should read\u2026",
744
- required: true
738
+ emoji: "\u{1F41B}",
739
+ title: "Bug Report",
740
+ subtitle: "Something isn't working",
741
+ colors,
742
+ inputBorder,
743
+ onClick: () => {
744
+ setMode("bug");
745
+ setStep("form");
746
+ }
745
747
  }
746
- )
747
- ] }),
748
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20 }, children: [
749
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-lang", children: "Language" }),
750
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
751
- "select",
748
+ ),
749
+ enableFeatureRequests && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
750
+ TypeSelectorButton,
752
751
  {
753
- id: "flint-text-lang",
754
- value: textLang,
755
- onChange: (e) => setTextLang(e.target.value),
756
- style: {
757
- ...inputStyle,
758
- appearance: "none",
759
- cursor: "pointer"
760
- },
761
- children: [
762
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "en", children: "English (en)" }),
763
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "he", children: "\u05E2\u05D1\u05E8\u05D9\u05EA (he)" })
764
- ]
752
+ emoji: "\u{1F4A1}",
753
+ title: "Feature Request / Improvement",
754
+ subtitle: "I have an idea or suggestion",
755
+ colors,
756
+ inputBorder,
757
+ onClick: () => {
758
+ setMode("feature");
759
+ setStep("form");
760
+ }
765
761
  }
766
- )
767
- ] })
768
- ] }),
769
- needsIdentity && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, marginBottom: 14 }, children: [
770
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
771
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-reporter-name", children: t("nameLabel", "Your name") }),
772
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
773
- "input",
762
+ ),
763
+ enableTextIssues && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
764
+ TypeSelectorButton,
774
765
  {
775
- id: "flint-reporter-name",
776
- type: "text",
777
- style: inputStyle,
778
- value: reporterName,
779
- onChange: (e) => setReporterName(e.target.value),
780
- placeholder: "Jane Doe"
766
+ emoji: "\u270F\uFE0F",
767
+ title: "Text Issue",
768
+ subtitle: "There's a text or translation problem",
769
+ colors,
770
+ inputBorder,
771
+ onClick: () => {
772
+ setMode("text");
773
+ setStep("form");
774
+ }
781
775
  }
782
776
  )
783
777
  ] }),
784
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
785
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-reporter-email", children: t("emailLabel", "Email") }),
786
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
787
- "input",
788
- {
789
- id: "flint-reporter-email",
790
- type: "email",
791
- style: inputStyle,
792
- value: reporterEmail,
793
- onChange: (e) => setReporterEmail(e.target.value),
794
- placeholder: "jane@company.com"
795
- }
796
- )
797
- ] })
798
- ] }),
799
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 18, display: mode === "text" ? "none" : void 0 }, children: [
800
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("severityLabel") }),
801
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }, children: SEVERITIES.map((sev) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
802
- SeverityButton,
803
- {
804
- sev,
805
- label: t(`severity_${sev}_label`),
806
- selected: severity === sev,
807
- hint: t(`severity_${sev}_hint`),
808
- color: SEV_COLOR[sev],
809
- accent: colors.accent,
810
- border: inputBorder,
811
- bg: colors.backgroundSecondary,
812
- text: colors.text,
813
- onClick: () => setSeverity(sev)
814
- },
815
- sev
816
- )) })
817
- ] }),
818
- mode === "bug" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
819
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-description", children: t("whatIsBrokenLabel") }),
820
778
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
821
- "textarea",
779
+ "button",
822
780
  {
823
- id: "flint-description",
824
- style: { ...inputStyle, resize: "vertical", minHeight: 80 },
825
- value: description,
826
- onChange: (e) => setDescription(e.target.value),
827
- placeholder: t("whatIsBrokenPlaceholder"),
828
- required: true
781
+ type: "button",
782
+ onClick: onClose,
783
+ style: {
784
+ width: "100%",
785
+ padding: "10px",
786
+ background: "none",
787
+ border: "none",
788
+ cursor: "pointer",
789
+ fontSize: 15,
790
+ color: colors.textMuted,
791
+ fontFamily: "inherit",
792
+ borderRadius: 8
793
+ },
794
+ children: t("cancel")
829
795
  }
830
796
  )
831
797
  ] }),
832
- mode === "bug" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
833
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-expected", children: t("expectedBehaviorLabel") }),
834
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
835
- "textarea",
798
+ step === "form" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
799
+ !hasInitialSelection && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
800
+ "button",
836
801
  {
837
- id: "flint-expected",
838
- style: { ...inputStyle, resize: "vertical", minHeight: 72 },
839
- value: expectedBehavior,
840
- onChange: (e) => setExpectedBehavior(e.target.value),
841
- placeholder: t("expectedBehaviorPlaceholder")
802
+ type: "button",
803
+ onClick: () => setStep("selector"),
804
+ style: {
805
+ display: "flex",
806
+ alignItems: "center",
807
+ gap: 4,
808
+ background: "none",
809
+ border: "none",
810
+ cursor: "pointer",
811
+ fontSize: 13,
812
+ fontWeight: 600,
813
+ color: colors.textMuted,
814
+ fontFamily: "inherit",
815
+ padding: "0 0 12px",
816
+ marginBottom: 4
817
+ },
818
+ children: [
819
+ "\u2190 ",
820
+ mode === "bug" ? "\u{1F41B} Bug Report" : mode === "feature" ? "\u{1F4A1} Feature Request" : "\u270F\uFE0F Text Issue"
821
+ ]
842
822
  }
843
- )
844
- ] }),
845
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20, display: mode === "text" || !enableScreenshot ? "none" : void 0 }, children: [
846
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("screenshotLabel") }),
847
- screenshot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
848
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
849
- "img",
850
- {
851
- src: URL.createObjectURL(screenshot),
852
- alt: "Screenshot preview",
853
- style: { height: 60, borderRadius: 8, objectFit: "cover", border: `1px solid ${inputBorder}` }
854
- }
855
- ),
856
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
857
- "span",
858
- {
859
- style: {
860
- fontSize: 13,
861
- color: colors.textMuted,
862
- flex: 1,
863
- overflow: "hidden",
864
- textOverflow: "ellipsis",
865
- whiteSpace: "nowrap"
866
- },
867
- children: screenshot.name
868
- }
869
- ),
823
+ ),
824
+ mode === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
825
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
827
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
828
+ "textarea",
829
+ {
830
+ id: "flint-text-original",
831
+ style: { ...inputStyle, resize: "vertical", minHeight: 60 },
832
+ value: textOriginal,
833
+ onChange: (e) => setTextOriginal(e.target.value),
834
+ placeholder: "Text that is wrong on screen\u2026",
835
+ required: true
836
+ }
837
+ )
838
+ ] }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
840
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-suggested", children: "Suggested correction" }),
841
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
842
+ "textarea",
843
+ {
844
+ id: "flint-text-suggested",
845
+ style: { ...inputStyle, resize: "vertical", minHeight: 60 },
846
+ value: textSuggested,
847
+ onChange: (e) => setTextSuggested(e.target.value),
848
+ placeholder: "How it should read\u2026",
849
+ required: true
850
+ }
851
+ )
852
+ ] }),
853
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20 }, children: [
854
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-lang", children: "Language" }),
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
856
+ "select",
857
+ {
858
+ id: "flint-text-lang",
859
+ value: textLang,
860
+ onChange: (e) => setTextLang(e.target.value),
861
+ style: {
862
+ ...inputStyle,
863
+ appearance: "none",
864
+ cursor: "pointer"
865
+ },
866
+ children: [
867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "en", children: "English (en)" }),
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "he", children: "\u05E2\u05D1\u05E8\u05D9\u05EA (he)" })
869
+ ]
870
+ }
871
+ )
872
+ ] })
873
+ ] }),
874
+ mode === "feature" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
875
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
876
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-feature-description", children: "Description" }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
878
+ "textarea",
879
+ {
880
+ id: "flint-feature-description",
881
+ style: { ...inputStyle, resize: "vertical", minHeight: 80 },
882
+ value: featureDescription,
883
+ onChange: (e) => setFeatureDescription(e.target.value),
884
+ placeholder: "Describe the feature you'd like to see...",
885
+ required: true
886
+ }
887
+ )
888
+ ] }),
889
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20 }, children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-feature-usecase", children: "Use case" }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
892
+ "textarea",
893
+ {
894
+ id: "flint-feature-usecase",
895
+ style: { ...inputStyle, resize: "vertical", minHeight: 72 },
896
+ value: featureUseCase,
897
+ onChange: (e) => setFeatureUseCase(e.target.value),
898
+ placeholder: "What problem would this solve for you?"
899
+ }
900
+ )
901
+ ] })
902
+ ] }),
903
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
904
+ "div",
905
+ {
906
+ style: { marginBottom: 18, display: mode === "text" || mode === "feature" ? "none" : void 0 },
907
+ children: [
908
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("severityLabel") }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }, children: SEVERITIES.map((sev) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
910
+ SeverityButton,
911
+ {
912
+ sev,
913
+ label: t(`severity_${sev}_label`),
914
+ selected: severity === sev,
915
+ hint: t(`severity_${sev}_hint`),
916
+ color: SEV_COLOR[sev],
917
+ accent: colors.accent,
918
+ border: inputBorder,
919
+ bg: colors.backgroundSecondary,
920
+ text: colors.text,
921
+ onClick: () => setSeverity(sev)
922
+ },
923
+ sev
924
+ )) })
925
+ ]
926
+ }
927
+ ),
928
+ mode === "bug" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
929
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-description", children: t("whatIsBrokenLabel") }),
870
930
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
871
- "button",
931
+ "textarea",
872
932
  {
873
- type: "button",
874
- onClick: () => setScreenshot(null),
875
- "aria-label": "Remove screenshot",
876
- style: {
877
- background: "none",
878
- border: "none",
879
- cursor: "pointer",
880
- fontSize: 18,
881
- color: colors.textMuted,
882
- lineHeight: 1,
883
- padding: "2px 6px",
884
- borderRadius: 6,
885
- fontFamily: "inherit"
886
- },
887
- children: "\xD7"
933
+ id: "flint-description",
934
+ style: { ...inputStyle, resize: "vertical", minHeight: 80 },
935
+ value: description,
936
+ onChange: (e) => setDescription(e.target.value),
937
+ placeholder: t("whatIsBrokenPlaceholder"),
938
+ required: true
888
939
  }
889
940
  )
890
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
891
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
892
- "button",
893
- {
894
- type: "button",
895
- onClick: () => fileRef.current?.click(),
896
- style: {
897
- flex: 1,
898
- display: "flex",
899
- alignItems: "center",
900
- justifyContent: "center",
901
- gap: 6,
902
- padding: "10px 13px",
903
- borderRadius: 10,
904
- border: `1px dashed ${inputBorder}`,
905
- cursor: "pointer",
906
- fontSize: 14,
907
- color: colors.textMuted,
908
- background: colors.backgroundSecondary,
909
- fontFamily: "inherit"
910
- },
911
- children: [
912
- "\u{1F4CE} ",
913
- t("screenshotAttachFile")
914
- ]
915
- }
916
- ),
917
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
918
- "button",
941
+ ] }),
942
+ mode === "bug" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
943
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-expected", children: t("expectedBehaviorLabel") }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
945
+ "textarea",
919
946
  {
920
- type: "button",
921
- onClick: () => setAnnotating(true),
922
- style: {
923
- flex: 1,
924
- display: "flex",
925
- alignItems: "center",
926
- justifyContent: "center",
927
- gap: 6,
928
- padding: "10px 13px",
929
- borderRadius: 10,
930
- border: `1px dashed ${inputBorder}`,
931
- cursor: "pointer",
932
- fontSize: 14,
933
- color: colors.textMuted,
934
- background: colors.backgroundSecondary,
935
- fontFamily: "inherit"
936
- },
937
- children: [
938
- "\u{1F532} ",
939
- t("screenshotMarkOnScreen")
940
- ]
947
+ id: "flint-expected",
948
+ style: { ...inputStyle, resize: "vertical", minHeight: 72 },
949
+ value: expectedBehavior,
950
+ onChange: (e) => setExpectedBehavior(e.target.value),
951
+ placeholder: t("expectedBehaviorPlaceholder")
941
952
  }
942
953
  )
943
954
  ] }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
956
+ "div",
957
+ {
958
+ style: {
959
+ marginBottom: 20,
960
+ display: mode === "text" || mode === "feature" || !enableScreenshot ? "none" : void 0
961
+ },
962
+ children: [
963
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("screenshotLabel") }),
964
+ screenshot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
966
+ "img",
967
+ {
968
+ src: URL.createObjectURL(screenshot),
969
+ alt: "Screenshot preview",
970
+ style: {
971
+ height: 60,
972
+ borderRadius: 8,
973
+ objectFit: "cover",
974
+ border: `1px solid ${inputBorder}`
975
+ }
976
+ }
977
+ ),
978
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
979
+ "span",
980
+ {
981
+ style: {
982
+ fontSize: 13,
983
+ color: colors.textMuted,
984
+ flex: 1,
985
+ overflow: "hidden",
986
+ textOverflow: "ellipsis",
987
+ whiteSpace: "nowrap"
988
+ },
989
+ children: screenshot.name
990
+ }
991
+ ),
992
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
993
+ "button",
994
+ {
995
+ type: "button",
996
+ onClick: () => setScreenshot(null),
997
+ "aria-label": "Remove screenshot",
998
+ style: {
999
+ background: "none",
1000
+ border: "none",
1001
+ cursor: "pointer",
1002
+ fontSize: 18,
1003
+ color: colors.textMuted,
1004
+ lineHeight: 1,
1005
+ padding: "2px 6px",
1006
+ borderRadius: 6,
1007
+ fontFamily: "inherit"
1008
+ },
1009
+ children: "\xD7"
1010
+ }
1011
+ )
1012
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
1013
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1014
+ "button",
1015
+ {
1016
+ type: "button",
1017
+ onClick: () => fileRef.current?.click(),
1018
+ style: {
1019
+ flex: 1,
1020
+ display: "flex",
1021
+ alignItems: "center",
1022
+ justifyContent: "center",
1023
+ gap: 6,
1024
+ padding: "10px 13px",
1025
+ borderRadius: 10,
1026
+ border: `1px dashed ${inputBorder}`,
1027
+ cursor: "pointer",
1028
+ fontSize: 14,
1029
+ color: colors.textMuted,
1030
+ background: colors.backgroundSecondary,
1031
+ fontFamily: "inherit"
1032
+ },
1033
+ children: [
1034
+ "\u{1F4CE} ",
1035
+ t("screenshotAttachFile")
1036
+ ]
1037
+ }
1038
+ ),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1040
+ "button",
1041
+ {
1042
+ type: "button",
1043
+ onClick: () => setAnnotating(true),
1044
+ style: {
1045
+ flex: 1,
1046
+ display: "flex",
1047
+ alignItems: "center",
1048
+ justifyContent: "center",
1049
+ gap: 6,
1050
+ padding: "10px 13px",
1051
+ borderRadius: 10,
1052
+ border: `1px dashed ${inputBorder}`,
1053
+ cursor: "pointer",
1054
+ fontSize: 14,
1055
+ color: colors.textMuted,
1056
+ background: colors.backgroundSecondary,
1057
+ fontFamily: "inherit"
1058
+ },
1059
+ children: [
1060
+ "\u{1F532} ",
1061
+ t("screenshotMarkOnScreen")
1062
+ ]
1063
+ }
1064
+ )
1065
+ ] }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1067
+ "input",
1068
+ {
1069
+ id: "flint-screenshot",
1070
+ ref: fileRef,
1071
+ type: "file",
1072
+ accept: "image/*",
1073
+ style: { display: "none" },
1074
+ onChange: (e) => setScreenshot(e.target.files?.[0] ?? null)
1075
+ }
1076
+ )
1077
+ ]
1078
+ }
1079
+ ),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1081
+ "div",
1082
+ {
1083
+ style: {
1084
+ display: "flex",
1085
+ alignItems: "center",
1086
+ gap: 8,
1087
+ padding: "9px 12px",
1088
+ borderRadius: 10,
1089
+ background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
1090
+ border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
1091
+ marginBottom: 16
1092
+ },
1093
+ children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
1096
+ ]
1097
+ }
1098
+ ),
1099
+ status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1100
+ "div",
1101
+ {
1102
+ style: {
1103
+ padding: "10px 13px",
1104
+ borderRadius: 10,
1105
+ background: "rgba(239,68,68,0.08)",
1106
+ border: "1px solid rgba(239,68,68,0.2)",
1107
+ color: "#f87171",
1108
+ fontSize: 14,
1109
+ marginBottom: 16
1110
+ },
1111
+ children: [
1112
+ "\u26A0\uFE0F ",
1113
+ errorMsg || t("errorLabel")
1114
+ ]
1115
+ }
1116
+ ),
1117
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1118
+ "button",
1119
+ {
1120
+ type: "submit",
1121
+ style: {
1122
+ width: "100%",
1123
+ padding: "13px 20px",
1124
+ borderRadius: 12,
1125
+ border: "none",
1126
+ background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
1127
+ color: colors.buttonText,
1128
+ fontSize: 17,
1129
+ fontWeight: 700,
1130
+ cursor: "pointer",
1131
+ letterSpacing: "-0.01em",
1132
+ boxShadow: accentGlow,
1133
+ fontFamily: "inherit",
1134
+ display: "flex",
1135
+ alignItems: "center",
1136
+ justifyContent: "center",
1137
+ gap: 8
1138
+ },
1139
+ children: [
1140
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.buttonText, size: 15 }),
1141
+ t("submitLabel")
1142
+ ]
1143
+ }
1144
+ ),
944
1145
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
945
- "input",
1146
+ "button",
946
1147
  {
947
- id: "flint-screenshot",
948
- ref: fileRef,
949
- type: "file",
950
- accept: "image/*",
951
- style: { display: "none" },
952
- onChange: (e) => setScreenshot(e.target.files?.[0] ?? null)
1148
+ type: "button",
1149
+ onClick: onClose,
1150
+ style: {
1151
+ width: "100%",
1152
+ padding: "10px",
1153
+ marginTop: 8,
1154
+ background: "none",
1155
+ border: "none",
1156
+ cursor: "pointer",
1157
+ fontSize: 15,
1158
+ color: colors.textMuted,
1159
+ fontFamily: "inherit",
1160
+ borderRadius: 8
1161
+ },
1162
+ children: t("cancel")
953
1163
  }
954
1164
  )
955
- ] }),
956
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
957
- "div",
958
- {
959
- style: {
960
- display: "flex",
961
- alignItems: "center",
962
- gap: 8,
963
- padding: "9px 12px",
964
- borderRadius: 10,
965
- background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
966
- border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
967
- marginBottom: 16
968
- },
969
- children: [
970
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
971
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
972
- ]
973
- }
974
- ),
975
- status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
976
- "div",
977
- {
978
- style: {
979
- padding: "10px 13px",
980
- borderRadius: 10,
981
- background: "rgba(239,68,68,0.08)",
982
- border: "1px solid rgba(239,68,68,0.2)",
983
- color: "#f87171",
984
- fontSize: 14,
985
- marginBottom: 16
986
- },
987
- children: [
988
- "\u26A0\uFE0F ",
989
- errorMsg || t("errorLabel")
990
- ]
991
- }
992
- ),
993
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
994
- "button",
995
- {
996
- type: "submit",
997
- style: {
998
- width: "100%",
999
- padding: "13px 20px",
1000
- borderRadius: 12,
1001
- border: "none",
1002
- background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
1003
- color: colors.buttonText,
1004
- fontSize: 17,
1005
- fontWeight: 700,
1006
- cursor: "pointer",
1007
- letterSpacing: "-0.01em",
1008
- boxShadow: accentGlow,
1009
- fontFamily: "inherit",
1010
- display: "flex",
1011
- alignItems: "center",
1012
- justifyContent: "center",
1013
- gap: 8
1014
- },
1015
- children: [
1016
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.buttonText, size: 15 }),
1017
- t("submitLabel")
1018
- ]
1019
- }
1020
- ),
1021
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1022
- "button",
1023
- {
1024
- type: "button",
1025
- onClick: onClose,
1026
- style: {
1027
- width: "100%",
1028
- padding: "10px",
1029
- marginTop: 8,
1030
- background: "none",
1031
- border: "none",
1032
- cursor: "pointer",
1033
- fontSize: 15,
1034
- color: colors.textMuted,
1035
- fontFamily: "inherit",
1036
- borderRadius: 8
1037
- },
1038
- children: t("cancel")
1039
- }
1040
- )
1165
+ ] })
1041
1166
  ] })
1042
1167
  ] }) })
1043
1168
  ] });
@@ -1151,6 +1276,51 @@ function SendingDots({ color }) {
1151
1276
  i
1152
1277
  )) });
1153
1278
  }
1279
+ function TypeSelectorButton({
1280
+ emoji,
1281
+ title,
1282
+ subtitle,
1283
+ colors,
1284
+ inputBorder,
1285
+ onClick
1286
+ }) {
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1288
+ "button",
1289
+ {
1290
+ type: "button",
1291
+ onClick,
1292
+ style: {
1293
+ display: "flex",
1294
+ alignItems: "center",
1295
+ gap: 14,
1296
+ padding: "14px 16px",
1297
+ borderRadius: 12,
1298
+ border: `1px solid ${inputBorder}`,
1299
+ background: colors.backgroundSecondary,
1300
+ cursor: "pointer",
1301
+ fontFamily: "inherit",
1302
+ textAlign: "left",
1303
+ transition: "border-color 0.15s, box-shadow 0.15s"
1304
+ },
1305
+ onMouseEnter: (e) => {
1306
+ e.currentTarget.style.borderColor = colors.accent;
1307
+ e.currentTarget.style.boxShadow = `0 0 0 1px ${colors.accent}40`;
1308
+ },
1309
+ onMouseLeave: (e) => {
1310
+ e.currentTarget.style.borderColor = inputBorder;
1311
+ e.currentTarget.style.boxShadow = "none";
1312
+ },
1313
+ children: [
1314
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 24, lineHeight: 1, flexShrink: 0 }, children: emoji }),
1315
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 14, fontWeight: 600, color: colors.text, marginBottom: 2 }, children: title }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: colors.textMuted }, children: subtitle })
1318
+ ] }),
1319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { marginLeft: "auto", fontSize: 16, color: colors.textMuted, flexShrink: 0 }, children: "\u2192" })
1320
+ ]
1321
+ }
1322
+ );
1323
+ }
1154
1324
  function SeverityButton({ sev, label, selected, hint, color, accent, border, bg, text, onClick }) {
1155
1325
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1156
1326
  "button",
@@ -1307,6 +1477,7 @@ function WidgetContent({
1307
1477
  statusPageUrl,
1308
1478
  enableScreenshot = true,
1309
1479
  enableTextIssues = true,
1480
+ enableFeatureRequests = false,
1310
1481
  onBeforeSubmit,
1311
1482
  onSuccess,
1312
1483
  onError,
@@ -1477,6 +1648,7 @@ function WidgetContent({
1477
1648
  initialSelection: pendingSelection.current,
1478
1649
  enableScreenshot,
1479
1650
  enableTextIssues,
1651
+ enableFeatureRequests,
1480
1652
  statusPageUrl,
1481
1653
  onBeforeSubmit,
1482
1654
  onSuccess,