@kelet-ai/feedback-ui 0.2.0 → 0.2.2

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
@@ -11,16 +11,16 @@ npx shadcn add https://feedback-ui.kelet.ai/r/vote-feedback.json
11
11
  ```
12
12
 
13
13
  ```tsx
14
- import {ShadcnVoteFeedback} from '@/components/ui/vote-feedback';
14
+ import { ShadcnVoteFeedback } from '@/components/ui/vote-feedback';
15
15
 
16
16
  function App() {
17
- return (
18
- <ShadcnVoteFeedback
19
- identifier="my-feature"
20
- onFeedback={(feedback) => console.log(feedback)}
21
- variant="outline"
22
- />
23
- );
17
+ return (
18
+ <ShadcnVoteFeedback
19
+ identifier="my-feature"
20
+ onFeedback={feedback => console.log(feedback)}
21
+ variant="outline"
22
+ />
23
+ );
24
24
  }
25
25
  ```
26
26
 
@@ -33,20 +33,45 @@ Perfect for collecting user feedback on:
33
33
  - **Documentation** - Learn what content helps users
34
34
  - **UI components** - Validate design decisions
35
35
 
36
- ## Two Ways to Use
36
+ ### 3. Automatic Feedback with useFeedbackState
37
+
38
+ Capture user behavior implicitly with our intelligent state hook:
39
+
40
+ ```tsx
41
+ import { useFeedbackState } from '@kelet-ai/feedback-ui';
42
+
43
+ function ContentEditor() {
44
+ // Drop-in useState replacement that tracks changes
45
+ const [content, setContent] = useFeedbackState(
46
+ 'Start writing...',
47
+ 'content-editor'
48
+ );
49
+
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
37
62
 
38
63
  ### 1. Start Fast with shadcn/ui (Recommended)
39
64
 
40
65
  Get beautiful, production-ready components instantly. Perfect for most use cases:
41
66
 
42
67
  ```tsx
43
- import {ShadcnVoteFeedback} from '@/components/ui/vote-feedback';
68
+ import { ShadcnVoteFeedback } from '@/components/ui/vote-feedback';
44
69
 
45
70
  <ShadcnVoteFeedback
46
- identifier="my-feature"
47
- onFeedback={handleFeedback}
48
- variant="outline"
49
- />
71
+ identifier="my-feature"
72
+ onFeedback={handleFeedback}
73
+ variant="outline"
74
+ />;
50
75
  ```
51
76
 
52
77
  ### 2. Go Headless for Full Control
@@ -54,20 +79,20 @@ import {ShadcnVoteFeedback} from '@/components/ui/vote-feedback';
54
79
  When you need complete design control, use the headless components:
55
80
 
56
81
  ```tsx
57
- import {VoteFeedback} from '@kelet-ai/feedback-ui';
82
+ import { VoteFeedback } from '@kelet-ai/feedback-ui';
58
83
 
59
84
  <VoteFeedback.Root onFeedback={handleFeedback}>
60
- <VoteFeedback.UpvoteButton className="your-styles">
61
- 👍 Like
62
- </VoteFeedback.UpvoteButton>
63
- <VoteFeedback.DownvoteButton className="your-styles">
64
- 👎 Dislike
65
- </VoteFeedback.DownvoteButton>
66
- <VoteFeedback.Popover className="your-popover">
67
- <VoteFeedback.Textarea placeholder="What could be better?"/>
68
- <VoteFeedback.SubmitButton>Send</VoteFeedback.SubmitButton>
69
- </VoteFeedback.Popover>
70
- </VoteFeedback.Root>
85
+ <VoteFeedback.UpvoteButton className="your-styles">
86
+ 👍 Like
87
+ </VoteFeedback.UpvoteButton>
88
+ <VoteFeedback.DownvoteButton className="your-styles">
89
+ 👎 Dislike
90
+ </VoteFeedback.DownvoteButton>
91
+ <VoteFeedback.Popover className="your-popover">
92
+ <VoteFeedback.Textarea placeholder="What could be better?" />
93
+ <VoteFeedback.SubmitButton>Send</VoteFeedback.SubmitButton>
94
+ </VoteFeedback.Popover>
95
+ </VoteFeedback.Root>;
71
96
  ```
72
97
 
73
98
  ## Features
@@ -76,26 +101,500 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
76
101
  - **TypeScript**: Complete type safety included
77
102
  - **Flexible**: Works with any styling solution
78
103
  - **Zero Dependencies**: Just React - no other dependencies
104
+ - **Automatic Feedback**: `useFeedbackState` hook captures implicit user behavior
79
105
 
80
106
  ## API Reference
81
107
 
82
108
  ### VoteFeedback.Root
83
109
 
84
- | Prop | Type | Required | Description |
85
- |------------------|------------|----------|-------------------------------------|
86
- | `identifier` | `string` | ✓ | Unique identifier for tracking |
87
- | `onFeedback` | `function` | ✓ | Callback when feedback is submitted |
88
- | `extra_metadata` | `object` | | Additional metadata to include |
110
+ | Prop | Type | Required | Description |
111
+ | ---------------- | ---------- | -------- | ----------------------------------------------- |
112
+ | `identifier` | `string` | ✓ | Unique identifier for tracking |
113
+ | `onFeedback` | `function` | ✓ | Callback when feedback is submitted |
114
+ | `extra_metadata` | `object` | | Additional metadata to include |
115
+ | `trigger_name` | `string` | | Optional trigger name for categorizing feedback |
89
116
 
90
117
  ### Feedback Object
91
118
 
92
119
  ```tsx
93
120
  {
94
121
  identifier: string;
95
- type: 'upvote' | 'downvote';
122
+ vote: 'upvote' | 'downvote';
96
123
  explanation ? : string;
97
124
  extra_metadata ? : object;
125
+ source ? : 'IMPLICIT' | 'EXPLICIT';
126
+ correction ? : string;
127
+ selection ? : string;
128
+ trigger_name ? : string; // Optional trigger name for categorizing feedback
129
+ }
130
+ ```
131
+
132
+ ## useFeedbackState Hook
133
+
134
+ The `useFeedbackState` hook is a **drop-in replacement for React's useState** that automatically tracks state changes
135
+ and sends implicit feedback through the Kelet system. Perfect for capturing user behavior without explicit feedback
136
+ prompts.
137
+
138
+ ### Key Features
139
+
140
+ - 🔄 **Drop-in useState replacement** - Same API signature and behavior
141
+ - 🎯 **Automatic diff detection** - Only triggers on actual changes
142
+ - ⏱️ **Smart debouncing** - Prevents feedback spam (default: 1500ms)
143
+ - 📊 **Multiple diff formats** - Git, object, or JSON diff formats
144
+ - 🎭 **Dynamic identifiers** - Can derive identifier from state
145
+ - 🎚️ **Intelligent vote determination** - Automatic upvote/downvote based on change magnitude
146
+ - 🔍 **Custom comparison** - Support for custom equality functions
147
+
148
+ ### Basic Usage
149
+
150
+ ```tsx
151
+ import { useFeedbackState } from '@kelet-ai/feedback-ui';
152
+
153
+ function MyComponent() {
154
+ // Drop-in replacement for useState
155
+ const [count, setCount] = useFeedbackState(0, 'counter-widget');
156
+
157
+ return (
158
+ <div>
159
+ <p>Count: {count}</p>
160
+ <button onClick={() => setCount(count + 1)}>Increment</button>
161
+ </div>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ### Advanced Usage
167
+
168
+ ```tsx
169
+ import { useFeedbackState } from '@kelet-ai/feedback-ui';
170
+
171
+ function UserProfile() {
172
+ const [profile, setProfile] = useFeedbackState(
173
+ { name: '', email: '', preferences: {} },
174
+ state => `profile-${state.email}`, // Dynamic identifier
175
+ {
176
+ debounceMs: 2000, // Wait 2s before sending feedback
177
+ diffType: 'object', // Use object diff format
178
+ metadata: {
179
+ component: 'UserProfile',
180
+ version: '1.2.0',
181
+ },
182
+ compareWith: (a, b) => {
183
+ // Custom comparison function
184
+ return a.name === b.name && a.email === b.email;
185
+ },
186
+ }
187
+ );
188
+
189
+ return (
190
+ <form>
191
+ <input
192
+ value={profile.name}
193
+ onChange={e => setProfile({ ...profile, name: e.target.value })}
194
+ placeholder="Name"
195
+ />
196
+ <input
197
+ value={profile.email}
198
+ onChange={e => setProfile({ ...profile, email: e.target.value })}
199
+ placeholder="Email"
200
+ />
201
+ </form>
202
+ );
203
+ }
204
+ ```
205
+
206
+ ### Trigger Names for State Changes
207
+
208
+ The `useFeedbackState` hook supports **trigger names** to categorize and track different types of state changes. This
209
+ powerful feature allows you to understand the context and cause of state changes in your feedback data.
210
+
211
+ #### Basic Usage with Trigger Names
212
+
213
+ ```tsx
214
+ const [content, setContent] = useFeedbackState(
215
+ 'Initial content',
216
+ 'content-editor',
217
+ {
218
+ default_trigger_name: 'manual_edit', // Default for all changes
219
+ }
220
+ );
221
+
222
+ // Uses default trigger name
223
+ setContent('User typed this');
224
+
225
+ // Override with specific trigger name
226
+ setContent('AI generated this', 'ai_assistance');
227
+ setContent('Spell checker fixed this', 'spell_check');
228
+ ```
229
+
230
+ #### Trigger Switching - Advanced Behavior
231
+
232
+ When you change trigger names, the hook **immediately flushes** the previous sequence and starts a new one:
233
+
234
+ ```tsx
235
+ const [draft, setDraft] = useFeedbackState('', 'email-draft');
236
+
237
+ setDraft('Hello', 'manual_typing'); // Starts debouncing for 'manual_typing'
238
+ setDraft('Hello world', 'manual_typing'); // Extends debounce (same trigger)
239
+ setDraft('Hello world!', 'ai_suggestion'); // IMMEDIATELY sends 'manual_typing' feedback
240
+ // then starts new 'ai_suggestion' sequence
241
+ ```
242
+
243
+ This creates two separate feedback entries:
244
+
245
+ 1. **manual_typing**: `'' → 'Hello world'`
246
+ 2. **ai_suggestion**: `'Hello world' → 'Hello world!'`
247
+
248
+ #### Real-world Examples
249
+
250
+ ```tsx
251
+ // Content creation with different interaction types
252
+ const [article, setArticle] = useFeedbackState(
253
+ { title: '', content: '', tags: [] },
254
+ 'article-editor',
255
+ {
256
+ default_trigger_name: 'user_input',
257
+ debounceMs: 2000,
258
+ }
259
+ );
260
+
261
+ // User typing
262
+ setArticle({ ...article, title: 'My Article' }, 'user_input');
263
+
264
+ // AI assistance
265
+ setArticle(
266
+ {
267
+ ...article,
268
+ content: 'AI generated introduction...',
269
+ },
270
+ 'ai_generation'
271
+ );
272
+
273
+ // Grammar correction
274
+ setArticle(
275
+ {
276
+ ...article,
277
+ content: 'AI-generated introduction...',
278
+ },
279
+ 'grammar_fix'
280
+ );
281
+
282
+ // User refinement
283
+ setArticle(
284
+ {
285
+ ...article,
286
+ content: 'AI-generated introduction with my changes...',
287
+ },
288
+ 'user_refinement'
289
+ );
290
+ ```
291
+
292
+ #### Common Trigger Name Patterns
293
+
294
+ ```tsx
295
+ // Content creation and editing
296
+ {
297
+ default_trigger_name: 'manual_edit';
98
298
  }
299
+ 'user_typing' | 'copy_paste' | 'drag_drop';
300
+
301
+ // AI interactions
302
+ 'ai_generation' | 'ai_completion' | 'ai_correction';
303
+ 'prompt_result' | 'ai_suggestion_accepted';
304
+
305
+ // Automated changes
306
+ 'spell_check' | 'auto_format' | 'auto_save';
307
+ 'validation_fix' | 'import_data';
308
+
309
+ // User interactions
310
+ 'voice_input' | 'gesture_input' | 'shortcut_key';
311
+ 'context_menu' | 'toolbar_action';
312
+
313
+ // Workflow steps
314
+ 'draft' | 'review' | 'approval' | 'publication';
315
+ 'undo' | 'redo' | 'revert';
316
+ ```
317
+
318
+ #### Advanced Pattern: Context-Aware Triggers
319
+
320
+ ```tsx
321
+ function SmartEditor() {
322
+ const [document, setDocument] = useFeedbackState(
323
+ { content: '', metadata: {} },
324
+ 'smart-editor',
325
+ { default_trigger_name: 'user_edit' }
326
+ );
327
+
328
+ const handleUserInput = text => {
329
+ setDocument({ ...document, content: text }, 'user_typing');
330
+ };
331
+
332
+ const handleAIComplete = completion => {
333
+ setDocument(
334
+ {
335
+ ...document,
336
+ content: document.content + completion,
337
+ },
338
+ 'ai_completion'
339
+ );
340
+ };
341
+
342
+ const handleSpellCheck = correctedText => {
343
+ setDocument(
344
+ {
345
+ ...document,
346
+ content: correctedText,
347
+ },
348
+ 'spell_check'
349
+ );
350
+ };
351
+
352
+ const handleImport = importedData => {
353
+ setDocument(importedData, 'data_import');
354
+ };
355
+ }
356
+ ```
357
+
358
+ #### Benefits of Trigger Names
359
+
360
+ 1. **Categorize Feedback** - Group feedback by interaction type
361
+ 2. **Track User Behavior** - Understand how users interact with features
362
+ 3. **Measure AI Impact** - See how often AI suggestions are used vs manual input
363
+ 4. **Debug Issues** - Identify problematic interaction patterns
364
+ 5. **Optimize UX** - Focus improvements on frequently used triggers
365
+
366
+ #### Analytics Insights
367
+
368
+ With trigger names, you can analyze:
369
+
370
+ - **User vs AI ratio**: How much content comes from AI vs manual input?
371
+ - **Correction patterns**: Are users frequently fixing AI suggestions?
372
+ - **Workflow efficiency**: Which triggers lead to the most refinements?
373
+ - **Feature adoption**: Are new features being used as intended?
374
+
375
+ ### API Reference
376
+
377
+ ```tsx
378
+ function useFeedbackState<T>(
379
+ initialState: T,
380
+ identifier: string | ((state: T) => string),
381
+ options?: DiffOptions<T>
382
+ ): [T, (value: SetStateAction<T>, trigger_name?: string) => void];
383
+ ```
384
+
385
+ #### Parameters
386
+
387
+ | Parameter | Type | Required | Description |
388
+ | -------------- | -------------------------------- | -------- | ------------------------------ |
389
+ | `initialState` | `T` | ✓ | Initial state value (any type) |
390
+ | `identifier` | `string \| (state: T) => string` | ✓ | Unique identifier for tracking |
391
+ | `options` | `DiffOptions<T>` | | Configuration options |
392
+
393
+ #### DiffOptions
394
+
395
+ | Option | Type | Default | Description |
396
+ | ---------------------- | --------------------------------------- | --------------------- | ------------------------------------------------- |
397
+ | `debounceMs` | `number` | `1500` | Debounce time in milliseconds |
398
+ | `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Format for diff output |
399
+ | `compareWith` | `(a: T, b: T) => boolean` | | Custom equality comparison function |
400
+ | `metadata` | `Record<string, any>` | | Additional metadata to include |
401
+ | `onFeedback` | `(data: FeedbackData) => Promise<void>` | | Custom feedback handler (for testing) |
402
+ | `vote` | `'upvote' \| 'downvote' \| function` | | Static vote or custom function for determination |
403
+ | `default_trigger_name` | `string` | `'auto_state_change'` | Default trigger name when no trigger is specified |
404
+
405
+ ### How It Works
406
+
407
+ 1. **State Changes**: When state updates, the hook captures the change
408
+ 2. **Debouncing**: Multiple rapid changes extend the timer (user is still editing)
409
+ 3. **Diff Calculation**: Once changes stop, calculates the difference from start to final state
410
+ 4. **Vote Determination**:
411
+ - **≤50% change** = `upvote` (minor refinement)
412
+ - **>50% change** = `downvote` (major revision)
413
+ 5. **Automatic Feedback**: Sends implicit feedback with diff data in the `correction` field
414
+
415
+ ### Diff Formats
416
+
417
+ #### Git Diff (default)
418
+
419
+ ```
420
+ ---
421
+ +++
422
+ @@ -1,1 +1,1 @@
423
+ -{"name": "John"}
424
+ +{"name": "John Doe"}
425
+ ```
426
+
427
+ #### Object Diff
428
+
429
+ ```json
430
+ [
431
+ {
432
+ "kind": "E",
433
+ "path": ["name"],
434
+ "lhs": "John",
435
+ "rhs": "John Doe"
436
+ }
437
+ ]
438
+ ```
439
+
440
+ #### JSON Diff
441
+
442
+ ```json
443
+ {
444
+ "before": {
445
+ "name": "John"
446
+ },
447
+ "after": {
448
+ "name": "John Doe"
449
+ }
450
+ }
451
+ ```
452
+
453
+ ### Use Cases
454
+
455
+ - **Form editing** - Track user input patterns and corrections
456
+ - **Content creation** - Understand how users refine their writing
457
+ - **Configuration changes** - Monitor settings adjustments
458
+ - **Data visualization** - Capture how users explore and filter data
459
+ - **AI interactions** - Learn from user modifications to AI-generated content
460
+
461
+ ### Vote Configuration
462
+
463
+ Control how changes are classified with the `vote` option:
464
+
465
+ #### Static Vote
466
+
467
+ ```tsx
468
+ // Always upvote - useful for content creation tools
469
+ const [content, setContent] = useFeedbackState('Draft...', 'content-editor', {
470
+ vote: 'upvote',
471
+ });
472
+
473
+ // Always downvote - useful for error tracking
474
+ const [errors, setErrors] = useFeedbackState([], 'error-log', {
475
+ vote: 'downvote',
476
+ });
477
+ ```
478
+
479
+ #### Custom Vote Function
480
+
481
+ ```tsx
482
+ // Business logic-based voting
483
+ const [issue, setIssue] = useFeedbackState(
484
+ { priority: 1, severity: 'low' },
485
+ 'issue-tracker',
486
+ {
487
+ vote: (before, after, diffPercentage) => {
488
+ // Priority increase = positive change
489
+ if (after.priority > before.priority) return 'upvote';
490
+ if (after.priority < before.priority) return 'downvote';
491
+
492
+ // Severity increase = negative change
493
+ const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
494
+ const beforeSev = severityOrder[before.severity] || 1;
495
+ const afterSev = severityOrder[after.severity] || 1;
496
+
497
+ // Fall back to diff percentage for other cases
498
+ return afterSev > beforeSev
499
+ ? 'downvote'
500
+ : diffPercentage > 0.7
501
+ ? 'downvote'
502
+ : 'upvote';
503
+ },
504
+ }
505
+ );
506
+ ```
507
+
508
+ #### Advanced Vote Logic Examples
509
+
510
+ ```tsx
511
+ // Content quality assessment
512
+ const [article, setArticle] = useFeedbackState(
513
+ { title: '', content: '', tags: [] },
514
+ 'article-editor',
515
+ {
516
+ vote: (before, after, diffPercentage) => {
517
+ // More tags = improvement
518
+ if (after.tags.length > before.tags.length) return 'upvote';
519
+
520
+ // Significant content expansion = improvement
521
+ const contentGrowth =
522
+ after.content.length / Math.max(before.content.length, 1);
523
+ if (contentGrowth > 1.2) return 'upvote';
524
+
525
+ // Major changes might indicate problems
526
+ return diffPercentage > 0.6 ? 'downvote' : 'upvote';
527
+ },
528
+ }
529
+ );
530
+
531
+ // User experience tracking
532
+ const [settings, setSettings] = useFeedbackState(
533
+ { theme: 'light', notifications: true, autoSave: false },
534
+ 'user-preferences',
535
+ {
536
+ vote: (before, after, diffPercentage) => {
537
+ // Enabling helpful features = positive
538
+ if (after.autoSave && !before.autoSave) return 'upvote';
539
+ if (after.notifications && !before.notifications) return 'upvote';
540
+
541
+ // Disabling features might indicate UX issues
542
+ if (!after.autoSave && before.autoSave) return 'downvote';
543
+
544
+ // Small tweaks are usually positive
545
+ return diffPercentage < 0.3 ? 'upvote' : 'downvote';
546
+ },
547
+ }
548
+ );
549
+ ```
550
+
551
+ ### Best Practices
552
+
553
+ ```tsx
554
+ // ✅ Good: Static identifier for consistent tracking
555
+ const [settings, setSettings] = useFeedbackState(
556
+ defaultSettings,
557
+ 'user-settings'
558
+ );
559
+
560
+ // ✅ Good: Dynamic identifier that makes sense
561
+ const [items, setItems] = useFeedbackState(
562
+ [],
563
+ items => `shopping-cart-${items.length}-items`
564
+ );
565
+
566
+ // ✅ Good: Appropriate debounce for use case
567
+ const [searchQuery, setSearchQuery] = useFeedbackState(
568
+ '',
569
+ 'search-input',
570
+ { debounceMs: 800 } // Shorter for search
571
+ );
572
+
573
+ // ✅ Good: Custom vote logic for domain-specific scenarios
574
+ const [formData, setFormData] = useFeedbackState(initialForm, 'contact-form', {
575
+ vote: (before, after, diffPercentage) => {
576
+ // Form completion = positive signal
577
+ const beforeFields = Object.values(before).filter(Boolean).length;
578
+ const afterFields = Object.values(after).filter(Boolean).length;
579
+ return afterFields > beforeFields ? 'upvote' : 'downvote';
580
+ },
581
+ });
582
+
583
+ // ❌ Avoid: Very short debounce times
584
+ const [text, setText] = useFeedbackState(
585
+ '',
586
+ 'editor',
587
+ { debounceMs: 50 } // Too aggressive
588
+ );
589
+
590
+ // ❌ Avoid: Overly complex vote logic
591
+ const [data, setData] = useFeedbackState({}, 'complex-data', {
592
+ vote: (before, after, diffPercentage) => {
593
+ // Don't make it too complicated - keep it readable!
594
+ // Complex business logic should be in separate functions
595
+ return determineVoteFromBusinessLogic(before, after, diffPercentage);
596
+ },
597
+ });
99
598
  ```
100
599
 
101
600
  ## Examples
@@ -103,9 +602,9 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
103
602
  ### Basic Usage
104
603
 
105
604
  ```tsx
106
- <VoteFeedback.Root onFeedback={(feedback) => console.log(feedback)}>
107
- <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
108
- <VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
605
+ <VoteFeedback.Root onFeedback={feedback => console.log(feedback)}>
606
+ <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
607
+ <VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
109
608
  </VoteFeedback.Root>
110
609
  ```
111
610
 
@@ -113,18 +612,35 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
113
612
 
114
613
  ```tsx
115
614
  <VoteFeedback.Root onFeedback={handleFeedback}>
116
- <VoteFeedback.UpvoteButton className="btn btn-success">
117
- Like
118
- </VoteFeedback.UpvoteButton>
119
- <VoteFeedback.DownvoteButton className="btn btn-danger">
120
- Dislike
121
- </VoteFeedback.DownvoteButton>
122
- <VoteFeedback.Popover className="popover">
123
- <VoteFeedback.Textarea className="textarea"/>
124
- <VoteFeedback.SubmitButton className="btn btn-primary">
125
- Submit
126
- </VoteFeedback.SubmitButton>
127
- </VoteFeedback.Popover>
615
+ <VoteFeedback.UpvoteButton className="btn btn-success">
616
+ Like
617
+ </VoteFeedback.UpvoteButton>
618
+ <VoteFeedback.DownvoteButton className="btn btn-danger">
619
+ Dislike
620
+ </VoteFeedback.DownvoteButton>
621
+ <VoteFeedback.Popover className="popover">
622
+ <VoteFeedback.Textarea className="textarea" />
623
+ <VoteFeedback.SubmitButton className="btn btn-primary">
624
+ Submit
625
+ </VoteFeedback.SubmitButton>
626
+ </VoteFeedback.Popover>
627
+ </VoteFeedback.Root>
628
+ ```
629
+
630
+ ### With Trigger Name
631
+
632
+ ```tsx
633
+ <VoteFeedback.Root
634
+ onFeedback={handleFeedback}
635
+ identifier="ai-response"
636
+ trigger_name="user_feedback"
637
+ >
638
+ <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
639
+ <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
640
+ <VoteFeedback.Popover>
641
+ <VoteFeedback.Textarea placeholder="How can we improve?" />
642
+ <VoteFeedback.SubmitButton>Send feedback</VoteFeedback.SubmitButton>
643
+ </VoteFeedback.Popover>
128
644
  </VoteFeedback.Root>
129
645
  ```
130
646
 
@@ -132,10 +648,10 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
132
648
 
133
649
  ```tsx
134
650
  <VoteFeedback.UpvoteButton asChild>
135
- <button className="custom-button">
136
- <Icon name="thumbs-up"/>
137
- Like
138
- </button>
651
+ <button className="custom-button">
652
+ <Icon name="thumbs-up" />
653
+ Like
654
+ </button>
139
655
  </VoteFeedback.UpvoteButton>
140
656
  ```
141
657
 
@@ -151,7 +667,8 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
151
667
  ```bash
152
668
  bun install
153
669
  bun dev # Start Storybook
154
- bun test # Run tests
670
+ bun run test:unit # Run unit tests
671
+ bun run test:storybook # Run Storybook tests
155
672
  bun build # Build library
156
673
  ```
157
674