@handled-ai/design-system 0.18.2 → 0.18.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/dist/charts/chart.d.ts +1 -1
- package/dist/components/feedback-primitives.d.ts +2 -21
- package/dist/components/feedback-primitives.js +6 -90
- package/dist/components/feedback-primitives.js.map +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/score-why-chips.js +5 -26
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/signal-priority-popover.js +7 -172
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/components/timeline-activity.d.ts +16 -1
- package/dist/components/timeline-activity.js +69 -1
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +12 -2
- package/dist/prototype/prototype-inbox-view.js +102 -37
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-DQ_VuHac.d.ts} +2 -26
- package/package.json +1 -3
- package/src/components/__tests__/timeline-activity.test.tsx +137 -0
- package/src/components/feedback-primitives.tsx +26 -148
- package/src/components/score-why-chips.tsx +2 -28
- package/src/components/signal-priority-popover.tsx +3 -194
- package/src/components/timeline-activity.tsx +112 -1
- package/src/index.ts +1 -1
- package/src/prototype/__tests__/detail-view-attention.test.tsx +2 -2
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +322 -0
- package/src/prototype/prototype-config.ts +1 -11
- package/src/prototype/prototype-inbox-view.tsx +131 -33
- package/src/components/__tests__/wit-636-feedback-states.test.tsx +0 -546
|
@@ -17,14 +17,10 @@ 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
22
|
import { FeedbackFooter } from "./feedback-primitives"
|
|
27
|
-
import type { FeedbackChipTree, FeedbackSubmitData
|
|
23
|
+
import type { FeedbackChipTree, FeedbackSubmitData } from "./feedback-primitives"
|
|
28
24
|
import type { SignalScoreUrgencyLabel } from "../prototype/prototype-config"
|
|
29
25
|
import { getSignalScoreUrgencyLabel, scoreRangeForUrgency, SIGNAL_TONE_CLASSES } from "./score-why-chips"
|
|
30
26
|
|
|
@@ -62,12 +58,6 @@ export interface SignalPriorityPopoverProps {
|
|
|
62
58
|
feedbackChips?: FeedbackChipTree[]
|
|
63
59
|
onFeedbackSubmit?: (data: FeedbackSubmitData) => void
|
|
64
60
|
className?: string
|
|
65
|
-
/** Persisted factor-level feedback (keyed by factor key). */
|
|
66
|
-
initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string; ownershipLabel?: string }>
|
|
67
|
-
/** Callback when user submits factor-level feedback. */
|
|
68
|
-
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
69
|
-
/** Persisted priority-level feedback for the footer. */
|
|
70
|
-
initialPriorityFeedback?: PersistedFeedbackData | null
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
// ---------------------------------------------------------------------------
|
|
@@ -162,13 +152,7 @@ function DirectionIcon({ direction }: { direction: PriorityFactor["direction"] }
|
|
|
162
152
|
// PriorityFactorRow
|
|
163
153
|
// ---------------------------------------------------------------------------
|
|
164
154
|
|
|
165
|
-
|
|
166
|
-
factor: PriorityFactor
|
|
167
|
-
initialFeedback?: { type: "up" | "down"; detail: string; ownershipLabel?: string }
|
|
168
|
-
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: PriorityFactorRowProps) {
|
|
155
|
+
function PriorityFactorRow({ factor }: { factor: PriorityFactor }) {
|
|
172
156
|
const IconComponent = FACTOR_ICONS[factor.icon] ?? Activity
|
|
173
157
|
const toneClasses = TONE_ICON_CLASSES[factor.tone]
|
|
174
158
|
const directionClasses = DIRECTION_CLASSES[factor.direction]
|
|
@@ -179,50 +163,6 @@ function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: Priori
|
|
|
179
163
|
? "Lowers"
|
|
180
164
|
: "Neutral"
|
|
181
165
|
|
|
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
166
|
return (
|
|
227
167
|
<div
|
|
228
168
|
className="grid grid-cols-[20px_1fr_auto] gap-x-3 gap-y-1 px-4 py-3"
|
|
@@ -284,124 +224,6 @@ function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: Priori
|
|
|
284
224
|
|
|
285
225
|
{/* empty grid cell under score column */}
|
|
286
226
|
<div />
|
|
287
|
-
|
|
288
|
-
{/* Factor-level feedback row (spans icon + content columns) */}
|
|
289
|
-
{onFactorFeedback && (
|
|
290
|
-
<>
|
|
291
|
-
<div />
|
|
292
|
-
<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
|
-
)}
|
|
402
|
-
</div>
|
|
403
|
-
</>
|
|
404
|
-
)}
|
|
405
227
|
</div>
|
|
406
228
|
)
|
|
407
229
|
}
|
|
@@ -419,9 +241,6 @@ export function SignalPriorityPopover({
|
|
|
419
241
|
feedbackChips,
|
|
420
242
|
onFeedbackSubmit,
|
|
421
243
|
className,
|
|
422
|
-
initialFactorFeedback,
|
|
423
|
-
onFactorFeedback,
|
|
424
|
-
initialPriorityFeedback,
|
|
425
244
|
}: SignalPriorityPopoverProps) {
|
|
426
245
|
const urgencyLabel = getSignalScoreUrgencyLabel(score, providedLabel)
|
|
427
246
|
const scoreRange = scoreRangeForUrgency(urgencyLabel)
|
|
@@ -433,9 +252,6 @@ export function SignalPriorityPopover({
|
|
|
433
252
|
const triggerHover = URGENCY_TRIGGER_HOVER[urgencyLabel]
|
|
434
253
|
const triggerOpen = URGENCY_TRIGGER_OPEN[urgencyLabel]
|
|
435
254
|
|
|
436
|
-
// Derive a stable feedbackKey for the footer from score + urgencyLabel
|
|
437
|
-
const footerFeedbackKey = `priority-${score}-${urgencyLabel}`
|
|
438
|
-
|
|
439
255
|
return (
|
|
440
256
|
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
|
441
257
|
<PopoverPrimitive.Trigger asChild>
|
|
@@ -516,12 +332,7 @@ export function SignalPriorityPopover({
|
|
|
516
332
|
{/* Factor rows */}
|
|
517
333
|
<div className="divide-y divide-border/40">
|
|
518
334
|
{factors.map((factor) => (
|
|
519
|
-
<PriorityFactorRow
|
|
520
|
-
key={factor.key}
|
|
521
|
-
factor={factor}
|
|
522
|
-
initialFeedback={initialFactorFeedback?.[factor.key]}
|
|
523
|
-
onFactorFeedback={onFactorFeedback}
|
|
524
|
-
/>
|
|
335
|
+
<PriorityFactorRow key={factor.key} factor={factor} />
|
|
525
336
|
))}
|
|
526
337
|
</div>
|
|
527
338
|
</>
|
|
@@ -538,8 +349,6 @@ export function SignalPriorityPopover({
|
|
|
538
349
|
negativeChips={feedbackChips ?? DEFAULT_PRIORITY_FEEDBACK_CHIPS}
|
|
539
350
|
positivePrompt="Thanks. Anything to keep about this score?"
|
|
540
351
|
className="px-4 py-3"
|
|
541
|
-
initialFeedback={initialPriorityFeedback}
|
|
542
|
-
feedbackKey={footerFeedbackKey}
|
|
543
352
|
/>
|
|
544
353
|
</div>
|
|
545
354
|
)}
|
|
@@ -4,6 +4,24 @@ import * as React from "react"
|
|
|
4
4
|
import { cn } from "../lib/utils"
|
|
5
5
|
import { ChevronDown, ChevronUp, ExternalLink } from "lucide-react"
|
|
6
6
|
|
|
7
|
+
export type TimelineEventTone =
|
|
8
|
+
| "red"
|
|
9
|
+
| "amber"
|
|
10
|
+
| "emerald"
|
|
11
|
+
| "violet"
|
|
12
|
+
| "blue"
|
|
13
|
+
| "slate"
|
|
14
|
+
| "salesforce"
|
|
15
|
+
| "gmail"
|
|
16
|
+
|
|
17
|
+
export interface TimelineEventActor {
|
|
18
|
+
kind: "user" | "integration" | "system"
|
|
19
|
+
name?: string
|
|
20
|
+
initials?: string
|
|
21
|
+
avatarUrl?: string
|
|
22
|
+
verb?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
export interface TimelineEvent {
|
|
8
26
|
id: string
|
|
9
27
|
icon: React.ReactNode
|
|
@@ -28,8 +46,57 @@ export interface TimelineEvent {
|
|
|
28
46
|
defaultExpanded?: boolean
|
|
29
47
|
isInteractive?: boolean
|
|
30
48
|
onSourceClick?: () => void
|
|
49
|
+
tone?: TimelineEventTone
|
|
50
|
+
actor?: TimelineEventActor
|
|
51
|
+
isSystemNoise?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Tone class map — every class is a complete static string literal so
|
|
56
|
+
// Tailwind's JIT scanner can detect them. NO interpolation.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
export const TONE_CLASSES: Record<
|
|
60
|
+
TimelineEventTone,
|
|
61
|
+
{ dot: string; icon: string }
|
|
62
|
+
> = {
|
|
63
|
+
red: {
|
|
64
|
+
dot: "bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40",
|
|
65
|
+
icon: "text-red-600 dark:text-red-300",
|
|
66
|
+
},
|
|
67
|
+
amber: {
|
|
68
|
+
dot: "bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40",
|
|
69
|
+
icon: "text-amber-600 dark:text-amber-300",
|
|
70
|
+
},
|
|
71
|
+
emerald: {
|
|
72
|
+
dot: "bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40",
|
|
73
|
+
icon: "text-emerald-600 dark:text-emerald-300",
|
|
74
|
+
},
|
|
75
|
+
violet: {
|
|
76
|
+
dot: "bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40",
|
|
77
|
+
icon: "text-violet-600 dark:text-violet-300",
|
|
78
|
+
},
|
|
79
|
+
blue: {
|
|
80
|
+
dot: "bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40",
|
|
81
|
+
icon: "text-blue-600 dark:text-blue-300",
|
|
82
|
+
},
|
|
83
|
+
slate: {
|
|
84
|
+
dot: "bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700",
|
|
85
|
+
icon: "text-slate-500 dark:text-slate-300",
|
|
86
|
+
},
|
|
87
|
+
salesforce: {
|
|
88
|
+
dot: "bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25",
|
|
89
|
+
icon: "text-[#00A1E0]",
|
|
90
|
+
},
|
|
91
|
+
gmail: {
|
|
92
|
+
dot: "bg-white border-red-200 dark:bg-background dark:border-red-900/40",
|
|
93
|
+
icon: "text-red-500 dark:text-red-300",
|
|
94
|
+
},
|
|
31
95
|
}
|
|
32
96
|
|
|
97
|
+
const NEUTRAL_DOT_CLASSES = "border-border/60 bg-background"
|
|
98
|
+
const NEUTRAL_ICON_CLASSES = "text-muted-foreground"
|
|
99
|
+
|
|
33
100
|
export interface TimelineActivityProps {
|
|
34
101
|
events: TimelineEvent[]
|
|
35
102
|
className?: string
|
|
@@ -49,12 +116,54 @@ export function TimelineActivity({ events, className }: TimelineActivityProps) {
|
|
|
49
116
|
)
|
|
50
117
|
}
|
|
51
118
|
|
|
119
|
+
function ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {
|
|
120
|
+
if (actor.kind === "system") return null
|
|
121
|
+
|
|
122
|
+
if (actor.kind === "integration") {
|
|
123
|
+
return (
|
|
124
|
+
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground" data-testid="actor-byline">
|
|
125
|
+
<span>Integration</span>
|
|
126
|
+
<span className="text-muted-foreground/40">·</span>
|
|
127
|
+
<span>{time}</span>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// actor.kind === "user"
|
|
133
|
+
const verb = actor.verb ?? "performed this action"
|
|
134
|
+
const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : "?")
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground" data-testid="actor-byline">
|
|
138
|
+
{actor.avatarUrl ? (
|
|
139
|
+
<img
|
|
140
|
+
src={actor.avatarUrl}
|
|
141
|
+
alt={actor.name ?? "User"}
|
|
142
|
+
className="h-4 w-4 rounded-full object-cover"
|
|
143
|
+
/>
|
|
144
|
+
) : (
|
|
145
|
+
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground">
|
|
146
|
+
{displayInitials}
|
|
147
|
+
</span>
|
|
148
|
+
)}
|
|
149
|
+
<span className="text-foreground font-medium">{actor.name}</span>
|
|
150
|
+
<span>{verb}</span>
|
|
151
|
+
<span className="text-muted-foreground/40">·</span>
|
|
152
|
+
<span>{time}</span>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
52
157
|
function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean }) {
|
|
53
158
|
const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)
|
|
54
159
|
const [showAllRecipients, setShowAllRecipients] = React.useState(false)
|
|
55
160
|
const hasContent = !!event.content
|
|
56
161
|
const hasEmail = !!event.email
|
|
57
162
|
|
|
163
|
+
const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null
|
|
164
|
+
const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES
|
|
165
|
+
const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES
|
|
166
|
+
|
|
58
167
|
return (
|
|
59
168
|
<div className="group relative flex gap-3.5">
|
|
60
169
|
{!isLast && (
|
|
@@ -62,7 +171,7 @@ function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean
|
|
|
62
171
|
)}
|
|
63
172
|
|
|
64
173
|
<div className="relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background">
|
|
65
|
-
<div className="flex h-4.5 w-4.5 items-center justify-center rounded-full border
|
|
174
|
+
<div className={cn("flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background", dotClasses, iconClasses)} data-testid="timeline-dot">
|
|
66
175
|
{event.icon}
|
|
67
176
|
</div>
|
|
68
177
|
</div>
|
|
@@ -77,6 +186,8 @@ function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean
|
|
|
77
186
|
</span>
|
|
78
187
|
</div>
|
|
79
188
|
|
|
189
|
+
{event.actor && <ActorByline actor={event.actor} time={event.time} />}
|
|
190
|
+
|
|
80
191
|
{(hasContent || hasEmail) && (
|
|
81
192
|
<div className="mt-2">
|
|
82
193
|
{event.isInteractive ? (
|
package/src/index.ts
CHANGED
|
@@ -38,7 +38,7 @@ export * from "./components/dropdown-menu"
|
|
|
38
38
|
export * from "./components/empty-state"
|
|
39
39
|
export * from "./components/entity-panel"
|
|
40
40
|
export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions } from "./components/feedback-primitives"
|
|
41
|
-
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData
|
|
41
|
+
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData } 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"
|
|
@@ -89,8 +89,8 @@ describe("DetailView attentionCount", () => {
|
|
|
89
89
|
expect(pill).not.toBeNull();
|
|
90
90
|
expect(pill!.textContent).toContain("5");
|
|
91
91
|
|
|
92
|
-
// Click the timeline
|
|
93
|
-
const timelineButton = container.querySelector("
|
|
92
|
+
// Click the timeline collapse button to expand
|
|
93
|
+
const timelineButton = container.querySelector('[data-testid="timeline-collapse-btn"]') as HTMLElement;
|
|
94
94
|
expect(timelineButton).not.toBeNull();
|
|
95
95
|
fireEvent.click(timelineButton);
|
|
96
96
|
|