@kelet-ai/feedback-ui 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,41 +33,74 @@ 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
53
78
 
54
- When you need complete design control, use the headless components:
79
+ When you need complete design control, use the headless components.
80
+
81
+ First, install the package:
82
+
83
+ ```console
84
+ npm install @kelet-ai/feedback-ui
85
+ ```
86
+
87
+ Then, import the headless components, and style them as you see fit:
55
88
 
56
89
  ```tsx
57
- import {VoteFeedback} from '@kelet-ai/feedback-ui';
90
+ import { VoteFeedback } from '@kelet-ai/feedback-ui';
58
91
 
59
92
  <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>
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>;
71
104
  ```
72
105
 
73
106
  ## Features
@@ -76,26 +109,500 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
76
109
  - **TypeScript**: Complete type safety included
77
110
  - **Flexible**: Works with any styling solution
78
111
  - **Zero Dependencies**: Just React - no other dependencies
112
+ - **Automatic Feedback**: `useFeedbackState` hook captures implicit user behavior
79
113
 
80
114
  ## API Reference
81
115
 
82
116
  ### VoteFeedback.Root
83
117
 
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 |
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 |
89
124
 
90
125
  ### Feedback Object
91
126
 
92
127
  ```tsx
93
128
  {
94
129
  identifier: string;
95
- type: 'upvote' | 'downvote';
130
+ vote: 'upvote' | 'downvote';
96
131
  explanation ? : string;
97
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.
145
+
146
+ ### Key Features
147
+
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
155
+
156
+ ### Basic Usage
157
+
158
+ ```tsx
159
+ import { useFeedbackState } from '@kelet-ai/feedback-ui';
160
+
161
+ function MyComponent() {
162
+ // Drop-in replacement for useState
163
+ const [count, setCount] = useFeedbackState(0, 'counter-widget');
164
+
165
+ return (
166
+ <div>
167
+ <p>Count: {count}</p>
168
+ <button onClick={() => setCount(count + 1)}>Increment</button>
169
+ </div>
170
+ );
171
+ }
172
+ ```
173
+
174
+ ### Advanced Usage
175
+
176
+ ```tsx
177
+ import { useFeedbackState } from '@kelet-ai/feedback-ui';
178
+
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
+ );
196
+
197
+ 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>
210
+ );
211
+ }
212
+ ```
213
+
214
+ ### Trigger Names for State Changes
215
+
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.
218
+
219
+ #### Basic Usage with Trigger Names
220
+
221
+ ```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
+ );
229
+
230
+ // Uses default trigger name
231
+ setContent('User typed this');
232
+
233
+ // Override with specific trigger name
234
+ setContent('AI generated this', 'ai_assistance');
235
+ setContent('Spell checker fixed this', 'spell_check');
236
+ ```
237
+
238
+ #### Trigger Switching - Advanced Behavior
239
+
240
+ When you change trigger names, the hook **immediately flushes** the previous sequence and starts a new one:
241
+
242
+ ```tsx
243
+ const [draft, setDraft] = useFeedbackState('', 'email-draft');
244
+
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
+ ```
250
+
251
+ This creates two separate feedback entries:
252
+
253
+ 1. **manual_typing**: `'' → 'Hello world'`
254
+ 2. **ai_suggestion**: `'Hello world' → 'Hello world!'`
255
+
256
+ #### Real-world Examples
257
+
258
+ ```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
+ }
267
+ );
268
+
269
+ // User typing
270
+ setArticle({ ...article, title: 'My Article' }, 'user_input');
271
+
272
+ // AI assistance
273
+ setArticle(
274
+ {
275
+ ...article,
276
+ content: 'AI generated introduction...',
277
+ },
278
+ 'ai_generation'
279
+ );
280
+
281
+ // Grammar correction
282
+ setArticle(
283
+ {
284
+ ...article,
285
+ content: 'AI-generated introduction...',
286
+ },
287
+ 'grammar_fix'
288
+ );
289
+
290
+ // User refinement
291
+ setArticle(
292
+ {
293
+ ...article,
294
+ content: 'AI-generated introduction with my changes...',
295
+ },
296
+ 'user_refinement'
297
+ );
298
+ ```
299
+
300
+ #### Common Trigger Name Patterns
301
+
302
+ ```tsx
303
+ // Content creation and editing
304
+ {
305
+ default_trigger_name: 'manual_edit';
98
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';
312
+
313
+ // Automated changes
314
+ 'spell_check' | 'auto_format' | 'auto_save';
315
+ 'validation_fix' | 'import_data';
316
+
317
+ // User interactions
318
+ 'voice_input' | 'gesture_input' | 'shortcut_key';
319
+ 'context_menu' | 'toolbar_action';
320
+
321
+ // Workflow steps
322
+ 'draft' | 'review' | 'approval' | 'publication';
323
+ 'undo' | 'redo' | 'revert';
324
+ ```
325
+
326
+ #### Advanced Pattern: Context-Aware Triggers
327
+
328
+ ```tsx
329
+ function SmartEditor() {
330
+ const [document, setDocument] = useFeedbackState(
331
+ { content: '', metadata: {} },
332
+ 'smart-editor',
333
+ { default_trigger_name: 'user_edit' }
334
+ );
335
+
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
+ }
364
+ ```
365
+
366
+ #### Benefits of Trigger Names
367
+
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
373
+
374
+ #### Analytics Insights
375
+
376
+ With trigger names, you can analyze:
377
+
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?
382
+
383
+ ### API Reference
384
+
385
+ ```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];
391
+ ```
392
+
393
+ #### Parameters
394
+
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 |
400
+
401
+ #### DiffOptions
402
+
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 |
412
+
413
+ ### How It Works
414
+
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
422
+
423
+ ### Diff Formats
424
+
425
+ #### Git Diff (default)
426
+
427
+ ```
428
+ ---
429
+ +++
430
+ @@ -1,1 +1,1 @@
431
+ -{"name": "John"}
432
+ +{"name": "John Doe"}
433
+ ```
434
+
435
+ #### Object Diff
436
+
437
+ ```json
438
+ [
439
+ {
440
+ "kind": "E",
441
+ "path": ["name"],
442
+ "lhs": "John",
443
+ "rhs": "John Doe"
444
+ }
445
+ ]
446
+ ```
447
+
448
+ #### JSON Diff
449
+
450
+ ```json
451
+ {
452
+ "before": {
453
+ "name": "John"
454
+ },
455
+ "after": {
456
+ "name": "John Doe"
457
+ }
458
+ }
459
+ ```
460
+
461
+ ### Use Cases
462
+
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
468
+
469
+ ### Vote Configuration
470
+
471
+ Control how changes are classified with the `vote` option:
472
+
473
+ #### Static Vote
474
+
475
+ ```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
+ });
485
+ ```
486
+
487
+ #### Custom Vote Function
488
+
489
+ ```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
+ );
514
+ ```
515
+
516
+ #### Advanced Vote Logic Examples
517
+
518
+ ```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',
543
+ {
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
+ },
555
+ }
556
+ );
557
+ ```
558
+
559
+ ### Best Practices
560
+
561
+ ```tsx
562
+ // ✅ Good: Static identifier for consistent tracking
563
+ const [settings, setSettings] = useFeedbackState(
564
+ defaultSettings,
565
+ 'user-settings'
566
+ );
567
+
568
+ // ✅ Good: Dynamic identifier that makes sense
569
+ const [items, setItems] = useFeedbackState(
570
+ [],
571
+ items => `shopping-cart-${items.length}-items`
572
+ );
573
+
574
+ // ✅ Good: Appropriate debounce for use case
575
+ const [searchQuery, setSearchQuery] = useFeedbackState(
576
+ '',
577
+ 'search-input',
578
+ { debounceMs: 800 } // Shorter for search
579
+ );
580
+
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
+ });
590
+
591
+ // ❌ Avoid: Very short debounce times
592
+ const [text, setText] = useFeedbackState(
593
+ '',
594
+ 'editor',
595
+ { debounceMs: 50 } // Too aggressive
596
+ );
597
+
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
+ });
99
606
  ```
100
607
 
101
608
  ## Examples
@@ -103,9 +610,9 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
103
610
  ### Basic Usage
104
611
 
105
612
  ```tsx
