@inputbuffer/feedback 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 inputbuffer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,638 @@
1
+ # @inputbuffer/feedback
2
+
3
+ [InputBuffer](https://inputbuffer.io) is a feedback platform built for developer-facing products. It collects feedback from your users and uses AI-driven categorization to surface patterns.
4
+
5
+ This package is a lightweight embeddable widget you can drop into your documentation sites, API and SDK references, CLI docs, or any other web property.
6
+
7
+ ---
8
+
9
+ ## Table of contents
10
+
11
+ - [Quick start](#quick-start)
12
+ - [Inline thumbs bar](#inline-thumbs-bar-web-component)
13
+ - [Thumbs only](#thumbs-only-web-component)
14
+ - [Floating thumbs bar](#floating-thumbs-bar-web-component)
15
+ - [Modal](#modal-script-tag)
16
+ - [Verify it's working](#verify-its-working)
17
+ - [Troubleshooting](#troubleshooting)
18
+ - [Installation](#installation)
19
+ - [CDN](#cdn-recommended)
20
+ - [npm](#npm)
21
+ - [Browser support](#browser-support)
22
+ - [Reference](#reference)
23
+ - [Feedback bar](#feedback-bar)
24
+ - [Web component](#web-component)
25
+ - [`createBar(config)`](#inputbufferiocreatebarconfig)
26
+ - [`bar.on(event, handler)`](#baronevent-handler)
27
+ - [`bar.destroy()`](#bardestroy)
28
+ - [Feedback modal](#feedback-modal)
29
+ - [Script tag attributes](#script-tag-attributes-auto-init)
30
+ - [`createModal(config)`](#inputbufferiocreatemodal config)
31
+ - [`instance.open(options?)`](#instanceopenoptions)
32
+ - [`instance.close()`](#instanceclose)
33
+ - [`instance.destroy()`](#instancedestroy)
34
+ - [`instance.on(event, handler)`](#instanceonevent-handler)
35
+ - [`InputBufferIO.version`](#inputbufferioversion)
36
+ - [Target metadata schemas](#target-metadata-schemas)
37
+ - [CSS customization](#css-customization)
38
+ - [Modal selectors](#modal-selectors)
39
+ - [Bar selectors](#bar-selectors)
40
+ - [CSS custom properties](#css-custom-properties)
41
+ - [Common tasks](#common-tasks)
42
+ - [License](#license)
43
+
44
+ ---
45
+
46
+ ## Quick start
47
+
48
+ Before you start you will need a widget API key from your [InputBuffer dashboard](https://inputbuffer.io).
49
+
50
+ There are three bundles — pick the one that matches your use case:
51
+
52
+ | Bundle | Size | What it includes |
53
+ |---|---|---|
54
+ | `bar.js` | 18 KB | Thumbs up/down bar with optional follow-up form |
55
+ | `modal.js` | 19 KB | Full-text feedback modal |
56
+ | `widget.js` | 36 KB | Both bar and modal |
57
+
58
+ ### Inline thumbs bar (web component)
59
+
60
+ The simplest way to add feedback. Drop the script tag anywhere in your page, then place the web component where you want the bar to appear:
61
+
62
+ ```html
63
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
64
+
65
+ <inputbuffer-feedback
66
+ api-key="YOUR_WIDGET_TOKEN"
67
+ label="Was this helpful?">
68
+ </inputbuffer-feedback>
69
+ ```
70
+
71
+ The bar renders inline with thumbs up/down buttons. After a vote, a short follow-up form appears so the user can add context. On submit, the feedback is sent to InputBuffer.
72
+
73
+ | Light | Dark |
74
+ |---|---|
75
+ | ![Bar light](example/bar-light.png) | ![Bar dark](example/bar-dark.png) |
76
+
77
+ ### Thumbs only (web component)
78
+
79
+ Omit the `label` attribute to show just the thumbs with no text:
80
+
81
+ ```html
82
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
83
+
84
+ <inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN"></inputbuffer-feedback>
85
+ ```
86
+
87
+ | Light | Dark |
88
+ |---|---|
89
+ | ![Thumbs light](example/thumbs-light.png) | ![Thumbs dark](example/thumbs-dark.png) |
90
+
91
+ ### Floating thumbs bar (web component)
92
+
93
+ Add `placement="fixed"` to pin the bar to the bottom of the viewport — useful for documentation pages where you want persistent feedback access:
94
+
95
+ ```html
96
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
97
+
98
+ <inputbuffer-feedback
99
+ api-key="YOUR_WIDGET_TOKEN"
100
+ label="Was this helpful?"
101
+ placement="fixed">
102
+ </inputbuffer-feedback>
103
+ ```
104
+
105
+ ### Modal (script tag)
106
+
107
+ If you want a full-text feedback modal triggered by a button, load `modal.js` with your API key and a CSS selector for the trigger element:
108
+
109
+ | Light | Dark |
110
+ |---|---|
111
+ | ![Modal light](example/modal-light.png) | ![Modal dark](example/modal-dark.png) |
112
+
113
+ ```html
114
+ <button id="feedback-btn">Send feedback</button>
115
+
116
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
117
+ data-api-key="YOUR_WIDGET_TOKEN"
118
+ data-attach-to="#feedback-btn">
119
+ </script>
120
+ ```
121
+
122
+ When the page loads the widget connects to `#feedback-btn`. Clicking it opens a modal with a text field and optional email field.
123
+
124
+ ### Verify it's working
125
+
126
+ Submit a test message from your page and open your InputBuffer dashboard. It should appear in your feedback inbox within a few seconds, already categorized.
127
+
128
+ ### Troubleshooting
129
+
130
+ - **Nothing happens on click:** check that the `data-attach-to` selector matches your button's `id` exactly, including the `#`.
131
+ - **401 error:** your `data-api-key` is invalid — try again with a fresh copy from the dashboard, and if it still fails [contact us](https://inputbuffer.io/contact).
132
+
133
+ ### Where to go from here
134
+
135
+ - Customize the bar with `label`, `placement`, and theme attributes (see [Web component](#web-component))
136
+ - Create the bar programmatically with `createBar()` for full config access (see [`InputBufferIO.createBar(config)`](#inputbuffercreatebarconfig))
137
+ - Listen for vote and submit events (see [`bar.on(event, handler)`](#baronevent-handler))
138
+
139
+ ---
140
+
141
+ ## Installation
142
+
143
+ ### CDN (recommended)
144
+
145
+ Load only the component you need — each is roughly half the full bundle:
146
+
147
+ ```html
148
+ <!-- Full bundle (modal + bar) — 36 KB -->
149
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/widget.js"
150
+ data-api-key="YOUR_WIDGET_TOKEN">
151
+ </script>
152
+
153
+ <!-- Modal only — 19 KB -->
154
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
155
+ data-api-key="YOUR_WIDGET_TOKEN">
156
+ </script>
157
+
158
+ <!-- Bar only — 18 KB -->
159
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
160
+ ```
161
+
162
+ > **SRI note:** For production deployments, add a Subresource Integrity `integrity` attribute to guard against CDN compromise. Generate the hash for each pinned version with:
163
+ > ```bash
164
+ > curl -s https://cdn.jsdelivr.net/npm/@inputbuffer/feedback@0.1.0/dist/widget.js | openssl dgst -sha384 -binary | openssl base64 -A
165
+ > ```
166
+ > Then use `integrity="sha384-<hash>" crossorigin="anonymous"` on the `<script>` tag.
167
+
168
+ The stylesheet is injected automatically — no separate `<link>` tag needed. Pass `data-inject-styles="false"` to opt out and load `dist/modal.css` or `dist/bar.css` yourself.
169
+
170
+ > **CSP note:** Style injection requires `style-src 'unsafe-inline'` in your Content Security Policy. If your CSP blocks inline styles, pass `data-inject-styles="false"` (or `injectStyles: false` in `createModal()`) and load the stylesheet manually via `<link>` instead.
171
+
172
+ ### npm
173
+
174
+ ```bash
175
+ npm install @inputbuffer/feedback
176
+ ```
177
+
178
+ Import only what you use — your bundler will tree-shake the rest:
179
+
180
+ ```js
181
+ // Modal only (~19 KB unminified)
182
+ import { createModal } from '@inputbuffer/feedback/modal';
183
+
184
+ // Bar only (~18 KB unminified)
185
+ import { createBar } from '@inputbuffer/feedback/bar';
186
+
187
+ // Full bundle
188
+ import { InputBufferIO } from '@inputbuffer/feedback';
189
+ ```
190
+
191
+ TypeScript types are included and exported from each entry point.
192
+
193
+ ### Browser support
194
+
195
+ Chrome 111+, Firefox 113+, Safari 16.2+. The bundles target ES2019 and rely on Custom Elements v1 (used by the `<inputbuffer-feedback>` web component).
196
+
197
+ ---
198
+
199
+ ## Reference
200
+
201
+ Each CDN bundle sets `window.InputBufferIO` at parse time — no `DOMContentLoaded` needed. What's exposed depends on which bundle you load:
202
+
203
+ | Bundle | `window.InputBufferIO` |
204
+ |---|---|
205
+ | `widget.js` | `{ createModal, createBar, version }` |
206
+ | `modal.js` | `{ createModal, version }` |
207
+ | `bar.js` | `{ createBar, version }` |
208
+
209
+ When using npm imports, use the named exports directly (`createModal`, `createBar`) rather than the global.
210
+
211
+ ---
212
+
213
+ ## Feedback bar
214
+
215
+ The feedback bar is an inline or fixed thumbs up/down component — a lightweight alternative to the modal. It can be used as a web component or created programmatically.
216
+
217
+ ### Web component
218
+
219
+ ```html
220
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
221
+
222
+ <inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN" label="Was this helpful?"></inputbuffer-feedback>
223
+ ```
224
+
225
+ Supported attributes:
226
+
227
+ | Attribute | Description |
228
+ |---|---|
229
+ | `api-key` | **Required.** Your widget API key. |
230
+ | `api-url` | Override the API endpoint. |
231
+ | `label` | Text shown next to the thumbs. |
232
+ | `placement` | `"inline"` (default) or `"fixed"` (pins to bottom of viewport). |
233
+ | `theme-primary` | Primary color. |
234
+ | `theme-background` | Background color. |
235
+ | `theme-text` | Text color. |
236
+ | `theme-selected` | Background color of the selected thumb. |
237
+ | `theme-selected-color` | Icon color of the selected thumb. |
238
+ | `inject-styles` | Set to `"false"` to skip automatic style injection. |
239
+
240
+ > **Note:** `colorScheme`, `showLabel`, `modalTitle`, `modalPlaceholder`, `showTitleField`, `showEmailField`, and `source` are not available as web component attributes. Use `InputBufferIO.createBar(config)` for those options.
241
+
242
+ ### `InputBufferIO.createBar(config)`
243
+
244
+ Creates a feedback bar programmatically and returns a `FeedbackBarInstance` with an `element` property (mount it yourself) and a `destroy()` method.
245
+
246
+ ```js
247
+ const bar = InputBufferIO.createBar({
248
+ apiKey: 'YOUR_WIDGET_TOKEN',
249
+ label: 'Was this helpful?',
250
+ });
251
+ document.getElementById('my-slot').appendChild(bar.element);
252
+ ```
253
+
254
+ | Property | Type | Default | Description |
255
+ |---|---|---|---|
256
+ | `apiKey` | string | — | **Required.** Your widget API key. |
257
+ | `apiUrl` | string | — | Override the API endpoint. |
258
+ | `label` | string | — | Text shown next to the thumbs. |
259
+ | `showLabel` | boolean | `true` | Set to `false` to show thumbs only. |
260
+ | `placement` | `'inline'` \| `'fixed'` | `'inline'` | `fixed` pins the bar to the bottom of the viewport. |
261
+ | `colorScheme` | `'light'` \| `'dark'` \| `'auto'` | `'auto'` | Force a color scheme or follow the system setting. |
262
+ | `theme.primary` | string | — | Primary color (buttons, focus rings). |
263
+ | `theme.background` | string | — | Bar and popover background color. |
264
+ | `theme.surface` | string | — | Popover header surface color. |
265
+ | `theme.text` | string | — | Text color. |
266
+ | `theme.selected` | string | — | Background color of the selected thumb. |
267
+ | `theme.selectedColor` | string | — | Icon color of the selected thumb. |
268
+ | `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | — | The kind of thing the user is giving feedback on. Used for AI categorization. |
269
+ | `target.targetId` | string | — | Optional stable ID for this target (used for deduplication on the server). |
270
+ | `target.displayName` | string | — | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
271
+ | `target.dedupKey` | string | — | Custom deduplication key (max 500 chars). |
272
+ | `target.metadata` | object | — | Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
273
+ | `modalTitle` | string | — | Heading for the follow-up popover. |
274
+ | `modalPlaceholder` | string | — | Textarea placeholder for the follow-up popover. |
275
+ | `showEmailField` | boolean | `false` | Show/hide the email field in the follow-up popover. |
276
+ | `showTitleField` | boolean | `false` | Show/hide the title field in the follow-up popover. |
277
+ | `source` | string | — | Tag identifying which of your surfaces this widget is embedded on (e.g. `"ios-app"`, `"docs-site"`). Stored on every submission for filtering in the dashboard. |
278
+ | `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
279
+
280
+ ### `bar.on(event, handler)`
281
+
282
+ Subscribes to bar lifecycle events.
283
+
284
+ ```js
285
+ bar.on('vote', ({ sentiment }) => console.log('Voted:', sentiment));
286
+ bar.on('open', ({ sentiment }) => console.log('Popover opened after:', sentiment));
287
+ bar.on('submit', ({ id }) => console.log('Feedback ID:', id));
288
+ bar.on('close', () => console.log('Popover closed'));
289
+ bar.on('error', (err) => console.error('Submission failed:', err));
290
+ ```
291
+
292
+ | Event | Handler signature | When it fires |
293
+ |---|---|---|
294
+ | `vote` | `({ sentiment: 'positive' \| 'negative' }) => void` | User clicks a thumb before submitting. |
295
+ | `open` | `({ sentiment: 'positive' \| 'negative' }) => void` | The follow-up popover opens. |
296
+ | `submit` | `({ id: string }) => void` | Feedback was submitted successfully. |
297
+ | `close` | `() => void` | The follow-up popover closes. |
298
+ | `error` | `(err: Error) => void` | The submission request failed. |
299
+
300
+ ### `bar.destroy()`
301
+
302
+ Removes the bar element and all event listeners.
303
+
304
+ ---
305
+
306
+ ## Feedback modal
307
+
308
+ The feedback modal is a full-text input form triggered by a button click. It can be opened programmatically or auto-initialized from the script tag.
309
+
310
+ ### Script tag attributes (auto-init)
311
+
312
+ When `data-api-key` is present on the script tag, the widget initializes automatically. All config is read from `data-*` attributes.
313
+
314
+ | Attribute | Type | Description |
315
+ |---|---|---|
316
+ | `data-api-key` | string | **Required.** Your widget API key. |
317
+ | `data-api-url` | string | Override the API endpoint (useful for local dev/testing). |
318
+ | `data-attach-to` | string | CSS selector for the element that opens the modal on click. |
319
+ | `data-inject-styles` | boolean | Set to `"false"` to skip automatic style injection. |
320
+ | `data-color-scheme` | string | `"light"`, `"dark"`, or `"auto"`. |
321
+ | `data-theme-primary` | string | Primary color (buttons, focus rings). Any CSS color value. |
322
+ | `data-theme-background` | string | Modal background color. |
323
+ | `data-theme-text` | string | Modal text color. |
324
+ | `data-theme-selected` | string | Background color of the selected sentiment thumb. |
325
+ | `data-theme-selected-color` | string | Icon color of the selected sentiment thumb. |
326
+
327
+ > **Note:** `theme.surface` is only available via the programmatic API (`createModal(config)`), not as a `data-*` attribute.
328
+
329
+ ### `InputBufferIO.createModal(config)`
330
+
331
+ Creates a widget instance programmatically. Returns a `WidgetInstance`.
332
+
333
+ ```js
334
+ const ib = InputBufferIO.createModal({
335
+ apiKey: 'YOUR_WIDGET_TOKEN',
336
+ });
337
+ ```
338
+
339
+ | Property | Type | Default | Description |
340
+ |---|---|---|---|
341
+ | `apiKey` | string | — | **Required.** Your widget API key. |
342
+ | `apiUrl` | string | — | Override the API endpoint (useful for local dev/testing). |
343
+ | `attachTo` | string | — | CSS selector. Clicking the matched element calls `open()`. |
344
+ | `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
345
+ | `title` | string | — | Modal heading. Omit to render no title. |
346
+ | `placeholder` | string | `"What's on your mind?"` | Textarea placeholder text. |
347
+ | `showEmailField` | boolean | `false` | Set to `true` to show an optional email field. |
348
+ | `showTitleField` | boolean | `false` | Set to `true` to show an optional title field. |
349
+ | `showSentiment` | boolean | `false` | Show thumbs up/down sentiment buttons in the modal. |
350
+ | `source` | string | — | Tag identifying which of your surfaces this widget is embedded on (e.g. `"ios-app"`, `"docs-site"`). Stored on every submission for filtering in the dashboard. |
351
+ | `colorScheme` | `'light'` \| `'dark'` \| `'auto'` | `'auto'` | Force a color scheme or follow the system setting. |
352
+ | `theme.primary` | string | — | Primary color (buttons, focus rings). |
353
+ | `theme.background` | string | — | Modal background color. |
354
+ | `theme.surface` | string | — | Modal header surface color. |
355
+ | `theme.text` | string | — | Modal text color. |
356
+ | `theme.selected` | string | — | Background color of the selected sentiment thumb. |
357
+ | `theme.selectedColor` | string | — | Icon color of the selected sentiment thumb. |
358
+
359
+ ### `instance.open(options?)`
360
+
361
+ Opens the feedback modal. Pass options to provide context at call time — useful when the same widget instance is reused across different pages or sections.
362
+
363
+ ```js
364
+ ib.open({
365
+ title: 'Was this helpful?',
366
+ target: {
367
+ type: 'documentation',
368
+ targetId: 'auth-overview',
369
+ displayName: 'Authentication overview',
370
+ metadata: {
371
+ page_url: window.location.href,
372
+ section_heading: 'Authentication',
373
+ },
374
+ },
375
+ prefill: {
376
+ description: '👍 This page was helpful',
377
+ },
378
+ });
379
+ ```
380
+
381
+ | Property | Type | Description |
382
+ |---|---|---|
383
+ | `title` | string | Overrides the modal heading for this open call. |
384
+ | `sentiment` | `'positive'` \| `'negative'` | Pre-selects a sentiment thumb. Only relevant when `showSentiment` is enabled. |
385
+ | `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | The kind of thing the user is giving feedback on. Used for AI categorization. |
386
+ | `target.targetId` | string | Optional stable ID for this target (used for deduplication on the server). |
387
+ | `target.displayName` | string | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
388
+ | `target.dedupKey` | string | Custom deduplication key (max 500 chars). |
389
+ | `target.metadata` | object | Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
390
+ | `prefill.email` | string | Pre-populates the email field. |
391
+ | `prefill.description` | string | Pre-populates the textarea. |
392
+ | `source` | string | Overrides the `source` set in `createModal(config)` for this open call. |
393
+
394
+ ### `instance.close()`
395
+
396
+ Closes the modal programmatically.
397
+
398
+ ### `instance.destroy()`
399
+
400
+ Closes the modal and removes all event listeners and DOM elements. The instance cannot be reused after this call — invoke `createModal()` again to get a fresh one. You may have multiple `WidgetInstance`s on a page, but opening more than one modal simultaneously is not supported (they share DOM IDs).
401
+
402
+ ### `instance.on(event, handler)`
403
+
404
+ Subscribes to widget lifecycle events.
405
+
406
+ ```js
407
+ ib.on('submit', (result) => console.log('Feedback ID:', result.id));
408
+ ib.on('close', () => console.log('Modal closed'));
409
+ ib.on('error', (err) => console.error('Submission failed:', err));
410
+ ```
411
+
412
+ | Event | Handler signature | When it fires |
413
+ |---|---|---|
414
+ | `submit` | `(result: { id: string }) => void` | Feedback was submitted successfully. `result.id` is the InputBuffer feedback ID. |
415
+ | `close` | `() => void` | The modal was closed, either by the user or programmatically. |
416
+ | `error` | `(err: Error) => void` | The submission request failed. |
417
+
418
+ ### `InputBufferIO.version`
419
+
420
+ The currently loaded widget version string.
421
+
422
+ ```js
423
+ console.log(InputBufferIO.version); // e.g. "0.1.0"
424
+ ```
425
+
426
+ ### Target metadata schemas
427
+
428
+ The fields accepted in `target.metadata` depend on `target.type`. The server validates required fields; the client does not enforce them.
429
+
430
+ **`documentation`**
431
+
432
+ | Field | Required | Description |
433
+ |---|---|---|
434
+ | `page_url` | No | Full URL of the page. |
435
+ | `page_slug` | No | Slug or path of the page. |
436
+ | `section_heading` | No | Heading of the section the user is viewing. |
437
+ | `doc_version` | No | Documentation version string. |
438
+
439
+ **`rest_endpoint`**
440
+
441
+ | Field | Required | Description |
442
+ |---|---|---|
443
+ | `method` | Yes* | HTTP method (`GET`, `POST`, etc.). |
444
+ | `path` | Yes* | API path (e.g. `/v1/users`). |
445
+ | `host` | No | Hostname (e.g. `api.example.com`). |
446
+ | `api_version` | No | API version string. |
447
+
448
+ **`cli_command`**
449
+
450
+ | Field | Required | Description |
451
+ |---|---|---|
452
+ | `command` | Yes* | Top-level CLI command (e.g. `auth`). |
453
+ | `subcommand` | No | Subcommand (e.g. `setup`). |
454
+ | `cli_version` | No | CLI version string. |
455
+
456
+ \* Required by the server; omitting them will result in a validation error response.
457
+
458
+ ---
459
+
460
+ ## CSS customization
461
+
462
+ Each component uses stable selectors you can target directly in your stylesheet.
463
+
464
+ ### Modal selectors
465
+
466
+ | Selector | Class alias | Element |
467
+ |---|---|---|
468
+ | `#ib-overlay` | — | Full-screen backdrop |
469
+ | `#ib-modal` | — | Modal container (scopes all CSS variables) |
470
+ | `#ib-modal-header` | — | Header bar |
471
+ | `#ib-modal-body` | — | Body area |
472
+ | `#ib-title` | `.ib-modal-title` | Modal heading |
473
+ | `#ib-textarea` | `.ib-modal-textarea` | Feedback text field |
474
+ | `#ib-email` | `.ib-modal-email` | Email input |
475
+ | `#ib-submit` | `.ib-modal-submit` | Submit button |
476
+ | `#ib-close` | `.ib-modal-close` | Close button |
477
+ | `#ib-success` | `.ib-modal-success` | Success message |
478
+ | `#ib-error` | `.ib-modal-error` | Error message |
479
+
480
+ The IDs are the stable public API and will not change between releases. The `.ib-modal-*` class aliases are equivalent and exist for symmetry with the bar.
481
+
482
+ ### Bar selectors
483
+
484
+ | Class | Element |
485
+ |---|---|
486
+ | `.ib-bar-wrapper` | Outer wrapper (scopes all CSS variables) |
487
+ | `.ib-bar` | The visible bar strip |
488
+ | `.ib-bar-label` | Label text |
489
+ | `.ib-bar-btn` | Thumb buttons |
490
+ | `.ib-bar-popover` | Follow-up popover container |
491
+ | `.ib-bar-header` | Popover header |
492
+ | `.ib-bar-body` | Popover body |
493
+ | `.ib-bar-title` | Popover heading |
494
+ | `.ib-bar-textarea` | Feedback text field |
495
+ | `.ib-bar-email` | Email input |
496
+ | `.ib-bar-submit` | Submit button |
497
+ | `.ib-bar-success` | Success message |
498
+ | `.ib-bar-error` | Error message |
499
+
500
+ ### CSS custom properties
501
+
502
+ Both components expose the same set of CSS custom properties. Set them on `#ib-modal` or `.ib-bar-wrapper` respectively, or pass them via the `theme` config option.
503
+
504
+ | Property | Default (light) | Default (dark) | What it affects |
505
+ |---|---|---|---|
506
+ | `--ib-primary` | `#6366f1` | `#3A5244` | Buttons, focus rings, borders |
507
+ | `--ib-primary-hover` | `#4338ca` | `#4E6857` | Button hover state |
508
+ | `--ib-background` | `#f6f6f8` | `#25272B` | Form area background |
509
+ | `--ib-surface` | `#ffffff` | `#2C3630` | Header/card background |
510
+ | `--ib-text` | `#111827` | `#E5E7EB` | Body text |
511
+ | `--ib-muted` | `#8d99ae` | `#9CA3AF` | Label and hint text |
512
+ | `--ib-border` | `(primary)` | `#363840` | Border color |
513
+ | `--ib-selected` | `(primary)` | `(primary)` | Selected thumb background |
514
+ | `--ib-selected-color` | `(surface)` | `(surface)` | Selected thumb icon color |
515
+ | `--ib-radius` | `2px` | `8px` | Container border radius |
516
+ | `--ib-radius-input` | `2px` | `4px` | Input/button border radius |
517
+
518
+ ---
519
+
520
+ ## Common tasks
521
+
522
+ ### Attach feedback to a specific docs page
523
+
524
+ ```js
525
+ // <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
526
+ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
527
+
528
+ document.getElementById('feedback-btn').addEventListener('click', () => {
529
+ ib.open({
530
+ title: 'Was this page helpful?',
531
+ target: {
532
+ type: 'documentation',
533
+ targetId: window.location.pathname,
534
+ displayName: document.title,
535
+ metadata: {
536
+ page_url: window.location.href,
537
+ section_heading: document.querySelector('h1')?.textContent ?? '',
538
+ },
539
+ },
540
+ });
541
+ });
542
+ ```
543
+
544
+ ### Attach feedback to a REST endpoint reference
545
+
546
+ ```js
547
+ // <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
548
+ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
549
+
550
+ ib.open({
551
+ target: {
552
+ type: 'rest_endpoint',
553
+ targetId: 'POST /v1/uploads',
554
+ displayName: 'Upload a file',
555
+ metadata: { method: 'POST', path: '/v1/uploads' },
556
+ },
557
+ });
558
+ ```
559
+
560
+ ### Attach feedback to a CLI command reference
561
+
562
+ ```js
563
+ // <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
564
+ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
565
+
566
+ ib.open({
567
+ target: {
568
+ type: 'cli_command',
569
+ targetId: 'deploy',
570
+ displayName: 'my-cli deploy',
571
+ metadata: { command: 'deploy' },
572
+ },
573
+ });
574
+ ```
575
+
576
+ ### Listen for submissions
577
+
578
+ ```js
579
+ // <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
580
+ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
581
+
582
+ ib.on('submit', ({ id }) => {
583
+ analytics.track('feedback_submitted', { feedbackId: id });
584
+ });
585
+
586
+ ib.on('error', (err) => {
587
+ console.error('Feedback submission failed:', err.message);
588
+ });
589
+ ```
590
+
591
+ ### Force a dark theme
592
+
593
+ ```js
594
+ // <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
595
+ InputBufferIO.createModal({
596
+ apiKey: 'YOUR_WIDGET_TOKEN',
597
+ colorScheme: 'dark',
598
+ theme: {
599
+ primary: '#818cf8',
600
+ background: '#1e1e2e',
601
+ text: '#cdd6f4',
602
+ },
603
+ }).open();
604
+ ```
605
+
606
+ ### Add a thumbs bar to a docs page
607
+
608
+ ```html
609
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
610
+
611
+ <inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN" label="Was this helpful?"></inputbuffer-feedback>
612
+ ```
613
+
614
+ ### Load only the bar via npm
615
+
616
+ ```js
617
+ import { createBar } from '@inputbuffer/feedback/bar';
618
+
619
+ const bar = createBar({ apiKey: 'YOUR_WIDGET_TOKEN', label: 'Was this helpful?' });
620
+ document.getElementById('my-slot').appendChild(bar.element);
621
+ ```
622
+
623
+ ### Load your own CSS instead of the injected styles
624
+
625
+ ```html
626
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.css">
627
+
628
+ <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
629
+ data-api-key="YOUR_WIDGET_TOKEN"
630
+ data-inject-styles="false">
631
+ </script>
632
+ ```
633
+
634
+ ---
635
+
636
+ ## License
637
+
638
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { OpenOptions } from './types.js';
2
+ export declare const WIDGET_VERSION: string;
3
+ export declare function submitFeedback(apiKey: string, description: string, email: string | null, title: string | null, options?: OpenOptions, apiUrl?: string): Promise<{
4
+ id: string;
5
+ }>;
@@ -0,0 +1,8 @@
1
+ import type { FeedbackBarConfig, FeedbackBarInstance } from './types.js';
2
+ declare function createBar(config: FeedbackBarConfig): FeedbackBarInstance;
3
+ declare const InputBufferIO: {
4
+ createBar: typeof createBar;
5
+ version: string;
6
+ };
7
+ export type { FeedbackBarConfig, FeedbackBarInstance };
8
+ export { InputBufferIO, createBar };
package/dist/bar.css ADDED
@@ -0,0 +1 @@
1
+ .ib-bar-wrapper{--ib-primary: #6366f1;--ib-primary-hover: #4338ca;--ib-background: #f6f6f8;--ib-surface: #ffffff;--ib-text: #111827;--ib-muted: #8d99ae;--ib-border: var(--ib-primary);--ib-focus-color: var(--ib-primary);--ib-input-hover-border: var(--ib-border);--ib-selected: var(--ib-primary);--ib-selected-color: var(--ib-surface);--ib-radius: 2px;--ib-radius-input: 2px;position:relative;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.ib-bar-wrapper--fixed{position:fixed;bottom:32px;right:32px;z-index:9998}.ib-bar{display:inline-flex;align-items:stretch;height:40px;width:100%;overflow:hidden;background-color:var(--ib-background);border:1px solid var(--ib-border);border-radius:2px;box-shadow:0 1px 4px #00000014;box-sizing:border-box}.ib-bar-popover{display:none;position:absolute;bottom:calc(100% + 8px);right:0;width:320px;border:1px solid var(--ib-border);border-radius:var(--ib-radius);box-shadow:0 1px 3px #0000001f;box-sizing:border-box;z-index:9999;overflow:hidden}.ib-bar-popover--visible{display:block}.ib-bar-header{background:var(--ib-surface);padding:12px 16px;border-bottom:1px solid var(--ib-border)}.ib-bar-body{background:var(--ib-background);padding:16px}.ib-bar-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--ib-text);margin:0}.ib-bar-textarea{width:100%;background:var(--ib-background);border:1px solid var(--ib-border);border-radius:var(--ib-radius-input);padding:10px 12px;font-size:14px;font-family:inherit;color:var(--ib-text);resize:vertical;min-height:80px;box-sizing:border-box;outline:none;display:block}.ib-bar-textarea:hover{border-color:var(--ib-input-hover-border)}.ib-bar-textarea:focus{border-color:var(--ib-focus-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--ib-focus-color) 15%,transparent)}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input,.ib-bar-wrapper .ib-bar-body .ib-bar-email{width:100%;background:var(--ib-background);border:1px solid var(--ib-border);border-radius:var(--ib-radius-input);padding:8px 12px;font-size:14px;font-family:inherit;color:var(--ib-text);box-sizing:border-box;outline:none;display:block}.ib-bar-title-input{margin-bottom:8px}.ib-bar-email{margin-top:8px}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:hover,.ib-bar-wrapper .ib-bar-body .ib-bar-email:hover{border-color:var(--ib-input-hover-border)}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:focus,.ib-bar-wrapper .ib-bar-body .ib-bar-email:focus{border-color:var(--ib-focus-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--ib-focus-color) 15%,transparent)}.ib-bar-submit{background:var(--ib-primary);color:#fff;border:none;border-radius:var(--ib-radius-input);padding:8px 16px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;font-family:inherit;cursor:pointer;display:inline-block;transition:background .15s}.ib-bar-submit:hover{background:var(--ib-primary-hover)}.ib-bar-submit:disabled{opacity:.6;cursor:not-allowed}.ib-bar-footer{display:flex;align-items:center;justify-content:space-between;margin-top:10px}.ib-branding a{font-size:10px;color:var(--ib-muted);text-decoration:none;letter-spacing:.03em}.ib-branding a:hover{color:var(--ib-primary);text-decoration:underline}.ib-bar-error,.ib-bar-success{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;margin:8px 0 0}.ib-bar-error:empty,.ib-bar-success:empty{display:none}.ib-bar-error{color:#dc2626}.ib-bar-success{color:#16a34a}.ib-bar-label-area{flex:1;display:flex;align-items:center;padding:0 16px}.ib-bar-label{font-size:11px;font-weight:600;color:var(--ib-muted);text-transform:uppercase;letter-spacing:.1em;white-space:nowrap;user-select:none}.ib-bar-actions{display:flex;align-items:stretch;background:var(--ib-background);border-left:1px solid var(--ib-border)}.ib-bar-actions--no-label{border-left:none}.ib-bar-btn{width:40px;display:flex;align-items:center;justify-content:center;background:var(--ib-background);border:none;border-right:1px solid var(--ib-border);border-radius:0;color:var(--ib-primary);cursor:pointer;transition:background .15s,color .15s,transform .1s;padding:0;-webkit-appearance:none;appearance:none;box-sizing:border-box;margin:0}.ib-bar-btn--down{border-right:none}.ib-bar-btn:hover{background:var(--ib-primary);color:var(--ib-surface);transform:scale(1.15)}.ib-bar-btn:active{transform:scale(.95)}.ib-bar-btn:focus{outline:1px solid var(--ib-primary);outline-offset:-1px}.ib-bar-btn--active{background:var(--ib-selected);color:var(--ib-selected-color)}.ib-bar-btn--active:hover{background:var(--ib-primary-hover, var(--ib-primary));color:var(--ib-surface);transform:scale(1.1)}@media (prefers-color-scheme: dark){.ib-bar-wrapper:not(.ib-theme-light),.ib-bar-wrapper.ib-theme-dark{--ib-primary: #3A5244;--ib-primary-hover: #4E6857;--ib-background: #25272B;--ib-surface: #2C3630;--ib-text: #E5E7EB;--ib-muted: #9CA3AF;--ib-border: #363840;--ib-focus-color: #7B9B82;--ib-input-hover-border: rgba(255, 255, 255, .2);--ib-selected: #7B9B82;--ib-selected-color: #1a2420;--ib-radius: 8px;--ib-radius-input: 4px}}.ib-bar-wrapper.ib-theme-dark{--ib-primary: #3A5244;--ib-primary-hover: #4E6857;--ib-background: #25272B;--ib-surface: #2C3630;--ib-text: #E5E7EB;--ib-muted: #9CA3AF;--ib-border: #363840;--ib-focus-color: #7B9B82;--ib-input-hover-border: rgba(255, 255, 255, .2);--ib-selected: #7B9B82;--ib-selected-color: #1a2420;--ib-radius: 8px;--ib-radius-input: 4px}