@qaiddev/thumbs-embed 1.0.0 → 1.0.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/README.md +393 -0
- package/dist/qaid.js.map +1 -1
- package/dist/qaid.umd.cjs.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# @qaiddev/thumbs-embed
|
|
2
|
+
|
|
3
|
+
A zero-dependency, lightweight feedback embed that adds thumbs up/down buttons to any website. Users can optionally target specific page elements, leave messages, and capture screenshots — all submitted to your API endpoint or the [QAid.dev hosted dashboard](https://qaid.dev).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @qaiddev/thumbs-embed
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { FeedbackEmbed } from '@qaiddev/thumbs-embed';
|
|
15
|
+
|
|
16
|
+
const feedback = new FeedbackEmbed({
|
|
17
|
+
endpoint: 'https://qaid.dev/api/feedback',
|
|
18
|
+
apiKey: 'YOUR_API_KEY',
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### CDN / Script Tag
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script
|
|
26
|
+
src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"
|
|
27
|
+
data-endpoint="https://qaid.dev/api/feedback"
|
|
28
|
+
data-api-key="YOUR_API_KEY"
|
|
29
|
+
></script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The embed auto-initializes when it detects a `data-endpoint` attribute on its script tag.
|
|
33
|
+
|
|
34
|
+
### JSON Config (Script Tag)
|
|
35
|
+
|
|
36
|
+
For complex configurations, use a separate JSON config element:
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<script type="application/json" data-feedback-config>
|
|
40
|
+
{
|
|
41
|
+
"endpoint": "https://qaid.dev/api/feedback",
|
|
42
|
+
"apiKey": "YOUR_API_KEY",
|
|
43
|
+
"position": "bottom-left",
|
|
44
|
+
"colors": {
|
|
45
|
+
"positive": "#22c55e",
|
|
46
|
+
"negative": "#ef4444",
|
|
47
|
+
"marker": "#8b5cf6"
|
|
48
|
+
},
|
|
49
|
+
"text": {
|
|
50
|
+
"modalTitle": "How are we doing?",
|
|
51
|
+
"placeholder": "Tell us what you think..."
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
<script src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"></script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
1. Thumbs up/down buttons appear on your page
|
|
61
|
+
2. User clicks a thumb button
|
|
62
|
+
3. A targeting overlay activates — the user clicks on any page element to attach their feedback to it
|
|
63
|
+
4. Optional permission to save a screenshot of the current tab.
|
|
64
|
+
4. A modal appears where the user can optionally leave a more detailed message
|
|
65
|
+
5. Feedback is submitted to your endpoint as a JSON POST.
|
|
66
|
+
|
|
67
|
+
If `skipTargeting` is `true`, step 3 is skipped and the modal opens immediately.
|
|
68
|
+
|
|
69
|
+
If `captureScreenshot` is `false`, we skip step 4.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
All are optional except `endpoint`.
|
|
74
|
+
|
|
75
|
+
We offer a Free Plan for a compatible endpoint and a dashboard to manage your site's feedback.
|
|
76
|
+
|
|
77
|
+
### Core Options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `endpoint` | `string` | **(required)** | API endpoint URL for submitting feedback data |
|
|
82
|
+
| `apiKey` | `string` | `""` | API key for authenticating with the feedback service |
|
|
83
|
+
| `skipTargeting` | `boolean` | `false` | Skip element targeting and go directly to the feedback modal |
|
|
84
|
+
|
|
85
|
+
### Positioning
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|--------|------|---------|-------------|
|
|
89
|
+
| `position` | `string` | `"bottom-right"` | Button position: `"bottom-right"`, `"bottom-left"`, `"top-right"`, `"top-left"` |
|
|
90
|
+
| `offset` | `{ x?: number, y?: number }` | `{ x: 16, y: 16 }` | Distance from viewport edge in pixels |
|
|
91
|
+
| `container` | `string` | `""` | CSS selector for a custom container element. When set, `position`, `offset`, and `zIndex` are ignored |
|
|
92
|
+
| `zIndex` | `number` | `50` | z-index for the embed elements |
|
|
93
|
+
|
|
94
|
+
### Appearance
|
|
95
|
+
|
|
96
|
+
| Option | Type | Default | Description |
|
|
97
|
+
|--------|------|---------|-------------|
|
|
98
|
+
| `buttonSize` | `string` | `"medium"` | Button size: `"small"` (36px), `"medium"` (48px), `"large"` (64px) |
|
|
99
|
+
| `buttonClass` | `string` | `""` | Custom CSS class for buttons. When set, default button styles are not applied |
|
|
100
|
+
| `incognito` | `boolean` | `false` | Buttons are invisible until hovered |
|
|
101
|
+
| `modalWidth` | `number` | `400` | Width of the feedback modal in pixels |
|
|
102
|
+
| `backdropOpacity` | `number` | `0.3` | Opacity of the dark backdrop behind the modal (0-1) |
|
|
103
|
+
| `fontFamily` | `string` | `"system-ui, -apple-system, sans-serif"` | Font family for all text |
|
|
104
|
+
| `fontSize` | `number` | `16` | Base font size in pixels |
|
|
105
|
+
|
|
106
|
+
### Colors
|
|
107
|
+
|
|
108
|
+
Pass a `colors` object to customize the color scheme:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
new FeedbackEmbed({
|
|
112
|
+
endpoint: '/api/feedback',
|
|
113
|
+
colors: {
|
|
114
|
+
positive: 'rgb(0, 200, 83)', // Thumbs up color (default: green)
|
|
115
|
+
negative: 'rgb(255, 0, 0)', // Thumbs down color (default: red)
|
|
116
|
+
marker: '#6366f1', // Selected element outline & submit button (default: indigo)
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Colors accept hex (`#ABC`, `#AABBCC`) or `rgb(r, g, b)` format.
|
|
122
|
+
|
|
123
|
+
### Custom Icons
|
|
124
|
+
|
|
125
|
+
Replace the default thumb icons with SVG strings or emoji:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
new FeedbackEmbed({
|
|
129
|
+
endpoint: '/api/feedback',
|
|
130
|
+
positiveIcon: '<svg viewBox="0 0 24 24">...</svg>',
|
|
131
|
+
negativeIcon: '<svg viewBox="0 0 24 24">...</svg>',
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Text Customization
|
|
136
|
+
|
|
137
|
+
Every user-facing string can be overridden via the `text` object:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
new FeedbackEmbed({
|
|
141
|
+
endpoint: '/api/feedback',
|
|
142
|
+
text: {
|
|
143
|
+
tooltip: 'Any feedback? Click to start, Esc to cancel',
|
|
144
|
+
bannerText: 'Click on any element to target it with your feedback',
|
|
145
|
+
bannerHint: '(Press Escape to cancel)',
|
|
146
|
+
modalTitle: 'Thank you for your feedback!',
|
|
147
|
+
modalSubtitle: 'Would you like to add a message to help us understand your feedback better?',
|
|
148
|
+
placeholder: 'Optional: Tell us more about your experience...',
|
|
149
|
+
submitButton: 'Submit',
|
|
150
|
+
skipButton: 'Skip',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Screenshots
|
|
156
|
+
|
|
157
|
+
Enable automatic screenshot capture with feedback submissions:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
new FeedbackEmbed({
|
|
161
|
+
endpoint: '/api/feedback',
|
|
162
|
+
captureScreenshot: true,
|
|
163
|
+
screenshotOptions: {
|
|
164
|
+
quality: 0.8, // WebP compression quality (0-1)
|
|
165
|
+
maxWidth: 1280, // Max screenshot width in pixels
|
|
166
|
+
maxHeight: 800, // Max screenshot height in pixels
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Screenshots use the browser's Screen Capture API. The user will see a permission dialog. If they decline or the API is unavailable, the feedback is still submitted without a screenshot.
|
|
172
|
+
|
|
173
|
+
## Script Tag Data Attributes
|
|
174
|
+
|
|
175
|
+
When using the script tag method, all config options are available as `data-*` attributes:
|
|
176
|
+
|
|
177
|
+
| Attribute | Maps To |
|
|
178
|
+
|-----------|---------|
|
|
179
|
+
| `data-endpoint` | `endpoint` |
|
|
180
|
+
| `data-api-key` | `apiKey` |
|
|
181
|
+
| `data-position` | `position` |
|
|
182
|
+
| `data-zindex` | `zIndex` |
|
|
183
|
+
| `data-button-size` | `buttonSize` |
|
|
184
|
+
| `data-button-class` | `buttonClass` |
|
|
185
|
+
| `data-modal-width` | `modalWidth` |
|
|
186
|
+
| `data-backdrop-opacity` | `backdropOpacity` |
|
|
187
|
+
| `data-offset-x` | `offset.x` |
|
|
188
|
+
| `data-offset-y` | `offset.y` |
|
|
189
|
+
| `data-positive-color` | `colors.positive` |
|
|
190
|
+
| `data-negative-color` | `colors.negative` |
|
|
191
|
+
| `data-marker-color` | `colors.marker` |
|
|
192
|
+
| `data-container` | `container` |
|
|
193
|
+
| `data-skip-targeting` | `skipTargeting` |
|
|
194
|
+
| `data-incognito` | `incognito` |
|
|
195
|
+
| `data-font-family` | `fontFamily` |
|
|
196
|
+
| `data-font-size` | `fontSize` |
|
|
197
|
+
| `data-tooltip` | `text.tooltip` |
|
|
198
|
+
| `data-banner-text` | `text.bannerText` |
|
|
199
|
+
| `data-banner-hint` | `text.bannerHint` |
|
|
200
|
+
| `data-modal-title` | `text.modalTitle` |
|
|
201
|
+
| `data-modal-subtitle` | `text.modalSubtitle` |
|
|
202
|
+
| `data-placeholder` | `text.placeholder` |
|
|
203
|
+
| `data-submit-button` | `text.submitButton` |
|
|
204
|
+
| `data-skip-button` | `text.skipButton` |
|
|
205
|
+
| `data-positive-icon` | `positiveIcon` |
|
|
206
|
+
| `data-negative-icon` | `negativeIcon` |
|
|
207
|
+
|
|
208
|
+
## Custom Button Container
|
|
209
|
+
|
|
210
|
+
By default, the embed creates a fixed-position container in the viewport corner. To place the buttons inside your own element:
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<div id="my-feedback-spot"></div>
|
|
214
|
+
|
|
215
|
+
<script>
|
|
216
|
+
new FeedbackEmbed({
|
|
217
|
+
endpoint: '/api/feedback',
|
|
218
|
+
container: '#my-feedback-spot',
|
|
219
|
+
});
|
|
220
|
+
</script>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
When `container` is set, the `position`, `offset`, and `zIndex` options are ignored. You control the layout.
|
|
224
|
+
|
|
225
|
+
## Custom Button Styling
|
|
226
|
+
|
|
227
|
+
Use `buttonClass` to apply your own CSS instead of the default button styles:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
new FeedbackEmbed({
|
|
231
|
+
endpoint: '/api/feedback',
|
|
232
|
+
buttonClass: 'my-feedback-btn',
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
When `buttonClass` is provided, default button colors, sizing, and shadows are not applied. Structural styles (display, alignment, cursor) are still applied via `.qaid-btn-structural`. Your class controls everything visual.
|
|
237
|
+
|
|
238
|
+
```css
|
|
239
|
+
.my-feedback-btn {
|
|
240
|
+
width: 40px;
|
|
241
|
+
height: 40px;
|
|
242
|
+
background: #1a1a2e;
|
|
243
|
+
border: 2px solid #e94560;
|
|
244
|
+
border-radius: 8px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.my-feedback-btn:hover {
|
|
248
|
+
background: #e94560;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## API
|
|
253
|
+
|
|
254
|
+
### Constructor
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const feedback = new FeedbackEmbed(config: FeedbackConfig);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Methods
|
|
261
|
+
|
|
262
|
+
| Method | Description |
|
|
263
|
+
|--------|-------------|
|
|
264
|
+
| `destroy()` | Remove all DOM elements, event listeners, and injected styles. Safe to call multiple times. |
|
|
265
|
+
|
|
266
|
+
### Payload
|
|
267
|
+
|
|
268
|
+
The embed submits feedback in two steps:
|
|
269
|
+
|
|
270
|
+
**1. Initial submission** — `POST` to your endpoint:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"feedbackType": "up",
|
|
275
|
+
"pageUrl": "https://example.com/page",
|
|
276
|
+
"apiKey": "YOUR_API_KEY",
|
|
277
|
+
"elementSelector": "body > main:nth-child(2) > button:nth-child(3)",
|
|
278
|
+
"elementText": "Submit Order",
|
|
279
|
+
"consoleErrors": [
|
|
280
|
+
{ "message": "TypeError: Cannot read properties of undefined", "timestamp": 1704067200000, "level": "error" }
|
|
281
|
+
],
|
|
282
|
+
"screenWidth": 1920,
|
|
283
|
+
"screenHeight": 1080,
|
|
284
|
+
"clickX": 450,
|
|
285
|
+
"clickY": 320,
|
|
286
|
+
"scrollX": 0,
|
|
287
|
+
"scrollY": 150,
|
|
288
|
+
"screenshot": "data:image/webp;base64,...",
|
|
289
|
+
"visitorId": "a1b2c3d4-...",
|
|
290
|
+
"elementBounds": { "x": 400, "y": 300, "width": 120, "height": 40 },
|
|
291
|
+
"userAgent": "Mozilla/5.0 ..."
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Your endpoint should return `{ "id": 123 }`.
|
|
296
|
+
|
|
297
|
+
**2. Message update** — `PATCH` to `{endpoint}/{id}`:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"message": "The submit button doesn't work on mobile"
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Console Capture
|
|
306
|
+
|
|
307
|
+
The embed automatically captures up to 20 recent `console.error`, `console.warn`, and `console.log` calls during the user's session. These are included in the feedback payload as `consoleErrors`, giving you context about what went wrong before the user submitted feedback.
|
|
308
|
+
|
|
309
|
+
Console methods are restored to their originals when `destroy()` is called.
|
|
310
|
+
|
|
311
|
+
## Element Targeting
|
|
312
|
+
|
|
313
|
+
When the user clicks on an element during targeting mode, the embed generates a stable CSS selector using this priority:
|
|
314
|
+
|
|
315
|
+
1. **Data attributes** — `data-comp`, `data-qa`, `data-testid`, `data-id` (checked on the element and its ancestors)
|
|
316
|
+
2. **Element ID** — e.g. `#submit-button`
|
|
317
|
+
3. **nth-child path** — e.g. `body > main:nth-child(2) > button:nth-child(3)` (always unique, always valid)
|
|
318
|
+
|
|
319
|
+
The first 100 characters of the element's text content are also captured.
|
|
320
|
+
|
|
321
|
+
## Responsive Behavior
|
|
322
|
+
|
|
323
|
+
- **Desktop**: The feedback modal is positioned near the selected element with an arrow pointing at the click location
|
|
324
|
+
- **Mobile** (viewport < 640px): The modal displays as a bottom sheet, sliding up from the bottom of the screen with safe-area inset support
|
|
325
|
+
|
|
326
|
+
## CSS Custom Properties
|
|
327
|
+
|
|
328
|
+
The embed injects CSS custom properties you can use or override:
|
|
329
|
+
|
|
330
|
+
```css
|
|
331
|
+
:root {
|
|
332
|
+
--qaid-positive: rgb(0, 200, 83);
|
|
333
|
+
--qaid-negative: rgb(255, 0, 0);
|
|
334
|
+
--qaid-marker: #6366f1;
|
|
335
|
+
--qaid-btn-size: 48px;
|
|
336
|
+
--qaid-icon-size: 24px;
|
|
337
|
+
--qaid-modal-width: 400px;
|
|
338
|
+
--qaid-backdrop-opacity: 0.3;
|
|
339
|
+
--qaid-font-family: system-ui, -apple-system, sans-serif;
|
|
340
|
+
--qaid-font-size: 16px;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The embed also supports dark mode automatically via the `light-dark()` CSS function.
|
|
345
|
+
|
|
346
|
+
## Hosted Dashboard
|
|
347
|
+
|
|
348
|
+
[Qaid](https://qaid.dev) provides a hosted dashboard for managing feedback collected by this embed:
|
|
349
|
+
|
|
350
|
+
- **Feedback inbox** with archiving and admin notes
|
|
351
|
+
- **Project management** with multiple API keys
|
|
352
|
+
- **Team collaboration** with owners, members, and invitations
|
|
353
|
+
- **Browser context** — console errors, viewport info, user agent captured with each submission
|
|
354
|
+
- **Screenshot viewing** for visual bug reports
|
|
355
|
+
- **Analytics** and data retention
|
|
356
|
+
|
|
357
|
+
### Plans
|
|
358
|
+
|
|
359
|
+
| | Cadet (Free) | Commander ($9/mo) | Admiral (Enterprise) |
|
|
360
|
+
|---|---|---|---|
|
|
361
|
+
| Projects | 1 | Unlimited | Unlimited |
|
|
362
|
+
| Messages | 100/mo | Unlimited | Unlimited |
|
|
363
|
+
| Data retention | 7 days | 1 year | Unlimited |
|
|
364
|
+
| Screenshots | - | Yes | Yes |
|
|
365
|
+
| Notifications | - | Email/Text | Email/Text |
|
|
366
|
+
| Integrations | - | GitHub + more | Custom |
|
|
367
|
+
|
|
368
|
+
Sign up at [qaid.dev](https://qaid.dev). You can also self-host — the embed works with any endpoint that accepts the payload format described above.
|
|
369
|
+
|
|
370
|
+
## Self-Hosting
|
|
371
|
+
|
|
372
|
+
The embed is endpoint-agnostic. Point it at your own server:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
new FeedbackEmbed({
|
|
376
|
+
endpoint: 'https://your-server.com/api/feedback',
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Your server needs to handle:
|
|
381
|
+
|
|
382
|
+
1. `POST /api/feedback` — Accept the feedback payload, return `{ "id": number }`
|
|
383
|
+
2. `PATCH /api/feedback/:id` — Accept `{ "message": string | null }` or `{ "feedbackType": "up" | "down" }`
|
|
384
|
+
|
|
385
|
+
## Project Badges
|
|
386
|
+
|
|
387
|
+
<a href="https://www.npmjs.com/package/@qaiddev/thumbs-embed">
|
|
388
|
+
<img alt="npm version" src="https://img.shields.io/npm/v/@qaiddev/thumbs-embed" />
|
|
389
|
+
</a>
|
|
390
|
+
|
|
391
|
+
<a href="https://github.com/qaiddev/thumbs-embed/blob/prod/LICENSE">
|
|
392
|
+
<img alt="license" src="https://img.shields.io/npm/l/@qaiddev/thumbs-embed"/>
|
|
393
|
+
</a>
|