@kelet-ai/feedback-ui 0.2.4 → 0.3.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/README.md CHANGED
@@ -1,10 +1,22 @@
1
1
  # Feedback UI
2
2
 
3
- A headless React component to collect feedback for product and AI features.
3
+ <!-- METADATA
4
+ **Project Type**: React Component and Hooks Library
5
+ **Primary Use Case**: User Feedback Collection. Especially great for learning from interactions with AI systems.
6
+ **Framework**: React 19+, TypeScript, Headless UI
7
+ **Installation**: `npm install @kelet-ai/feedback-ui` or with your preferred package manager. If using shadcn/ui, use `npx shadcn add https://feedback-ui.kelet.ai/r/vote-feedback.json` or with your preferred package manager.
8
+ -->
4
9
 
5
- ## Quick Start with shadcn/ui
10
+ > **A headless React component to collect feedback for product and AI features.**
6
11
 
7
- The easiest way to start is by using the shadcn/ui theme for instant, beautiful results:
12
+ Perfect for capturing user reactions to AI-generated content, new features, documentation, and UI components with both
13
+ explicit voting interfaces and implicit behavior tracking.
14
+
15
+ ---
16
+
17
+ ## 🚀 Quick Start
18
+
19
+ Get beautiful feedback components in 30 seconds with shadcn/ui integration:
8
20
 
9
21
  ```bash
10
22
  npx shadcn add https://feedback-ui.kelet.ai/r/vote-feedback.json
@@ -24,590 +36,388 @@ function App() {
24
36
  }
25
37
  ```
26
38
 
27
- ## Why Feedback UI?
39
+ **Result**: Fully styled thumbs up/down buttons with popover feedback form.
28
40
 
29
- Perfect for collecting user feedback on:
41
+ ---
30
42
 
31
- - **Product features** - Get user reactions to new functionality
32
- - **AI responses** - Improve AI models with user feedback
33
- - **Documentation** - Learn what content helps users
34
- - **UI components** - Validate design decisions
43
+ ## Why Feedback UI?
35
44
 
36
- ### 3. Automatic Feedback with useFeedbackState
45
+ ### **Perfect For**
37
46
 
38
- Capture user behavior implicitly with our intelligent state hook:
47
+ - **🤖 AI Response Feedback** - Improve models with user reactions
48
+ - **🆕 Product Feature Validation** - Get user sentiment on new functionality
49
+ - **📚 Documentation Effectiveness** - Learn what content helps users
50
+ - **🎨 UI/UX Component Testing** - Validate design decisions
39
51
 
40
- ```tsx
41
- import { useFeedbackState } from '@kelet-ai/feedback-ui';
52
+ ### **Key Benefits**
42
53
 
43
- function ContentEditor() {
44
- // Drop-in useState replacement that tracks changes
45
- const [content, setContent] = useFeedbackState(
46
- 'Start writing...',
47
- 'content-editor'
48
- );
54
+ - **🎯 Headless Architecture** - Complete design control
55
+ - **♿ Accessibility First** - ARIA support, keyboard navigation, focus management
56
+ - **📊 Implicit Tracking** - Capture user behavior without explicit feedback prompts
57
+ - **🔒 TypeScript Native** - Full type safety included
58
+ - **⚡ Zero Dependencies** - Just React, no other deps
59
+ - **🎨 Flexible Styling** - Works with any CSS framework
49
60
 
50
- return (
51
- <textarea
52
- value={content}
53
- onChange={e => setContent(e.target.value)}
54
- placeholder="Edit this content..."
55
- />
56
- );
57
- // 🎯 Automatically sends feedback when user stops editing!
58
- }
59
- ```
60
-
61
- ## Three Ways to Use
61
+ ---
62
62
 
63
- ### 1. Start Fast with shadcn/ui (Recommended)
63
+ ## 📋 Installation Methods
64
64
 
65
- Get beautiful, production-ready components instantly. Perfect for most use cases:
65
+ ### **Option 1: shadcn/ui (Recommended)**
66
66
 
67
- ```tsx
68
- import { ShadcnVoteFeedback } from '@/components/ui/vote-feedback';
67
+ First [install shadcn/ui](https://ui.shadcn.com/docs/installation) and then:
69
68
 
70
- <ShadcnVoteFeedback
71
- identifier="my-feature"
72
- onFeedback={handleFeedback}
73
- variant="outline"
74
- />;
69
+ ```bash
70
+ npx shadcn add https://feedback-ui.kelet.ai/r/vote-feedback.json
75
71
  ```
76
72
 
77
- ### 2. Go Headless for Full Control
78
-
79
- When you need complete design control, use the headless components.
73
+ **Best for**: Quick setup with beautiful pre-styled components
80
74
 
81
- First, install the package:
75
+ ### **Option 2: NPM Package**
82
76
 
83
- ```console
77
+ ```bash
84
78
  npm install @kelet-ai/feedback-ui
85
79
  ```
86
80
 
87
- Then, import the headless components, and style them as you see fit:
81
+ **Best for**: Full control over styling and behavior
88
82
 