106
- <VoteFeedback.Root onFeedback={(feedback) => console.log(feedback)}>
107
- <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
108
- <VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
613
+ <VoteFeedback.Root onFeedback={feedback => console.log(feedback)}>
614
+ <VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
615
+ <VoteFeedback.DownvoteButton>👎</VoteFeedback.DownvoteButton>
109
616
  </VoteFeedback.Root>
110
617
  ```
111
618
 
@@ -113,18 +620,35 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
113
620
 
114
621
  ```tsx
115
622
  <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>
623
+ <VoteFeedback.UpvoteButton className="btn btn-success">
624
+ Like
625
+ </VoteFeedback.UpvoteButton>
626
+ <VoteFeedback.DownvoteButton className="btn btn-danger">
627
+ Dislike
628
+ </VoteFeedback.DownvoteButton>
629
+ <VoteFeedback.Popover className="popover">
630
+ <VoteFeedback.Textarea className="textarea" />
631
+ <VoteFeedback.SubmitButton className="btn btn-primary">
632
+ Submit
633
+ </VoteFeedback.SubmitButton>
634
+ </VoteFeedback.Popover>
635
+ </VoteFeedback.Root>
636
+ ```
637
+
638
+ ### With Trigger Name
639
+
640
+ ```tsx
641
+ <VoteFeedback.Root
642
+ onFeedback={handleFeedback}
643
+ identifier="ai-response"
644
+ trigger_name="user_feedback"
645
+ >
646
+ <VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
647
+ <VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
648
+ <VoteFeedback.Popover>
649
+ <VoteFeedback.Textarea placeholder="How can we improve?" />
650
+ <VoteFeedback.SubmitButton>Send feedback</VoteFeedback.SubmitButton>
651
+ </VoteFeedback.Popover>
128
652
  </VoteFeedback.Root>
129
653
  ```
130
654
 
@@ -132,10 +656,10 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
132
656
 
133
657
  ```tsx
134
658
  <VoteFeedback.UpvoteButton asChild>
135
- <button className="custom-button">
136
- <Icon name="thumbs-up"/>
137
- Like
138
- </button>
659
+ <button className="custom-button">
660
+ <Icon name="thumbs-up" />
661
+ Like
662
+ </button>
139
663
  </VoteFeedback.UpvoteButton>
140
664
  ```
141
665
 
@@ -151,7 +675,8 @@ import {VoteFeedback} from '@kelet-ai/feedback-ui';
151
675
  ```bash
152
676
  bun install
153
677
  bun dev # Start Storybook
154
- bun test # Run tests
678
+ bun run test:unit # Run unit tests
679
+ bun run test:storybook # Run Storybook tests
155
680
  bun build # Build library
156
681
  ```
157
682