feedtack 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,8 +23,9 @@ interface FeedtackProviderProps {
23
23
  classes?: FeedtackClasses;
24
24
  sentimentLabels?: FeedtackSentimentLabels;
25
25
  onError?: (err: Error) => void;
26
+ disabled?: boolean;
26
27
  }
27
- declare function FeedtackProvider({ children, adapter, currentUser, hotkey, adminOnly, theme, classes, sentimentLabels, onError, }: FeedtackProviderProps): react_jsx_runtime.JSX.Element;
28
+ declare function FeedtackProvider({ children, adapter, currentUser, hotkey, adminOnly, theme, classes, sentimentLabels, onError, disabled, }: FeedtackProviderProps): react_jsx_runtime.JSX.Element;
28
29
 
29
30
  interface FeedtackContextValue {
30
31
  activatePinMode: () => void;
@@ -24,16 +24,6 @@ var PIN_PALETTE = [
24
24
  // pink
25
25
  ];
26
26
 
27
- // src/react/context.ts
28
- import { createContext, useContext } from "react";
29
- var FeedtackContext = createContext(null);
30
- function useFeedtackContext() {
31
- const ctx = useContext(FeedtackContext);
32
- if (!ctx)
33
- throw new Error("useFeedtack must be used inside <FeedtackProvider>");
34
- return ctx;
35
- }
36
-
37
27
  // src/react/utils.ts
38
28
  function generateId() {
39
29
  return `ft_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
@@ -55,8 +45,98 @@ function cx(...parts) {
55
45
  return parts.filter(Boolean).join(" ");
56
46
  }
57
47
 
58
- // src/react/ThreadPanel.tsx
48
+ // src/react/CommentForm.tsx
59
49
  import { jsx, jsxs } from "react/jsx-runtime";
50
+ function CommentForm({
51
+ comment,
52
+ commentError,
53
+ sentiment,
54
+ submitting,
55
+ formPos,
56
+ classes,
57
+ sentimentLabels,
58
+ onCommentChange,
59
+ onSentimentChange,
60
+ onSubmit,
61
+ onCancel
62
+ }) {
63
+ return /* @__PURE__ */ jsxs(
64
+ "div",
65
+ {
66
+ className: cx("feedtack-form", classes.form),
67
+ style: { position: "fixed", ...formPos },
68
+ children: [
69
+ /* @__PURE__ */ jsx(
70
+ "textarea",
71
+ {
72
+ className: commentError ? "error" : "",
73
+ placeholder: "What's the issue? (required)",
74
+ value: comment,
75
+ onChange: (e) => onCommentChange(e.target.value),
76
+ ref: (el) => el?.focus()
77
+ }
78
+ ),
79
+ commentError && /* @__PURE__ */ jsx("span", { className: "feedtack-error-msg", children: "Comment is required" }),
80
+ /* @__PURE__ */ jsxs("div", { className: "feedtack-sentiment", children: [
81
+ /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ className: sentiment === "satisfied" ? "selected" : "",
86
+ onClick: () => onSentimentChange(sentiment === "satisfied" ? null : "satisfied"),
87
+ children: sentimentLabels.satisfied ?? "\u{1F60A} Satisfied"
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ className: sentiment === "dissatisfied" ? "selected" : "",
95
+ onClick: () => onSentimentChange(
96
+ sentiment === "dissatisfied" ? null : "dissatisfied"
97
+ ),
98
+ children: sentimentLabels.dissatisfied ?? "\u{1F61E} Dissatisfied"
99
+ }
100
+ )
101
+ ] }),
102
+ /* @__PURE__ */ jsxs("div", { className: "feedtack-form-actions", children: [
103
+ /* @__PURE__ */ jsx(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ className: "feedtack-btn-cancel",
108
+ onClick: onCancel,
109
+ children: "Cancel"
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ "button",
114
+ {
115
+ type: "button",
116
+ className: "feedtack-btn-submit",
117
+ onClick: onSubmit,
118
+ disabled: submitting,
119
+ children: submitting ? "Sending\u2026" : "Submit"
120
+ }
121
+ )
122
+ ] })
123
+ ]
124
+ }
125
+ );
126
+ }
127
+
128
+ // src/react/context.ts
129
+ import { createContext, useContext } from "react";
130
+ var FeedtackContext = createContext(null);
131
+ function useFeedtackContext() {
132
+ const ctx = useContext(FeedtackContext);
133
+ if (!ctx)
134
+ throw new Error("useFeedtack must be used inside <FeedtackProvider>");
135
+ return ctx;
136
+ }
137
+
138
+ // src/react/ThreadPanel.tsx
139
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
60
140
  function ThreadPanel({
61
141
  item,
62
142
  replyBody,
@@ -69,26 +149,26 @@ function ThreadPanel({
69
149
  }) {
70
150
  const pin = item.payload.pins[0];
71
151
  const pos = getAnchoredPosition(pin.x, pin.y);
72
- return /* @__PURE__ */ jsxs(
152
+ return /* @__PURE__ */ jsxs2(
73
153
  "div",
74
154
  {
75
155
  className: cx("feedtack-thread", className),
76
156
  style: { position: "fixed", ...pos },
77
157
  children: [
78
- /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: item.payload.submittedBy.name }),
79
- /* @__PURE__ */ jsx("p", { style: { fontSize: 13 }, children: item.payload.comment }),
80
- item.replies.map((r) => /* @__PURE__ */ jsxs(
158
+ /* @__PURE__ */ jsx2("strong", { style: { fontSize: 13 }, children: item.payload.submittedBy.name }),
159
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 13 }, children: item.payload.comment }),
160
+ item.replies.map((r) => /* @__PURE__ */ jsxs2(
81
161
  "div",
82
162
  {
83
163
  style: { borderTop: "1px solid #f3f4f6", paddingTop: 8 },
84
164
  children: [
85
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, fontWeight: 600 }, children: r.author.name }),
86
- /* @__PURE__ */ jsx("p", { style: { fontSize: 12 }, children: r.body })
165
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, fontWeight: 600 }, children: r.author.name }),
166
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 12 }, children: r.body })
87
167
  ]
88
168
  },
89
169
  r.id
90
170
  )),
91
- /* @__PURE__ */ jsx(
171
+ /* @__PURE__ */ jsx2(
92
172
  "textarea",
93
173
  {
94
174
  placeholder: "Reply\u2026",
@@ -104,8 +184,8 @@ function ThreadPanel({
104
184
  }
105
185
  }
106
186
  ),
107
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
108
- /* @__PURE__ */ jsx(
187
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
188
+ /* @__PURE__ */ jsx2(
109
189
  "button",
110
190
  {
111
191
  type: "button",
@@ -115,7 +195,7 @@ function ThreadPanel({
115
195
  children: "Reply"
116
196
  }
117
197
  ),
118
- /* @__PURE__ */ jsx(
198
+ /* @__PURE__ */ jsx2(
119
199
  "button",
120
200
  {
121
201
  type: "button",
@@ -125,7 +205,7 @@ function ThreadPanel({
125
205
  children: "Mark Resolved"
126
206
  }
127
207
  ),
128
- /* @__PURE__ */ jsx(
208
+ /* @__PURE__ */ jsx2(
129
209
  "button",
130
210
  {
131
211
  type: "button",
@@ -135,7 +215,7 @@ function ThreadPanel({
135
215
  children: "Archive"
136
216
  }
137
217
  ),
138
- /* @__PURE__ */ jsx(
218
+ /* @__PURE__ */ jsx2(
139
219
  "button",
140
220
  {
141
221
  type: "button",
@@ -385,9 +465,10 @@ var FEEDTACK_STYLES = `
385
465
  `;
386
466
 
387
467
  // src/react/useFeedtackDom.ts
388
- function useFeedtackDom(theme) {
468
+ function useFeedtackDom(theme, disabled) {
389
469
  const rootRef = useRef(null);
390
470
  useEffect(() => {
471
+ if (disabled) return;
391
472
  if (document.getElementById("feedtack-styles")) return;
392
473
  const style = document.createElement("style");
393
474
  style.id = "feedtack-styles";
@@ -396,8 +477,9 @@ function useFeedtackDom(theme) {
396
477
  return () => {
397
478
  style.remove();
398
479
  };
399
- }, []);
480
+ }, [disabled]);
400
481
  useEffect(() => {
482
+ if (disabled) return;
401
483
  const root = document.createElement("div");
402
484
  root.id = "feedtack-root";
403
485
  document.body.appendChild(root);
@@ -405,21 +487,22 @@ function useFeedtackDom(theme) {
405
487
  return () => {
406
488
  root.remove();
407
489
  };
408
- }, []);
490
+ }, [disabled]);
409
491
  useEffect(() => {
492
+ if (disabled) return;
410
493
  const root = document.getElementById("feedtack-root");
411
494
  if (!root || !theme) return;
412
495
  const tokens = themeToCSS(theme);
413
496
  for (const [k, v] of Object.entries(tokens)) {
414
497
  root.style.setProperty(k, v);
415
498
  }
416
- }, [theme]);
499
+ }, [theme, disabled]);
417
500
  return rootRef;
418
501
  }
419
502
 
420
503
  // src/react/usePinMode.ts
421
504
  import { useCallback, useEffect as useEffect2, useState } from "react";
422
- function usePinMode({ hotkey, onDeactivate }) {
505
+ function usePinMode({ hotkey, onDeactivate, disabled }) {
423
506
  const [isActive, setIsActive] = useState(false);
424
507
  const [pendingPins, setPendingPins] = useState([]);
425
508
  const [selectedColor, setSelectedColor] = useState(PIN_PALETTE[0]);
@@ -440,6 +523,7 @@ function usePinMode({ hotkey, onDeactivate }) {
440
523
  return () => document.documentElement.classList.remove("feedtack-crosshair");
441
524
  }, [isActive]);
442
525
  useEffect2(() => {
526
+ if (disabled) return;
443
527
  const handler = (e) => {
444
528
  if (e.key === hotkey.toUpperCase() && e.shiftKey) {
445
529
  setIsActive((prev) => !prev);
@@ -458,7 +542,7 @@ function usePinMode({ hotkey, onDeactivate }) {
458
542
  };
459
543
  window.addEventListener("keydown", handler);
460
544
  return () => window.removeEventListener("keydown", handler);
461
- }, [hotkey, deactivate, isActive]);
545
+ }, [hotkey, deactivate, isActive, disabled]);
462
546
  const handlePageClick = useCallback(
463
547
  (e) => {
464
548
  if (!isActive) return;
@@ -480,9 +564,10 @@ function usePinMode({ hotkey, onDeactivate }) {
480
564
  [isActive, selectedColor]
481
565
  );
482
566
  useEffect2(() => {
567
+ if (disabled) return;
483
568
  document.addEventListener("click", handlePageClick, true);
484
569
  return () => document.removeEventListener("click", handlePageClick, true);
485
- }, [handlePageClick]);
570
+ }, [handlePageClick, disabled]);
486
571
  return {
487
572
  isActive,
488
573
  activate,
@@ -500,9 +585,10 @@ function useFeedtackState({
500
585
  currentUser,
501
586
  hotkey,
502
587
  theme,
503
- onError
588
+ onError,
589
+ disabled
504
590
  }) {
505
- useFeedtackDom(theme);
591
+ useFeedtackDom(theme, disabled);
506
592
  const [pathname, setPathname] = useState2(() => window.location.pathname);
507
593
  useEffect3(() => {
508
594
  const update = () => setPathname(window.location.pathname);
@@ -538,6 +624,7 @@ function useFeedtackState({
538
624
  }, []);
539
625
  const pinMode = usePinMode({
540
626
  hotkey,
627
+ disabled,
541
628
  onDeactivate: () => {
542
629
  resetForm();
543
630
  setOpenThreadId(null);
@@ -672,7 +759,7 @@ function useFeedtackState({
672
759
  }
673
760
 
674
761
  // src/react/FeedtackProvider.tsx
675
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
762
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
676
763
  function FeedtackProvider({
677
764
  children,
678
765
  adapter,
@@ -682,30 +769,34 @@ function FeedtackProvider({
682
769
  theme,
683
770
  classes = {},
684
771
  sentimentLabels = {},
685
- onError
772
+ onError,
773
+ disabled = false
686
774
  }) {
687
775
  const state = useFeedtackState({
688
776
  adapter,
689
777
  currentUser,
690
778
  hotkey,
691
779
  theme,
692
- onError
780
+ onError,
781
+ disabled
693
782
  });
694
783
  const firstPin = state.pendingPins[0];
695
784
  const formPos = firstPin ? getAnchoredPosition(firstPin.x, firstPin.y) : {};
696
785
  const showButton = !adminOnly || currentUser.role === "admin";
697
786
  const openItem = state.openThreadId ? state.feedbackItems.find((i) => i.payload.id === state.openThreadId) : null;
698
- return /* @__PURE__ */ jsxs2(
787
+ return /* @__PURE__ */ jsxs3(
699
788
  FeedtackContext.Provider,
700
789
  {
701
790
  value: {
702
- activatePinMode: state.activatePinMode,
703
- deactivatePinMode: state.deactivatePinMode,
704
- isPinModeActive: state.isPinModeActive
791
+ activatePinMode: disabled ? () => {
792
+ } : state.activatePinMode,
793
+ deactivatePinMode: disabled ? () => {
794
+ } : state.deactivatePinMode,
795
+ isPinModeActive: disabled ? false : state.isPinModeActive
705
796
  },
706
797
  children: [
707
798
  children,
708
- showButton && /* @__PURE__ */ jsxs2(
799
+ !disabled && showButton && /* @__PURE__ */ jsxs3(
709
800
  "button",
710
801
  {
711
802
  type: "button",
@@ -723,7 +814,7 @@ function FeedtackProvider({
723
814
  ]
724
815
  }
725
816
  ),
726
- state.isPinModeActive && /* @__PURE__ */ jsx2("div", { className: cx("feedtack-color-picker", classes.colorPicker), children: PIN_PALETTE.map((color) => /* @__PURE__ */ jsx2(
817
+ state.isPinModeActive && /* @__PURE__ */ jsx3("div", { className: cx("feedtack-color-picker", classes.colorPicker), children: PIN_PALETTE.map((color) => /* @__PURE__ */ jsx3(
727
818
  "button",
728
819
  {
729
820
  type: "button",
@@ -737,7 +828,7 @@ function FeedtackProvider({
737
828
  },
738
829
  color
739
830
  )) }),
740
- state.pendingPins.map((pin) => /* @__PURE__ */ jsx2(
831
+ state.pendingPins.map((pin) => /* @__PURE__ */ jsx3(
741
832
  "div",
742
833
  {
743
834
  className: cx("feedtack-pin-marker", classes.pinMarker),
@@ -750,77 +841,28 @@ function FeedtackProvider({
750
841
  },
751
842
  `${pin.x}-${pin.y}-${pin.color}`
752
843
  )),
753
- state.showForm && /* @__PURE__ */ jsxs2(
754
- "div",
844
+ state.showForm && /* @__PURE__ */ jsx3(
845
+ CommentForm,
755
846
  {
756
- className: cx("feedtack-form", classes.form),
757
- style: { position: "fixed", ...formPos },
758
- children: [
759
- /* @__PURE__ */ jsx2(
760
- "textarea",
761
- {
762
- className: state.commentError ? "error" : "",
763
- placeholder: "What's the issue? (required)",
764
- value: state.comment,
765
- onChange: (e) => {
766
- state.setComment(e.target.value);
767
- state.setCommentError(false);
768
- },
769
- ref: (el) => el?.focus()
770
- }
771
- ),
772
- state.commentError && /* @__PURE__ */ jsx2("span", { className: "feedtack-error-msg", children: "Comment is required" }),
773
- /* @__PURE__ */ jsxs2("div", { className: "feedtack-sentiment", children: [
774
- /* @__PURE__ */ jsx2(
775
- "button",
776
- {
777
- type: "button",
778
- className: state.sentiment === "satisfied" ? "selected" : "",
779
- onClick: () => state.setSentiment(
780
- state.sentiment === "satisfied" ? null : "satisfied"
781
- ),
782
- children: sentimentLabels.satisfied ?? "\u{1F60A} Satisfied"
783
- }
784
- ),
785
- /* @__PURE__ */ jsx2(
786
- "button",
787
- {
788
- type: "button",
789
- className: state.sentiment === "dissatisfied" ? "selected" : "",
790
- onClick: () => state.setSentiment(
791
- state.sentiment === "dissatisfied" ? null : "dissatisfied"
792
- ),
793
- children: sentimentLabels.dissatisfied ?? "\u{1F61E} Dissatisfied"
794
- }
795
- )
796
- ] }),
797
- /* @__PURE__ */ jsxs2("div", { className: "feedtack-form-actions", children: [
798
- /* @__PURE__ */ jsx2(
799
- "button",
800
- {
801
- type: "button",
802
- className: "feedtack-btn-cancel",
803
- onClick: state.deactivatePinMode,
804
- children: "Cancel"
805
- }
806
- ),
807
- /* @__PURE__ */ jsx2(
808
- "button",
809
- {
810
- type: "button",
811
- className: "feedtack-btn-submit",
812
- onClick: state.handleSubmit,
813
- disabled: state.submitting,
814
- children: state.submitting ? "Sending\u2026" : "Submit"
815
- }
816
- )
817
- ] })
818
- ]
847
+ comment: state.comment,
848
+ commentError: state.commentError,
849
+ sentiment: state.sentiment,
850
+ submitting: state.submitting,
851
+ formPos,
852
+ classes,
853
+ sentimentLabels,
854
+ onCommentChange: (v) => {
855
+ state.setComment(v);
856
+ state.setCommentError(false);
857
+ },
858
+ onSentimentChange: state.setSentiment,
859
+ onSubmit: state.handleSubmit,
860
+ onCancel: state.deactivatePinMode
819
861
  }
820
862
  ),
821
863
  !state.loading && state.feedbackItems.filter((item) => item.payload.page.pathname === state.pathname).filter((item) => !state.isArchivedForUser(item)).map((item) => {
822
864
  const pin = item.payload.pins[0];
823
- return /* @__PURE__ */ jsx2(
865
+ return /* @__PURE__ */ jsx3(
824
866
  "button",
825
867
  {
826
868
  type: "button",
@@ -835,12 +877,12 @@ function FeedtackProvider({
835
877
  onClick: () => state.setOpenThreadId(
836
878
  state.openThreadId === item.payload.id ? null : item.payload.id
837
879
  ),
838
- children: state.hasUnread(item) && /* @__PURE__ */ jsx2("div", { className: "feedtack-pin-badge" })
880
+ children: state.hasUnread(item) && /* @__PURE__ */ jsx3("div", { className: "feedtack-pin-badge" })
839
881
  },
840
882
  item.payload.id
841
883
  );
842
884
  }),
843
- openItem && /* @__PURE__ */ jsx2(
885
+ openItem && /* @__PURE__ */ jsx3(
844
886
  ThreadPanel,
845
887
  {
846
888
  item: openItem,
@@ -853,7 +895,7 @@ function FeedtackProvider({
853
895
  className: classes.thread
854
896
  }
855
897
  ),
856
- state.loading && /* @__PURE__ */ jsx2("div", { className: "feedtack-loading", children: "Loading feedback\u2026" })
898
+ state.loading && /* @__PURE__ */ jsx3("div", { className: "feedtack-loading", children: "Loading feedback\u2026" })
857
899
  ]
858
900
  }
859
901
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feedtack",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Click anywhere. Drop a pin. Get a payload a developer can act on.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "test:watch": "vitest",
31
31
  "lint": "biome check src/",
32
32
  "lint:fix": "biome check --fix src/",
33
- "release": "release-it",
33
+ "release": "release-it --ci",
34
34
  "prepublishOnly": "pnpm test && pnpm build",
35
35
  "prepare": "husky"
36
36
  },