89
- ```tsx
90
- import { VoteFeedback } from '@kelet-ai/feedback-ui';
91
-
92
- <VoteFeedback.Root onFeedback={handleFeedback}>
93
- <VoteFeedback.UpvoteButton className="your-styles">
94
- 👍 Like
95
- </VoteFeedback.UpvoteButton>
96
- <VoteFeedback.DownvoteButton className="your-styles">
97
- 👎 Dislike
98
- </VoteFeedback.DownvoteButton>
99
- <VoteFeedback.Popover className="your-popover">
100
- <VoteFeedback.Textarea placeholder="What could be better?" />
101
- <VoteFeedback.SubmitButton>Send</VoteFeedback.SubmitButton>
102
- </VoteFeedback.Popover>
103
- </VoteFeedback.Root>;
104
- ```
105
-
106
- ## Features
107
-
108
- - **Accessible**: Full keyboard navigation and ARIA support
109
- - **TypeScript**: Complete type safety included
110
- - **Flexible**: Works with any styling solution
111
- - **Zero Dependencies**: Just React - no other dependencies
112
- - **Automatic Feedback**: `useFeedbackState` hook captures implicit user behavior
113
-
114
- ## API Reference
83
+ ---
115
84
 
116
- ### VoteFeedback.Root
85
+ ## 🎯 Usage Patterns
117
86
 
118
- | Prop | Type | Required | Description |
119
- | ---------------- | ---------- | -------- | ----------------------------------------------- |
120
- | `identifier` | `string` | ✓ | Unique identifier for tracking |
121
- | `onFeedback` | `function` | ✓ | Callback when feedback is submitted |
122
- | `extra_metadata` | `object` | | Additional metadata to include |
123
- | `trigger_name` | `string` | | Optional trigger name for categorizing feedback |
87
+ ### **Pattern 1: Explicit Feedback (Most Common)**
124
88
 
125
- ### Feedback Object
89
+ Users explicitly vote and provide comments:
126
90
 
127
91
  ```tsx
128
- {
129
- identifier: string;
130
- vote: 'upvote' | 'downvote';
131
- explanation ? : string;
132
- extra_metadata ? : object;
133
- source ? : 'IMPLICIT' | 'EXPLICIT';
134
- correction ? : string;
135
- selection ? : string;
136
- trigger_name ? : string; // Optional trigger name for categorizing feedback
137
- }
138
- ```
139
-
140
- ## useFeedbackState Hook
141
-
142
- The `useFeedbackState` hook is a **drop-in replacement for React's useState** that automatically tracks state changes
143
- and sends implicit feedback through the Kelet system. Perfect for capturing user behavior without explicit feedback
144
- prompts.
92
+ import { VoteFeedback } from '@kelet-ai/feedback-ui';
145
93
 
146
- ### Key Features
94
+ <VoteFeedback.Root onFeedback={handleFeedback} identifier="ai-response">
95
+ <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
96
+ <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
97
+ <VoteFeedback.Popover>
98
+ <VoteFeedback.Textarea placeholder="How can we improve?" />
99
+ <VoteFeedback.SubmitButton>Send feedback</VoteFeedback.SubmitButton>
100
+ </VoteFeedback.Popover>
101
+ </VoteFeedback.Root>;
102
+ ```
147
103
 
148
- - 🔄 **Drop-in useState replacement** - Same API signature and behavior
149
- - 🎯 **Automatic diff detection** - Only triggers on actual changes
150
- - ⏱️ **Smart debouncing** - Prevents feedback spam (default: 1500ms)
151
- - 📊 **Multiple diff formats** - Git, object, or JSON diff formats
152
- - 🎭 **Dynamic identifiers** - Can derive identifier from state
153
- - 🎚️ **Intelligent vote determination** - Automatic upvote/downvote based on change magnitude
154
- - 🔍 **Custom comparison** - Support for custom equality functions
104
+ ### **Pattern 2: Implicit State Tracking**
155
105
 
156
- ### Basic Usage
106
+ Capture user behavior automatically by using a drop-in replacement for useState:
157
107
 
158
108
  ```tsx
159
109
  import { useFeedbackState } from '@kelet-ai/feedback-ui';
160
110
 
