@handled-ai/design-system 0.18.2 → 0.18.4

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.
@@ -17,13 +17,9 @@ import {
17
17
  ChevronDown,
18
18
  ChevronUp,
19
19
  Info,
20
- ThumbsUp,
21
- ThumbsDown,
22
- Check,
23
- Pencil,
24
20
  } from "lucide-react"
25
21
  import { cn } from "../lib/utils"
26
- import { FeedbackFooter } from "./feedback-primitives"
22
+ import { FeedbackFooter, InlineFeedbackControl } from "./feedback-primitives"
27
23
  import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "./feedback-primitives"
28
24
  import type { SignalScoreUrgencyLabel } from "../prototype/prototype-config"
29
25
  import { getSignalScoreUrgencyLabel, scoreRangeForUrgency, SIGNAL_TONE_CLASSES } from "./score-why-chips"
@@ -179,50 +175,6 @@ function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: Priori
179
175
  ? "Lowers"
180
176
  : "Neutral"
181
177
 
182
- const [thumbState, setThumbState] = React.useState<"up" | "down" | null>(
183
- initialFeedback?.type ?? null,
184
- )
185
- const [showInput, setShowInput] = React.useState(false)
186
- const [detailText, setDetailText] = React.useState(initialFeedback?.detail ?? "")
187
- const [saved, setSaved] = React.useState(!!initialFeedback)
188
- const [savedDetail, setSavedDetail] = React.useState(initialFeedback?.detail ?? "")
189
- const ownershipLabel = initialFeedback?.ownershipLabel ?? "Your feedback"
190
-
191
- // Sync with initialFeedback prop changes
192
- React.useEffect(() => {
193
- if (initialFeedback) {
194
- setThumbState(initialFeedback.type)
195
- setSaved(true)
196
- setSavedDetail(initialFeedback.detail)
197
- }
198
- }, [initialFeedback])
199
-
200
- const handleThumbClick = React.useCallback(
201
- (type: "up" | "down") => {
202
- if (thumbState === type) {
203
- // Toggle off
204
- setThumbState(null)
205
- setShowInput(false)
206
- setSaved(false)
207
- onFactorFeedback?.(factor.key, null)
208
- } else {
209
- setThumbState(type)
210
- setShowInput(true)
211
- setSaved(false)
212
- }
213
- },
214
- [thumbState, factor.key, onFactorFeedback],
215
- )
216
-
217
- const handleSubmitDetail = React.useCallback(() => {
218
- if (!thumbState) return
219
- const text = detailText.trim()
220
- onFactorFeedback?.(factor.key, thumbState, text)
221
- setSaved(true)
222
- setSavedDetail(text)
223
- setShowInput(false)
224
- }, [thumbState, detailText, factor.key, onFactorFeedback])
225
-
226
178
  return (
227
179
  <div
228
180
  className="grid grid-cols-[20px_1fr_auto] gap-x-3 gap-y-1 px-4 py-3"
@@ -290,115 +242,12 @@ function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: Priori
290
242
  <>
291
243
  <div />
292
244
  <div className="col-span-2 mt-1">
293
- {saved && !showInput ? (
294
- /* Persisted / saved indicator */
295
- <button
296
- type="button"
297
- onClick={() => {
298
- setDetailText(savedDetail)
299
- setShowInput(true)
300
- setSaved(false)
301
- }}
302
- className="group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
303
- data-testid={`factor-feedback-persisted-${factor.key}`}
304
- >
305
- <span className="font-medium">{ownershipLabel}:</span>
306
- {thumbState === "up" ? (
307
- <ThumbsUp className="h-[10px] w-[10px]" />
308
- ) : (
309
- <ThumbsDown className="h-[10px] w-[10px]" />
310
- )}
311
- {savedDetail && (
312
- <span className="max-w-[180px] truncate text-muted-foreground/70">
313
- {savedDetail}
314
- </span>
315
- )}
316
- <Pencil className="h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" />
317
- </button>
318
- ) : (
319
- <div className="flex items-center gap-1.5">
320
- {/* Inline thumb buttons */}
321
- <button
322
- type="button"
323
- onClick={() => handleThumbClick("up")}
324
- className={cn(
325
- "p-1 rounded transition-colors",
326
- thumbState === "up"
327
- ? "text-foreground bg-muted"
328
- : "text-muted-foreground/40 hover:text-foreground hover:bg-muted/50",
329
- )}
330
- title="This factor is accurate"
331
- data-testid={`factor-thumb-up-${factor.key}`}
332
- >
333
- <ThumbsUp className="h-[10px] w-[10px]" />
334
- </button>
335
- <button
336
- type="button"
337
- onClick={() => handleThumbClick("down")}
338
- className={cn(
339
- "p-1 rounded transition-colors",
340
- thumbState === "down"
341
- ? "text-red-600 bg-red-50"
342
- : "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50",
343
- )}
344
- title="Report issue with this factor"
345
- data-testid={`factor-thumb-down-${factor.key}`}
346
- >
347
- <ThumbsDown className="h-[10px] w-[10px]" />
348
- </button>
349
-
350
- {/* Transient "Saved" pill */}
351
- {saved && (
352
- <span
353
- className="inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600"
354
- role="status"
355
- data-testid={`factor-saved-${factor.key}`}
356
- >
357
- <Check className="h-[10px] w-[10px]" />
358
- Saved
359
- </span>
360
- )}
361
- </div>
362
- )}
363
-
364
- {/* Inline detail input */}
365
- {showInput && thumbState && (
366
- <div className="mt-1.5">
367
- <input
368
- type="text"
369
- value={detailText}
370
- onChange={(e) => setDetailText(e.target.value)}
371
- onKeyDown={(e) => {
372
- if (e.key === "Enter") handleSubmitDetail()
373
- if (e.key === "Escape") setShowInput(false)
374
- }}
375
- placeholder={
376
- thumbState === "up"
377
- ? "What\u2019s accurate? (optional)"
378
- : "What\u2019s wrong? (optional)"
379
- }
380
- className="w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring"
381
- data-testid={`factor-detail-input-${factor.key}`}
382
- />
383
- <div className="mt-1 flex items-center gap-1.5">
384
- <button
385
- type="button"
386
- onClick={handleSubmitDetail}
387
- className="bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold"
388
- data-testid={`factor-submit-${factor.key}`}
389
- >
390
- Submit
391
- </button>
392
- <button
393
- type="button"
394
- onClick={() => setShowInput(false)}
395
- className="border border-border rounded px-2 py-0.5 text-[10px] font-medium"
396
- >
397
- Cancel
398
- </button>
399
- </div>
400
- </div>
401
- )}
245
+ <InlineFeedbackControl
246
+ feedbackKey={factor.key}
247
+ initialFeedback={initialFeedback}
248
+ onFeedback={onFactorFeedback}
249
+ testIdPrefix="factor"
250
+ />
402
251
  </div>
403
252
  </>
404
253
  )}
package/src/index.ts CHANGED
@@ -37,8 +37,8 @@ export * from "./components/dialog"
37
37
  export * from "./components/dropdown-menu"
38
38
  export * from "./components/empty-state"
39
39
  export * from "./components/entity-panel"
40
- export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions } from "./components/feedback-primitives"
41
- export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData } from "./components/feedback-primitives"
40
+ export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from "./components/feedback-primitives"
41
+ export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from "./components/feedback-primitives"
42
42
  export { SignalPriorityPopover } from "./components/signal-priority-popover"
43
43
  export type { SignalPriorityPopoverProps, PriorityFactor } from "./components/signal-priority-popover"
44
44
  export * from "./components/filter-chip"