feedtack 0.0.1 → 0.0.3

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.
@@ -1,6 +1,20 @@
1
1
  // src/types/payload.ts
2
2
  var SCHEMA_VERSION = "1.0.0";
3
3
 
4
+ // src/types/theme.ts
5
+ function themeToCSS(theme) {
6
+ const map = {};
7
+ if (theme.primary) map["--ft-primary"] = theme.primary;
8
+ if (theme.background) map["--ft-bg"] = theme.background;
9
+ if (theme.surface) map["--ft-surface"] = theme.surface;
10
+ if (theme.text) map["--ft-text"] = theme.text;
11
+ if (theme.textMuted) map["--ft-text-muted"] = theme.textMuted;
12
+ if (theme.border) map["--ft-border"] = theme.border;
13
+ if (theme.radius) map["--ft-radius"] = theme.radius;
14
+ if (theme.badge) map["--ft-badge"] = theme.badge;
15
+ return map;
16
+ }
17
+
4
18
  // src/capture/target.ts
5
19
  function getCSSSelector(element) {
6
20
  const parts = [];
@@ -96,6 +110,7 @@ function getPinCoords(event) {
96
110
 
97
111
  export {
98
112
  SCHEMA_VERSION,
113
+ themeToCSS,
99
114
  getCSSSelector,
100
115
  getTargetMeta,
101
116
  getViewportMeta,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FeedtackAdapter, a as FeedtackPayload, b as FeedtackReply, c as FeedtackResolution, d as FeedtackFilter, e as FeedbackItem, f as FeedtackPinTarget, g as FeedtackDeviceMeta, h as FeedtackPageMeta, i as FeedtackViewportMeta } from './adapter-DvQFXmyi.js';
2
- export { j as FeedtackArchive, k as FeedtackBoundingRect, l as FeedtackPin, m as FeedtackSentiment, n as FeedtackUser, S as SCHEMA_VERSION } from './adapter-DvQFXmyi.js';
1
+ import { F as FeedtackAdapter, a as FeedtackPayload, b as FeedtackReply, c as FeedtackResolution, d as FeedtackFilter, e as FeedbackItem, f as FeedtackPinTarget, g as FeedtackDeviceMeta, h as FeedtackPageMeta, i as FeedtackViewportMeta } from './theme-CHGvGcG5.js';
2
+ export { j as FeedtackArchive, k as FeedtackBoundingRect, l as FeedtackPin, m as FeedtackSentiment, n as FeedtackTheme, o as FeedtackUser, S as SCHEMA_VERSION, t as themeToCSS } from './theme-CHGvGcG5.js';
3
3
 
4
4
  /** Development adapter — logs all operations to the browser console */
5
5
  declare class ConsoleAdapter implements FeedtackAdapter {
package/dist/index.js CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  getPageMeta,
6
6
  getPinCoords,
7
7
  getTargetMeta,
8
- getViewportMeta
9
- } from "./chunk-VSVTP7O5.js";
8
+ getViewportMeta,
9
+ themeToCSS
10
+ } from "./chunk-NOFOQJHM.js";
10
11
 
11
12
  // src/adapters/ConsoleAdapter.ts
12
13
  var ConsoleAdapter = class {
@@ -76,5 +77,6 @@ export {
76
77
  getPageMeta,
77
78
  getPinCoords,
78
79
  getTargetMeta,
79
- getViewportMeta
80
+ getViewportMeta,
81
+ themeToCSS
80
82
  };
@@ -1,7 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { F as FeedtackAdapter, n as FeedtackUser } from '../adapter-DvQFXmyi.js';
3
+ import { F as FeedtackAdapter, o as FeedtackUser, n as FeedtackTheme } from '../theme-CHGvGcG5.js';
4
4
 
5
+ interface FeedtackClasses {
6
+ /** Class added to the activation button */
7
+ button?: string;
8
+ /** Class added to the comment form panel */
9
+ form?: string;
10
+ /** Class added to the thread/reply panel */
11
+ thread?: string;
12
+ /** Class added to the color picker row */
13
+ colorPicker?: string;
14
+ /** Class added to each pin marker */
15
+ pinMarker?: string;
16
+ }
17
+ interface FeedtackSentimentLabels {
18
+ satisfied?: React.ReactNode;
19
+ dissatisfied?: React.ReactNode;
20
+ }
5
21
  interface FeedtackProviderProps {
6
22
  children: React.ReactNode;
7
23
  adapter: FeedtackAdapter;
@@ -10,9 +26,15 @@ interface FeedtackProviderProps {
10
26
  hotkey?: string;
11
27
  /** Only show the activation button for users whose role is in this list */
12
28
  adminOnly?: boolean;
29
+ /** CSS token overrides for brand alignment */
30
+ theme?: FeedtackTheme;
31
+ /** Additional class names for individual feedtack elements */
32
+ classes?: FeedtackClasses;
33
+ /** Custom labels/content for sentiment toggle buttons */
34
+ sentimentLabels?: FeedtackSentimentLabels;
13
35
  onError?: (err: Error) => void;
14
36
  }
15
- declare function FeedtackProvider({ children, adapter, currentUser, hotkey, adminOnly, onError }: FeedtackProviderProps): react_jsx_runtime.JSX.Element;
37
+ declare function FeedtackProvider({ children, adapter, currentUser, hotkey, adminOnly, theme, classes, sentimentLabels, onError }: FeedtackProviderProps): react_jsx_runtime.JSX.Element;
16
38
 
17
39
  interface FeedtackContextValue {
18
40
  activatePinMode: () => void;
@@ -23,4 +45,4 @@ interface FeedtackContextValue {
23
45
  /** Hook for host app to programmatically control feedtack */
24
46
  declare function useFeedtack(): FeedtackContextValue;
25
47
 
26
- export { FeedtackProvider, useFeedtack };
48
+ export { type FeedtackClasses, FeedtackProvider, type FeedtackProviderProps, type FeedtackSentimentLabels, useFeedtack };
@@ -4,8 +4,9 @@ import {
4
4
  getPageMeta,
5
5
  getPinCoords,
6
6
  getTargetMeta,
7
- getViewportMeta
8
- } from "../chunk-VSVTP7O5.js";
7
+ getViewportMeta,
8
+ themeToCSS
9
+ } from "../chunk-NOFOQJHM.js";
9
10
 
10
11
  // src/react/FeedtackProvider.tsx
11
12
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -20,6 +21,20 @@ function useFeedtackContext() {
20
21
  }
21
22
 
22
23
  // src/ui/styles.ts
24
+ var FEEDTACK_DEFAULT_TOKENS = `
25
+ #feedtack-root {
26
+ --ft-primary: #2563eb;
27
+ --ft-primary-hover: #1d4ed8;
28
+ --ft-bg: #ffffff;
29
+ --ft-surface: #f9fafb;
30
+ --ft-text: #111827;
31
+ --ft-text-muted: #6b7280;
32
+ --ft-border: #e5e7eb;
33
+ --ft-radius: 8px;
34
+ --ft-error: #ef4444;
35
+ --ft-badge: #f59e0b;
36
+ }
37
+ `;
23
38
  var FEEDTACK_STYLES = `
24
39
  #feedtack-root * {
25
40
  box-sizing: border-box;
@@ -34,10 +49,10 @@ var FEEDTACK_STYLES = `
34
49
  bottom: 24px;
35
50
  right: 24px;
36
51
  z-index: 2147483640;
37
- background: #1a1a1a;
38
- color: #fff;
52
+ background: var(--ft-text);
53
+ color: var(--ft-bg);
39
54
  border: none;
40
- border-radius: 8px;
55
+ border-radius: var(--ft-radius);
41
56
  padding: 8px 14px;
42
57
  font-size: 13px;
43
58
  font-weight: 500;
@@ -50,11 +65,11 @@ var FEEDTACK_STYLES = `
50
65
  }
51
66
 
52
67
  .feedtack-btn:hover {
53
- background: #333;
68
+ opacity: 0.85;
54
69
  }
55
70
 
56
71
  .feedtack-btn.active {
57
- background: #2563eb;
72
+ background: var(--ft-primary);
58
73
  }
59
74
 
60
75
  .feedtack-crosshair * {
@@ -80,17 +95,17 @@ var FEEDTACK_STYLES = `
80
95
  right: -4px;
81
96
  width: 10px;
82
97
  height: 10px;
83
- background: #f59e0b;
98
+ background: var(--ft-badge);
84
99
  border-radius: 50%;
85
- border: 1.5px solid #fff;
100
+ border: 1.5px solid var(--ft-bg);
86
101
  }
87
102
 
88
103
  .feedtack-color-picker {
89
104
  display: flex;
90
105
  gap: 6px;
91
106
  padding: 8px;
92
- background: #fff;
93
- border-radius: 8px;
107
+ background: var(--ft-bg) !important;
108
+ border-radius: var(--ft-radius);
94
109
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
95
110
  position: fixed;
96
111
  bottom: 72px;
@@ -108,15 +123,15 @@ var FEEDTACK_STYLES = `
108
123
  }
109
124
 
110
125
  .feedtack-color-swatch.selected {
111
- border-color: #1a1a1a;
126
+ border-color: var(--ft-text);
112
127
  transform: scale(1.15);
113
128
  }
114
129
 
115
130
  .feedtack-form {
116
131
  position: absolute;
117
132
  z-index: 2147483642;
118
- background: #fff;
119
- border-radius: 10px;
133
+ background: var(--ft-bg) !important;
134
+ border-radius: calc(var(--ft-radius) + 2px);
120
135
  box-shadow: 0 4px 20px rgba(0,0,0,0.18);
121
136
  padding: 16px;
122
137
  width: 280px;
@@ -127,26 +142,28 @@ var FEEDTACK_STYLES = `
127
142
 
128
143
  .feedtack-form textarea {
129
144
  width: 100%;
130
- border: 1.5px solid #e5e7eb;
131
- border-radius: 6px;
145
+ border: 1.5px solid var(--ft-border);
146
+ border-radius: var(--ft-radius);
132
147
  padding: 8px;
133
148
  font-size: 13px;
134
149
  resize: vertical;
135
150
  min-height: 80px;
136
151
  outline: none;
152
+ background: var(--ft-surface);
153
+ color: var(--ft-text);
137
154
  }
138
155
 
139
156
  .feedtack-form textarea:focus {
140
- border-color: #2563eb;
157
+ border-color: var(--ft-primary);
141
158
  }
142
159
 
143
160
  .feedtack-form textarea.error {
144
- border-color: #ef4444;
161
+ border-color: var(--ft-error);
145
162
  }
146
163
 
147
164
  .feedtack-error-msg {
148
165
  font-size: 12px;
149
- color: #ef4444;
166
+ color: var(--ft-error);
150
167
  }
151
168
 
152
169
  .feedtack-sentiment {
@@ -157,18 +174,19 @@ var FEEDTACK_STYLES = `
157
174
  .feedtack-sentiment button {
158
175
  flex: 1;
159
176
  padding: 6px 10px;
160
- border: 1.5px solid #e5e7eb;
161
- border-radius: 6px;
162
- background: #fff;
177
+ border: 1.5px solid var(--ft-border);
178
+ border-radius: var(--ft-radius);
179
+ background: var(--ft-bg);
180
+ color: var(--ft-text);
163
181
  font-size: 12px;
164
182
  cursor: pointer;
165
183
  transition: all 0.1s;
166
184
  }
167
185
 
168
186
  .feedtack-sentiment button.selected {
169
- border-color: #2563eb;
170
- background: #eff6ff;
171
- color: #2563eb;
187
+ border-color: var(--ft-primary);
188
+ background: var(--ft-surface);
189
+ color: var(--ft-primary);
172
190
  }
173
191
 
174
192
  .feedtack-form-actions {
@@ -179,9 +197,10 @@ var FEEDTACK_STYLES = `
179
197
 
180
198
  .feedtack-btn-cancel {
181
199
  padding: 6px 12px;
182
- border: 1.5px solid #e5e7eb;
183
- border-radius: 6px;
184
- background: #fff;
200
+ border: 1.5px solid var(--ft-border);
201
+ border-radius: var(--ft-radius);
202
+ background: var(--ft-bg);
203
+ color: var(--ft-text);
185
204
  font-size: 13px;
186
205
  cursor: pointer;
187
206
  }
@@ -189,8 +208,8 @@ var FEEDTACK_STYLES = `
189
208
  .feedtack-btn-submit {
190
209
  padding: 6px 12px;
191
210
  border: none;
192
- border-radius: 6px;
193
- background: #2563eb;
211
+ border-radius: var(--ft-radius);
212
+ background: var(--ft-primary);
194
213
  color: #fff;
195
214
  font-size: 13px;
196
215
  font-weight: 500;
@@ -205,8 +224,8 @@ var FEEDTACK_STYLES = `
205
224
  .feedtack-thread {
206
225
  position: absolute;
207
226
  z-index: 2147483642;
208
- background: #fff;
209
- border-radius: 10px;
227
+ background: var(--ft-bg) !important;
228
+ border-radius: calc(var(--ft-radius) + 2px);
210
229
  box-shadow: 0 4px 20px rgba(0,0,0,0.18);
211
230
  padding: 16px;
212
231
  width: 300px;
@@ -222,7 +241,7 @@ var FEEDTACK_STYLES = `
222
241
  bottom: 70px;
223
242
  right: 24px;
224
243
  font-size: 12px;
225
- color: #6b7280;
244
+ color: var(--ft-text-muted);
226
245
  z-index: 2147483640;
227
246
  }
228
247
  `;
@@ -262,7 +281,7 @@ function getAnchoredPosition(x, y) {
262
281
  const bottom = clientY > vh - EDGE ? vh - clientY + FORM_HEIGHT : void 0;
263
282
  return { left, right, top, bottom };
264
283
  }
265
- function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminOnly = false, onError }) {
284
+ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminOnly = false, theme, classes = {}, sentimentLabels = {}, onError }) {
266
285
  const [isPinModeActive, setIsPinModeActive] = useState(false);
267
286
  const [pendingPins, setPendingPins] = useState([]);
268
287
  const [selectedColor, setSelectedColor] = useState(PIN_PALETTE[0]);
@@ -280,7 +299,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
280
299
  if (document.getElementById("feedtack-styles")) return;
281
300
  const style = document.createElement("style");
282
301
  style.id = "feedtack-styles";
283
- style.textContent = FEEDTACK_STYLES;
302
+ style.textContent = FEEDTACK_DEFAULT_TOKENS + FEEDTACK_STYLES;
284
303
  document.head.appendChild(style);
285
304
  return () => {
286
305
  style.remove();
@@ -295,6 +314,12 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
295
314
  root.remove();
296
315
  };
297
316
  }, []);
317
+ useEffect(() => {
318
+ const root = document.getElementById("feedtack-root");
319
+ if (!root || !theme) return;
320
+ const tokens = themeToCSS(theme);
321
+ Object.entries(tokens).forEach(([k, v]) => root.style.setProperty(k, v));
322
+ }, [theme]);
298
323
  useEffect(() => {
299
324
  setLoading(true);
300
325
  adapter.loadFeedback({ pathname: window.location.pathname }).then(setFeedbackItems).catch((err) => onError?.(err)).finally(() => setLoading(false));
@@ -423,7 +448,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
423
448
  showButton && /* @__PURE__ */ jsxs(
424
449
  "button",
425
450
  {
426
- className: `feedtack-btn${isPinModeActive ? " active" : ""}`,
451
+ className: `feedtack-btn${isPinModeActive ? " active" : ""}${classes.button ? ` ${classes.button}` : ""}`,
427
452
  onClick: () => isPinModeActive ? deactivatePinMode() : activatePinMode(),
428
453
  title: "Toggle feedback pin mode",
429
454
  children: [
@@ -433,7 +458,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
433
458
  ]
434
459
  }
435
460
  ),
436
- isPinModeActive && /* @__PURE__ */ jsx("div", { className: "feedtack-color-picker", children: PIN_PALETTE.map((color) => /* @__PURE__ */ jsx(
461
+ isPinModeActive && /* @__PURE__ */ jsx("div", { className: `feedtack-color-picker${classes.colorPicker ? ` ${classes.colorPicker}` : ""}`, children: PIN_PALETTE.map((color) => /* @__PURE__ */ jsx(
437
462
  "button",
438
463
  {
439
464
  className: `feedtack-color-swatch${selectedColor === color ? " selected" : ""}`,
@@ -446,7 +471,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
446
471
  pendingPins.map((pin, i) => /* @__PURE__ */ jsx(
447
472
  "div",
448
473
  {
449
- className: "feedtack-pin-marker",
474
+ className: `feedtack-pin-marker${classes.pinMarker ? ` ${classes.pinMarker}` : ""}`,
450
475
  style: {
451
476
  background: pin.color,
452
477
  left: pin.x,
@@ -456,7 +481,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
456
481
  },
457
482
  i
458
483
  )),
459
- showForm && /* @__PURE__ */ jsxs("div", { className: "feedtack-form", style: { position: "fixed", ...formPos }, children: [
484
+ showForm && /* @__PURE__ */ jsxs("div", { className: `feedtack-form${classes.form ? ` ${classes.form}` : ""}`, style: { position: "fixed", ...formPos }, children: [
460
485
  /* @__PURE__ */ jsx(
461
486
  "textarea",
462
487
  {
@@ -477,7 +502,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
477
502
  {
478
503
  className: sentiment === "satisfied" ? "selected" : "",
479
504
  onClick: () => setSentiment(sentiment === "satisfied" ? null : "satisfied"),
480
- children: "\u{1F60A} Satisfied"
505
+ children: sentimentLabels.satisfied ?? "\u{1F60A} Satisfied"
481
506
  }
482
507
  ),
483
508
  /* @__PURE__ */ jsx(
@@ -485,7 +510,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
485
510
  {
486
511
  className: sentiment === "dissatisfied" ? "selected" : "",
487
512
  onClick: () => setSentiment(sentiment === "dissatisfied" ? null : "dissatisfied"),
488
- children: "\u{1F61E} Dissatisfied"
513
+ children: sentimentLabels.dissatisfied ?? "\u{1F61E} Dissatisfied"
489
514
  }
490
515
  )
491
516
  ] }),
@@ -500,7 +525,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
500
525
  return /* @__PURE__ */ jsx(
501
526
  "div",
502
527
  {
503
- className: "feedtack-pin-marker",
528
+ className: `feedtack-pin-marker${classes.pinMarker ? ` ${classes.pinMarker}` : ""}`,
504
529
  style: {
505
530
  background: firstItemPin.color,
506
531
  left: firstItemPin.x,
@@ -519,7 +544,7 @@ function FeedtackProvider({ children, adapter, currentUser, hotkey = "p", adminO
519
544
  if (!item) return null;
520
545
  const pin = item.payload.pins[0];
521
546
  const pos = getAnchoredPosition(pin.x, pin.y);
522
- return /* @__PURE__ */ jsxs("div", { className: "feedtack-thread", style: { position: "fixed", ...pos }, children: [
547
+ return /* @__PURE__ */ jsxs("div", { className: `feedtack-thread${classes.thread ? ` ${classes.thread}` : ""}`, style: { position: "fixed", ...pos }, children: [
523
548
  /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: item.payload.submittedBy.name }),
524
549
  /* @__PURE__ */ jsx("p", { style: { fontSize: 13 }, children: item.payload.comment }),
525
550
  item.replies.map((r) => /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid #f3f4f6", paddingTop: 8 }, children: [
@@ -122,4 +122,25 @@ interface FeedtackAdapter {
122
122
  loadFeedback(filter?: FeedtackFilter): Promise<FeedbackItem[]>;
123
123
  }
124
124
 
125
- export { type FeedtackAdapter as F, SCHEMA_VERSION as S, type FeedtackPayload as a, type FeedtackReply as b, type FeedtackResolution as c, type FeedtackFilter as d, type FeedbackItem as e, type FeedtackPinTarget as f, type FeedtackDeviceMeta as g, type FeedtackPageMeta as h, type FeedtackViewportMeta as i, type FeedtackArchive as j, type FeedtackBoundingRect as k, type FeedtackPin as l, type FeedtackSentiment as m, type FeedtackUser as n };
125
+ interface FeedtackTheme {
126
+ /** Accent color — pin button active state, focus rings, selected states */
127
+ primary?: string;
128
+ /** Panel and picker background */
129
+ background?: string;
130
+ /** Input and card surface background */
131
+ surface?: string;
132
+ /** Primary text color */
133
+ text?: string;
134
+ /** Secondary/placeholder text color */
135
+ textMuted?: string;
136
+ /** Panel and input border color */
137
+ border?: string;
138
+ /** Border radius applied to panels and inputs */
139
+ radius?: string;
140
+ /** Notification badge color */
141
+ badge?: string;
142
+ }
143
+ /** Maps FeedtackTheme fields to CSS custom properties on #feedtack-root */
144
+ declare function themeToCSS(theme: FeedtackTheme): Record<string, string>;
145
+
146
+ export { type FeedtackAdapter as F, SCHEMA_VERSION as S, type FeedtackPayload as a, type FeedtackReply as b, type FeedtackResolution as c, type FeedtackFilter as d, type FeedbackItem as e, type FeedtackPinTarget as f, type FeedtackDeviceMeta as g, type FeedtackPageMeta as h, type FeedtackViewportMeta as i, type FeedtackArchive as j, type FeedtackBoundingRect as k, type FeedtackPin as l, type FeedtackSentiment as m, type FeedtackTheme as n, type FeedtackUser as o, themeToCSS as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feedtack",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Click anywhere. Drop a pin. Get a payload a developer can act on.",
5
5
  "type": "module",
6
6
  "license": "MIT",