@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 +569 -52
- package/dist/dist/feedback-ui.es.d.ts +21 -19
- package/dist/feedback-ui.es.js +1316 -392
- package/dist/feedback-ui.umd.js +18 -4
- package/dist/src/components/vote-feedback.d.ts +1 -1
- package/dist/src/hooks/feedback-state/diff-utils.d.ts +9 -0
- package/dist/src/hooks/feedback-state/index.d.ts +3 -0
- package/dist/src/hooks/feedback-state/types.d.ts +52 -0
- package/dist/src/hooks/feedback-state/use-feedback-state.d.ts +31 -0
- package/dist/src/hooks/feedback-state/use-feedback-state.stories.d.ts +20 -0
- package/dist/src/hooks/feedback-state/use-feedback-state.test.d.ts +1 -0
- package/dist/src/hooks/index.d.ts +1 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/types/index.d.ts +5 -0
- package/package.json +37 -28
- package/dist/eslint.config.d.ts +0 -2
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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={
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|