161
- function MyComponent() {
162
- // Drop-in replacement for useState
163
- const [count, setCount] = useFeedbackState(0, 'counter-widget');
111
+ function ContentEditor() {
112
+ // Drop-in useState replacement that tracks changes
113
+ const [content, setContent] = useFeedbackState(
114
+ 'Start writing...',
115
+ 'content-editor'
116
+ );
164
117
 
165
118
  return (
166
- <div>
167
- <p>Count: {count}</p>
168
- <button onClick={() => setCount(count + 1)}>Increment</button>
169
- </div>
119
+ <textarea
120
+ value={content}
121
+ onChange={e => setContent(e.target.value)}
122
+ placeholder="Edit this content..."
123
+ />
170
124
  );
125
+ // 🎯 Automatically sends feedback when user stops editing!
171
126
  }
172
127
  ```
173
128
 
174
- ### Advanced Usage
129
+ ### **Pattern 3: Complex State with Reducer**
130
+
131
+ For advanced state management with automatic trigger tracking, by using a drop-in replacement for useReducer:
175
132
 
176
133
  ```tsx
177
- import { useFeedbackState } from '@kelet-ai/feedback-ui';
134
+ import { useFeedbackReducer } from '@kelet-ai/feedback-ui';
178
135
 
179
- function UserProfile() {
180
- const [profile, setProfile] = useFeedbackState(
181
- { name: '', email: '', preferences: {} },
182
- state => `profile-${state.email}`, // Dynamic identifier
183
- {
184
- debounceMs: 2000, // Wait 2s before sending feedback
185
- diffType: 'object', // Use object diff format
186
- metadata: {
187
- component: 'UserProfile',
188
- version: '1.2.0',
189
- },
190
- compareWith: (a, b) => {
191
- // Custom comparison function
192
- return a.name === b.name && a.email === b.email;
193
- },
194
- }
195
- );
136
+ function TodoApp() {
137
+ const [todos, dispatch] = useFeedbackReducer(todoReducer, [], 'todo-app');
196
138
 
197
139
  return (
198
- <form>
199
- <input
200
- value={profile.name}
201
- onChange={e => setProfile({ ...profile, name: e.target.value })}
202
- placeholder="Name"
203
- />
204
- <input
205
- value={profile.email}
206
- onChange={e => setProfile({ ...profile, email: e.target.value })}
207
- placeholder="Email"
208
- />
209
- </form>
140
+ <button onClick={() => dispatch({ type: 'ADD_TODO', text: 'New task' })}>
141
+ Add Todo
142
+ </button>
143
+ // 🎯 Automatically sends feedback with trigger_name: 'ADD_TODO'
210
144
  );
211
145
  }
212
146
  ```
213
147
 
214
- ### Trigger Names for State Changes
148
+ ---
149
+
150
+ ## 💡 Core Concepts
215
151
 
216
- The `useFeedbackState` hook supports **trigger names** to categorize and track different types of state changes. This
217
- powerful feature allows you to understand the context and cause of state changes in your feedback data.
152
+ Understanding these fundamental concepts will help you implement feedback collection effectively:
218
153
 
219
- #### Basic Usage with Trigger Names
154
+ ### **🔑 Identifiers**
155
+
156
+ **What**: Unique tracking ID that connects feedback to its context
157
+ **Purpose**: Links feedback to specific sessions, users, or content pieces
158
+ **Best Practice**: Use traceable IDs from your logging system (session ID, trace ID, request ID)
220
159
 
221
160
  ```tsx
222
- const [content, setContent] = useFeedbackState(
223
- 'Initial content',
224
- 'content-editor',
225
- {
226
- default_trigger_name: 'manual_edit', // Default for all changes
227
- }
228
- );
161
+ // Good: Traceable identifier
162
+ <VoteFeedback.Root identifier="session-abc123-ai-response-456"/>
229
163
 
230
- // Uses default trigger name
231
- setContent('User typed this');
164
+ // Good: Content-based identifier
165
+ <VoteFeedback.Root identifier={`article-${articleId}-section-${sectionId}`}/>
232
166
 
233
- // Override with specific trigger name
234
- setContent('AI generated this', 'ai_assistance');
235
- setContent('Spell checker fixed this', 'spell_check');
167
+ // Poor: Generic identifier
168
+ <VoteFeedback.Root identifier="feedback"/>
236
169
  ```
237
170
 
238
- #### Trigger Switching - Advanced Behavior
171
+ ### **📊 Feedback Sources**
239
172
 
240
- When you change trigger names, the hook **immediately flushes** the previous sequence and starts a new one:
173
+ Controls how feedback is collected and what data is captured:
241
174
 
242
- ```tsx
243
- const [draft, setDraft] = useFeedbackState('', 'email-draft');
175
+ #### **Explicit Feedback**
244
176
 
245
- setDraft('Hello', 'manual_typing'); // Starts debouncing for 'manual_typing'
246
- setDraft('Hello world', 'manual_typing'); // Extends debounce (same trigger)
247
- setDraft('Hello world!', 'ai_suggestion'); // IMMEDIATELY sends 'manual_typing' feedback
248
- // then starts new 'ai_suggestion' sequence
249
- ```
177
+ - **What**: Users actively vote and provide comments
178
+ - **When to use**: Direct user opinions, satisfaction surveys, feature ratings
179
+ - **Data captured**: Vote (upvote/downvote), comments, metadata
180
+ - **User experience**: Interactive buttons and forms
250
181
 
251
- This creates two separate feedback entries:
182
+ ```tsx
183
+ // Explicit feedback - user clicks buttons
184
+ <VoteFeedback.Root onFeedback={handleExplicitFeedback}>
185
+ <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
186
+ <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
187
+ </VoteFeedback.Root>
188
+ ```
252
189
 
253
- 1. **manual_typing**: `'' → 'Hello world'`
254
- 2. **ai_suggestion**: `'Hello world' → 'Hello world!'`
190
+ #### **Implicit Feedback**
255
191
 
256
- #### Real-world Examples
192
+ - **What**: Automatic behavior tracking without user prompts
193
+ - **When to use**: Content editing, user interactions, workflow analysis
194
+ - **Data captured**: State diffs, interaction patterns, timing data
195
+ - **User experience**: Seamless, no interruption
257
196
 
258
197
  ```tsx
259
- // Content creation with different interaction types
260
- const [article, setArticle] = useFeedbackState(
261
- { title: '', content: '', tags: [] },
262
- 'article-editor',
263
- {
264
- default_trigger_name: 'user_input',
265
- debounceMs: 2000,
266
- }
198
+ // Implicit feedback - tracks changes automatically
199
+ const [content, setContent] = useFeedbackState(
200
+ 'Initial content',
201
+ 'content-editor'
267
202
  );
203
+ // Sends feedback when user stops editing
204
+ ```
268
205
 
269
- // User typing
270
- setArticle({ ...article, title: 'My Article' }, 'user_input');
206
+ ### **🏷️ Trigger Names**
271
207
 
272
- // AI assistance
273
- setArticle(
274
- {
275
- ...article,
276
- content: 'AI generated introduction...',
277
- },
278
- 'ai_generation'
279
- );
208
+ Categorization system for grouping and analyzing feedback:
280
209
 
281
- // Grammar correction
282
- setArticle(
283
- {
284
- ...article,
285
- content: 'AI-generated introduction...',
286
- },
287
- 'grammar_fix'
288
- );
210
+ #### **Purpose**
289
211
 
290
- // User refinement
291
- setArticle(
292
- {
293
- ...article,
294
- content: 'AI-generated introduction with my changes...',
295
- },
296
- 'user_refinement'
297
- );
298
- ```
212
+ - **Group related feedback** for analysis
213
+ - **Track interaction types** (manual vs AI-assisted)
214
+ - **Measure feature adoption** and usage patterns
215
+ - **Debug user experience** issues
299
216
 
300
- #### Common Trigger Name Patterns
217
+ #### **Common Patterns**
301
218
 
302
219
  ```tsx
303
- // Content creation and editing
304
- {
305
- default_trigger_name: 'manual_edit';
306
- }
307
- 'user_typing' | 'copy_paste' | 'drag_drop';
308
-
309
- // AI interactions
310
- 'ai_generation' | 'ai_completion' | 'ai_correction';
311
- 'prompt_result' | 'ai_suggestion_accepted';
220
+ // Content creation triggers
221
+ 'user_typing' | 'ai_generation' | 'spell_check' | 'auto_format';
312
222
 
313
- // Automated changes
314
- 'spell_check' | 'auto_format' | 'auto_save';
315
- 'validation_fix' | 'import_data';
223
+ // User interaction triggers
224
+ 'manual_edit' | 'voice_input' | 'copy_paste' | 'drag_drop';
316
225
 
317
- // User interactions
318
- 'voice_input' | 'gesture_input' | 'shortcut_key';
319
- 'context_menu' | 'toolbar_action';
320
-
321
- // Workflow steps
226
+ // Workflow triggers
322
227
  'draft' | 'review' | 'approval' | 'publication';
323
- 'undo' | 'redo' | 'revert';
228
+
229
+ // AI interaction triggers
230
+ 'ai_completion' | 'ai_correction' | 'prompt_result';
324
231
  ```
325
232
 
326
- #### Advanced Pattern: Context-Aware Triggers
233
+ #### **Dynamic Trigger Names**
327
234
 
328
235
  ```tsx
329
- function SmartEditor() {
330
- const [document, setDocument] = useFeedbackState(
331
- { content: '', metadata: {} },
332
- 'smart-editor',
333
- { default_trigger_name: 'user_edit' }
334
- );
236
+ const [document, setDocument] = useFeedbackState(
237
+ initialDoc,
238
+ 'document-editor',
239
+ { default_trigger_name: 'user_edit' }
240
+ );
335
241
 
336
- const handleUserInput = text => {
337
- setDocument({ ...document, content: text }, 'user_typing');
338
- };
339
-
340
- const handleAIComplete = completion => {
341
- setDocument(
342
- {
343
- ...document,
344
- content: document.content + completion,
345
- },
346
- 'ai_completion'
347
- );
348
- };
349
-
350
- const handleSpellCheck = correctedText => {
351
- setDocument(
352
- {
353
- ...document,
354
- content: correctedText,
355
- },
356
- 'spell_check'
357
- );
358
- };
359
-
360
- const handleImport = importedData => {
361
- setDocument(importedData, 'data_import');
362
- };
363
- }
242
+ // Different triggers for different actions
243
+ setDocument(aiGeneratedContent, 'ai_generation');
244
+ setDocument(userEditedContent, 'manual_refinement');
245
+ setDocument(spellCheckedContent, 'spell_check');
364
246
  ```
365
247
 
366
- #### Benefits of Trigger Names
248
+ ### **🔄 State Change Tracking**
249
+
250
+ For implicit feedback, understanding how state changes are processed:
251
+
252
+ #### **Debouncing Logic**
253
+
254
+ ```tsx
255
+ // User types: "Hello" → "Hello World" → "Hello World!"
256
+ // Only sends ONE feedback after user stops typing
257
+ const [text, setText] = useFeedbackState('', 'editor', {
258
+ debounceMs: 1500, // Wait 1.5s after last change
259
+ });
260
+ ```
367
261
 
368
- 1. **Categorize Feedback** - Group feedback by interaction type
369
- 2. **Track User Behavior** - Understand how users interact with features
370
- 3. **Measure AI Impact** - See how often AI suggestions are used vs manual input
371
- 4. **Debug Issues** - Identify problematic interaction patterns
372
- 5. **Optimize UX** - Focus improvements on frequently used triggers
262
+ #### **Diff Calculation**
373
263
 
374
- #### Analytics Insights
264
+ Three formats available for different use cases:
375
265
 
376
- With trigger names, you can analyze:
266
+ | Format | Best For | Output |
267
+ | -------- | ------------------- | ------------------- |
268
+ | `git` | Code/text changes | Unified diff format |
269
+ | `object` | Structured data | Deep object diff |
270
+ | `json` | Simple before/after | JSON comparison |
377
271
 
378
- - **User vs AI ratio**: How much content comes from AI vs manual input?
379
- - **Correction patterns**: Are users frequently fixing AI suggestions?
380
- - **Workflow efficiency**: Which triggers lead to the most refinements?
381
- - **Feature adoption**: Are new features being used as intended?
272
+ #### **Vote Determination**
382
273
 
383
- ### API Reference
274
+ Automatic classification of changes:
384
275
 
385
276
  ```tsx
386
- function useFeedbackState<T>(
387
- initialState: T,
388
- identifier: string | ((state: T) => string),
389
- options?: DiffOptions<T>
390
- ): [T, (value: SetStateAction<T>, trigger_name?: string) => void];
277
+ // Smart vote logic based on change magnitude
278
+ const [data, setData] = useFeedbackState(initial, 'tracker', {
279
+ vote: (before, after, diffPercentage) => {
280
+ // Small changes = refinement (positive)
281
+ if (diffPercentage <= 0.5) return 'upvote';
282
+ // Large changes = major revision (might indicate issues)
283
+ return 'downvote';
284
+ },
285
+ });
391
286
  ```
392
287
 
393
- #### Parameters
288
+ ### **🎯 Best Practices Summary**
394
289
 
395
- | Parameter | Type | Required | Description |
396
- | -------------- | -------------------------------- | -------- | ------------------------------ |
397
- | `initialState` | `T` | ✓ | Initial state value (any type) |
398
- | `identifier` | `string \| (state: T) => string` | ✓ | Unique identifier for tracking |
399
- | `options` | `DiffOptions<T>` | | Configuration options |
290
+ #### **Identifiers**
400
291
 
401
- #### DiffOptions
292
+ Use traceable session/request IDs
293
+ ✅ Include context in identifier structure
294
+ ✅ Keep identifiers consistent across related actions
402
295
 
403
- | Option | Type | Default | Description |
404
- | ---------------------- | --------------------------------------- | --------------------- | ------------------------------------------------- |
405
- | `debounceMs` | `number` | `1500` | Debounce time in milliseconds |
406
- | `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Format for diff output |
407
- | `compareWith` | `(a: T, b: T) => boolean` | | Custom equality comparison function |
408
- | `metadata` | `Record<string, any>` | | Additional metadata to include |
409
- | `onFeedback` | `(data: FeedbackData) => Promise<void>` | | Custom feedback handler (for testing) |
410
- | `vote` | `'upvote' \| 'downvote' \| function` | | Static vote or custom function for determination |
411
- | `default_trigger_name` | `string` | `'auto_state_change'` | Default trigger name when no trigger is specified |
296
+ #### **Feedback Sources**
412
297
 
413
- ### How It Works
298
+ Use explicit feedback for user opinions
299
+ ✅ Use implicit feedback for behavior analysis
300
+ ✅ Combine both for comprehensive insights
414
301
 
415
- 1. **State Changes**: When state updates, the hook captures the change
416
- 2. **Debouncing**: Multiple rapid changes extend the timer (user is still editing)
417
- 3. **Diff Calculation**: Once changes stop, calculates the difference from start to final state
418
- 4. **Vote Determination**:
419
- - **≤50% change** = `upvote` (minor refinement)
420
- - **>50% change** = `downvote` (major revision)
421
- 5. **Automatic Feedback**: Sends implicit feedback with diff data in the `correction` field
302
+ #### **Trigger Names**
422
303
 
423
- ### Diff Formats
304
+ Use consistent naming conventions
305
+ ✅ Group related triggers with prefixes
306
+ ✅ Document trigger meanings for your team
424
307
 
425
- #### Git Diff (default)
308
+ #### **Integration**
309
+
310
+ ✅ Handle feedback data asynchronously
311
+ ✅ Include relevant metadata for context
312
+ ✅ Test both explicit and implicit flows
426
313
 
427
- ```
428
314
  ---
429
- +++
430
- @@ -1,1 +1,1 @@
431
- -{"name": "John"}
432
- +{"name": "John Doe"}
433
- ```
434
315
 
435
- #### Object Diff
316
+ ## 🔧 Core Components
436
317
 
437
- ```json
438
- [
439
- {
440
- "kind": "E",
441
- "path": ["name"],
442
- "lhs": "John",
443
- "rhs": "John Doe"
444
- }
445
- ]
446
- ```
318
+ ### **VoteFeedback.Root**
447
319
 
448
- #### JSON Diff
320
+ Main container component that manages feedback state.
449
321
 
450
- ```json
451
- {
452
- "before": {
453
- "name": "John"
454
- },
455
- "after": {
456
- "name": "John Doe"
457
- }
458
- }
322
+ ```tsx
323
+ <VoteFeedback.Root
324
+ identifier="unique-id" // Required: Unique tracking ID
325
+ onFeedback={handleFeedback} // Required: Callback function
326
+ trigger_name="user_feedback" // Optional: Categorization
327
+ extra_metadata={{ page: 'home' }} // Optional: Additional data
328
+ >
329
+ {/* Child components */}
330
+ </VoteFeedback.Root>
459
331
  ```
460
332
 
461
- ### Use Cases
333
+ ### **VoteFeedback.UpvoteButton / DownvoteButton**
462
334
 
463
- - **Form editing** - Track user input patterns and corrections
464
- - **Content creation** - Understand how users refine their writing
465
- - **Configuration changes** - Monitor settings adjustments
466
- - **Data visualization** - Capture how users explore and filter data
467
- - **AI interactions** - Learn from user modifications to AI-generated content
335
+ Interactive voting buttons with built-in state management.
468
336
 
469
- ### Vote Configuration
337
+ ```tsx
338
+ <VoteFeedback.UpvoteButton className="your-styles">
339
+ 👍 Like
340
+ </VoteFeedback.UpvoteButton>
341
+ ```
470
342
 
471
- Control how changes are classified with the `vote` option:
343
+ ### **VoteFeedback.Popover**
472
344
 
473
- #### Static Vote
345
+ Context-aware feedback form that appears after voting.
474
346
 
475
347
  ```tsx
476
- // Always upvote - useful for content creation tools
477
- const [content, setContent] = useFeedbackState('Draft...', 'content-editor', {
478
- vote: 'upvote',
479
- });
480
-
481
- // Always downvote - useful for error tracking
482
- const [errors, setErrors] = useFeedbackState([], 'error-log', {
483
- vote: 'downvote',
484
- });
348
+ <VoteFeedback.Popover className="your-popover-styles">
349
+ <VoteFeedback.Textarea placeholder="Tell us more..." />
350
+ <VoteFeedback.SubmitButton>Send</VoteFeedback.SubmitButton>
351
+ </VoteFeedback.Popover>
485
352
  ```
486
353
 
487
- #### Custom Vote Function
354
+ ---
355
+
356
+ ## 📊 Automatic Feedback Hooks
357
+
358
+ ### **useFeedbackState Hook**
359
+
360
+ A **drop-in replacement for React's useState** that automatically tracks state changes.
361
+
362
+ #### **Basic Usage**
488
363
 
489
364
  ```tsx
490
- // Business logic-based voting
491
- const [issue, setIssue] = useFeedbackState(
492
- { priority: 1, severity: 'low' },
493
- 'issue-tracker',
494
- {
495
- vote: (before, after, diffPercentage) => {
496
- // Priority increase = positive change
497
- if (after.priority > before.priority) return 'upvote';
498
- if (after.priority < before.priority) return 'downvote';
499
-
500
- // Severity increase = negative change
501
- const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
502
- const beforeSev = severityOrder[before.severity] || 1;
503
- const afterSev = severityOrder[after.severity] || 1;
504
-
505
- // Fall back to diff percentage for other cases
506
- return afterSev > beforeSev
507
- ? 'downvote'
508
- : diffPercentage > 0.7
509
- ? 'downvote'
510
- : 'upvote';
511
- },
512
- }
513
- );
365
+ const [count, setCount] = useFeedbackState(0, 'counter-widget');
514
366
  ```
515
367
 
516
- #### Advanced Vote Logic Examples
368
+ #### **Advanced Configuration**
517
369
 
518
370
  ```tsx
519
- // Content quality assessment
520
- const [article, setArticle] = useFeedbackState(
521
- { title: '', content: '', tags: [] },
522
- 'article-editor',
523
- {
524
- vote: (before, after, diffPercentage) => {
525
- // More tags = improvement
526
- if (after.tags.length > before.tags.length) return 'upvote';
527
-
528
- // Significant content expansion = improvement
529
- const contentGrowth =
530
- after.content.length / Math.max(before.content.length, 1);
531
- if (contentGrowth > 1.2) return 'upvote';
532
-
533
- // Major changes might indicate problems
534
- return diffPercentage > 0.6 ? 'downvote' : 'upvote';
535
- },
536
- }
537
- );
538
-
539
- // User experience tracking
540
- const [settings, setSettings] = useFeedbackState(
541
- { theme: 'light', notifications: true, autoSave: false },
542
- 'user-preferences',
371
+ const [profile, setProfile] = useFeedbackState(
372
+ { name: '', email: '' },
373
+ state => `profile-${state.email}`, // Dynamic identifier
543
374
  {
544
- vote: (before, after, diffPercentage) => {
545
- // Enabling helpful features = positive
546
- if (after.autoSave && !before.autoSave) return 'upvote';
547
- if (after.notifications && !before.notifications) return 'upvote';
548
-
549
- // Disabling features might indicate UX issues
550
- if (!after.autoSave && before.autoSave) return 'downvote';
551
-
552
- // Small tweaks are usually positive
553
- return diffPercentage < 0.3 ? 'upvote' : 'downvote';
554
- },
375
+ debounceMs: 2000, // Wait time before sending feedback
376
+ diffType: 'object', // Format: 'git' | 'object' | 'json'
377
+ metadata: { component: 'UserProfile' },
378
+ vote: 'upvote', // Static vote or custom function
555
379
  }
556
380
  );
557
381
  ```
558
382
 
559
- ### Best Practices
383
+ #### **Trigger Names for Categorization**
560
384
 
561
385
  ```tsx
562
- // Good: Static identifier for consistent tracking
563
- const [settings, setSettings] = useFeedbackState(
564
- defaultSettings,
565
- 'user-settings'
386
+ const [content, setContent] = useFeedbackState(
387
+ 'Initial content',
388
+ 'content-editor',
389
+ { default_trigger_name: 'manual_edit' }
566
390
  );
567
391
 
568
- // Good: Dynamic identifier that makes sense
569
- const [items, setItems] = useFeedbackState(
570
- [],
571
- items => `shopping-cart-${items.length}-items`
572
- );
392
+ // Uses default trigger
393
+ setContent('User typed this');
573
394
 
574
- // Good: Appropriate debounce for use case
575
- const [searchQuery, setSearchQuery] = useFeedbackState(
576
- '',
577
- 'search-input',
578
- { debounceMs: 800 } // Shorter for search
579
- );
395
+ // Override with specific trigger
396
+ setContent('AI generated this', 'ai_assistance');
397
+ setContent('Spell checker fixed this', 'spell_check');
398
+ ```
580
399
 
581
- // Good: Custom vote logic for domain-specific scenarios
582
- const [formData, setFormData] = useFeedbackState(initialForm, 'contact-form', {
583
- vote: (before, after, diffPercentage) => {
584
- // Form completion = positive signal
585
- const beforeFields = Object.values(before).filter(Boolean).length;
586
- const afterFields = Object.values(after).filter(Boolean).length;
587
- return afterFields > beforeFields ? 'upvote' : 'downvote';
588
- },
589
- });
400
+ ### **useFeedbackReducer Hook**
590
401
 
591
- // Avoid: Very short debounce times
592
- const [text, setText] = useFeedbackState(
593
- '',
594
- 'editor',
595
- { debounceMs: 50 } // Too aggressive
402
+ A **drop-in replacement for React's useReducer** with automatic trigger name extraction from action types.
403
+
404
+ ```tsx
405
+ const [state, dispatch] = useFeedbackReducer(
406
+ counterReducer,
407
+ { count: 0 },
408
+ 'counter-widget'
596
409
  );
597
410
 
598
- // ❌ Avoid: Overly complex vote logic
599
- const [data, setData] = useFeedbackState({}, 'complex-data', {
600
- vote: (before, after, diffPercentage) => {
601
- // Don't make it too complicated - keep it readable!
602
- // Complex business logic should be in separate functions
603
- return determineVoteFromBusinessLogic(before, after, diffPercentage);
604
- },
605
- });
411
+ dispatch({ type: 'increment' }); // trigger_name: 'increment'
412
+ dispatch({ type: 'reset' }); // trigger_name: 'reset'
413
+ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
606
414
  ```
607
415
 
608
- ## Examples
416
+ ---
417
+
418
+ ## 🎨 Examples
609
419
 
610
- ### Basic Usage
420
+ ### **Basic Voting Interface**
611
421
 
612
422
  ```tsx
613
423
  <VoteFeedback.Root onFeedback={feedback => console.log(feedback)}>
@@ -616,7 +426,7 @@ const [data, setData] = useFeedbackState({}, 'complex-data', {
616
426
  </VoteFeedback.Root>
617
427
  ```
618
428
 
619
- ### With Custom Styling
429
+ ### **Styled with Custom CSS**
620
430
 
621
431
  ```tsx
622
432
  <VoteFeedback.Root onFeedback={handleFeedback}>
@@ -635,51 +445,210 @@ const [data, setData] = useFeedbackState({}, 'complex-data', {
635
445
  </VoteFeedback.Root>
636
446
  ```
637
447
 
638
- ### With Trigger Name
448
+ ### **Polymorphic Components with asChild**
449
+
450
+ ```tsx
451
+ <VoteFeedback.UpvoteButton asChild>
452
+ <button className="custom-button">
453
+ <Icon name="thumbs-up" />
454
+ Like
455
+ </button>
456
+ </VoteFeedback.UpvoteButton>
457
+ ```
458
+
459
+ ### **AI Response Feedback**
639
460
 
640
461
  ```tsx
641
462
  <VoteFeedback.Root
463
+ identifier="ai-response-123"
642
464
  onFeedback={handleFeedback}
643
- identifier="ai-response"
644
- trigger_name="user_feedback"
465
+ trigger_name="ai_evaluation"
466
+ extra_metadata={{
467
+ model: 'gpt-4',
468
+ prompt_length: 150,
469
+ response_time: 1200,
470
+ }}
645
471
  >
646
472
  <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
647
473
  <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
648
474
  <VoteFeedback.Popover>
649
- <VoteFeedback.Textarea placeholder="How can we improve?" />
475
+ <VoteFeedback.Textarea placeholder="How can we improve this response?" />
650
476
  <VoteFeedback.SubmitButton>Send feedback</VoteFeedback.SubmitButton>
651
477
  </VoteFeedback.Popover>
652
478
  </VoteFeedback.Root>
653
479
  ```
654
480
 
655
- ### Using asChild Pattern
481
+ ---
482
+
483
+ ## 📖 API Reference
484
+
485
+ ### **Feedback Data Object**
486
+
487
+ ```typescript
488
+ interface FeedbackData {
489
+ identifier: string; // Unique tracking ID
490
+ vote: 'upvote' | 'downvote'; // User's vote
491
+ explanation?: string; // Optional user comment
492
+ extra_metadata?: Record<string, any>; // Additional context data
493
+ source?: 'IMPLICIT' | 'EXPLICIT'; // How feedback was collected
494
+ correction?: string; // For implicit feedback diffs
495
+ selection?: string; // Selected text context
496
+ trigger_name?: string; // Categorization tag
497
+ }
498
+ ```
499
+
500
+ ### **VoteFeedback.Root Props**
501
+
502
+ | Prop | Type | Required | Description |
503
+ | ---------------- | ------------------------------ | -------- | ----------------------------------- |
504
+ | `identifier` | `string` | ✅ | Unique identifier for tracking |
505
+ | `onFeedback` | `(data: FeedbackData) => void` | ✅ | Callback when feedback is submitted |
506
+ | `trigger_name` | `string` | ❌ | Optional categorization tag |
507
+ | `extra_metadata` | `object` | ❌ | Additional context data |
508
+
509
+ ### **Hook Options**
510
+
511
+ #### **useFeedbackState Options**
512
+
513
+ | Option | Type | Default | Description |
514
+ | ---------------------- | ------------------------------------ | --------------------- | ----------------------------- |
515
+ | `debounceMs` | `number` | `1500` | Debounce time in milliseconds |
516
+ | `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Diff output format |
517
+ | `compareWith` | `(a: T, b: T) => boolean` | `undefined` | Custom equality function |
518
+ | `metadata` | `Record<string, any>` | `{}` | Additional metadata |
519
+ | `vote` | `'upvote' \| 'downvote' \| function` | `auto` | Vote determination logic |
520
+ | `default_trigger_name` | `string` | `'auto_state_change'` | Default trigger name |
521
+
522
+ ---
523
+
524
+ ## ♿ Accessibility
525
+
526
+ ✅ **Full keyboard navigation support**
527
+ ✅ **ARIA labels and roles**
528
+ ✅ **Focus management**
529
+ ✅ **Screen reader compatible**
530
+ ✅ **High contrast support**
531
+ ✅ **Reduced motion respect**
532
+
533
+ ### **Keyboard Shortcuts**
534
+
535
+ - `Tab` / `Shift+Tab` - Navigate between elements
536
+ - `Enter` / `Space` - Activate buttons
537
+ - `Escape` - Close popover
538
+ - `Arrow Keys` - Navigate within popover
539
+
540
+ ---
541
+
542
+ ## 🛠 Development
543
+
544
+ ### **Setup**
545
+
546
+ ```bash
547
+ bun install # Install dependencies
548
+ bun dev # Start Storybook development server
549
+ bun run checks # Run all quality checks
550
+ ```
551
+
552
+ ### **Testing**
553
+
554
+ ```bash
555
+ bun run test:unit # Run unit tests
556
+ bun run test:storybook # Run Storybook interaction tests
557
+ bun run test:storybook:coverage # Run with coverage
558
+ ```
559
+
560
+ ### **Building**
561
+
562
+ ```bash
563
+ bun build # Build library for production
564
+ bun run typecheck # TypeScript type checking
565
+ bun run lint # ESLint code quality checks
566
+ bun run prettier # Code formatting
567
+ ```
568
+
569
+ ### **Quality Checks**
570
+
571
+ ```bash
572
+ bun run checks # Run all quality checks (lint, format, typecheck, tests)
573
+ ```
574
+
575
+ ---
576
+
577
+ ## ❓ Troubleshooting
578
+
579
+ ### **Common Issues**
580
+
581
+ #### **Q: Feedback not triggering**
582
+
583
+ ```tsx
584
+ // ❌ Missing required props
585
+ <VoteFeedback.Root>
586
+ <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
587
+ </VoteFeedback.Root>
588
+
589
+ // ✅ Include required identifier and onFeedback
590
+ <VoteFeedback.Root
591
+ identifier="my-feature"
592
+ onFeedback={handleFeedback}
593
+ >
594
+ <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
595
+ </VoteFeedback.Root>
596
+ ```
597
+
598
+ #### **Q: TypeScript errors with custom components**
656
599
 
657
600
  ```tsx
601
+ // ✅ Use asChild for custom components
658
602
  <VoteFeedback.UpvoteButton asChild>
659
- <button className="custom-button">
660
- <Icon name="thumbs-up" />
661
- Like
662
- </button>
603
+ <MyCustomButton>Like</MyCustomButton>
663
604
  </VoteFeedback.UpvoteButton>
664
605
  ```
665
606
 
666
- ## Accessibility
607
+ #### **Q: useFeedbackState not sending feedback**
667
608
 
668
- - Full keyboard navigation support
669
- - ARIA labels and roles
670
- - Focus management
671
- - Screen reader compatible
609
+ ```tsx
610
+ // Ensure debounce time has passed(the time, not the configuration! :P) and state actually changed
611
+ const [value, setValue] = useFeedbackState('initial', 'test', {
612
+ debounceMs: 1000, // Wait 1 second after last change
613
+ });
614
+ ```
672
615
 
673
- ## Development
616
+ #### **Q: Styling not working**
674
617
 
675
- ```bash
676
- bun install
677
- bun dev # Start Storybook
678
- bun run test:unit # Run unit tests
679
- bun run test:storybook # Run Storybook tests
680
- bun build # Build library
618
+ ```tsx
619
+ // ✅ Components are unstyled by default - add your own CSS or use the Shadcn UI library
620
+ <VoteFeedback.UpvoteButton className="bg-green-500 text-white p-2">
621
+ 👍 Like
622
+ </VoteFeedback.UpvoteButton>
681
623
  ```
682
624
 
625
+ ### **Best Practices**
626
+
627
+ ✅ **Use unique identifiers** for each feedback instance - the identifier should be traceable back to the session's log
628
+ and allow us to understand the context of the feedback.
629
+ ✅ **Handle feedback data asynchronously** in your callback
630
+ ✅ **Test keyboard navigation** in your implementation
631
+ ✅ **Provide meaningful trigger names** for categorization
632
+ ✅ **Include relevant metadata** for context
633
+
634
+ ❌ **Don't use the same identifier** for multiple components. An identifier should be traced back to the session's log -
635
+ allows us to understand the context of the feedback.
636
+
637
+ ---
638
+
639
+ ## 📚 Additional Resources
640
+
641
+ **📖 Full Documentation**: [https://feedback-ui.kelet.ai/](https://feedback-ui.kelet.ai/)
642
+ **🎮 Interactive Examples**: [Storybook Documentation](https://feedback-ui.kelet.ai/storybook/)
643
+ **🐛 Report Issues**: [GitHub Issues](https://github.com/kelet-ai/feedback-ui/issues)
644
+ **💬 Discussions**: [GitHub Discussions](https://github.com/kelet-ai/feedback-ui/discussions)
645
+
646
+ ---
647
+
683
648
  ## License
684
649
 
685
- MIT
650
+ **MIT License** - See [LICENSE](LICENSE) file for details.
651
+
652
+ ---
653
+
654
+ _Built with ❤️ by the [Kelet AI](https://kelet.ai) team_