@handled-ai/design-system 0.20.9 → 0.20.10
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/components/comment-composer.js +69 -49
- package/dist/components/comment-composer.js.map +1 -1
- package/dist/components/conversation-panel.js +84 -127
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/timeline-activity.d.ts +0 -1
- package/dist/components/timeline-activity.js +24 -55
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/prototype/prototype-inbox-view.js +107 -62
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/comment-composer.test.tsx +7 -3
- package/src/components/__tests__/conversation-panel.test.tsx +11 -75
- package/src/components/__tests__/timeline-activity.test.tsx +0 -36
- package/src/components/comment-composer.tsx +26 -14
- package/src/components/conversation-panel.tsx +29 -83
- package/src/components/timeline-activity.tsx +10 -43
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +3 -3
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +17 -14
- package/src/prototype/prototype-inbox-view.tsx +72 -44
|
@@ -55,29 +55,34 @@ function CommentComposer({
|
|
|
55
55
|
data-slot="comment-composer"
|
|
56
56
|
data-open={open ? "true" : undefined}
|
|
57
57
|
className={cn(
|
|
58
|
-
"
|
|
59
|
-
open && "ring-ring/30 ring-2",
|
|
58
|
+
"flex items-start gap-4 rounded-xl transition-colors",
|
|
60
59
|
className
|
|
61
60
|
)}
|
|
62
61
|
>
|
|
63
|
-
<Avatar size="sm" className="mt-
|
|
62
|
+
<Avatar size="sm" className="mt-1">
|
|
64
63
|
{author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? "You"} /> : null}
|
|
65
|
-
<AvatarFallback className="bg-
|
|
64
|
+
<AvatarFallback className="bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900">
|
|
66
65
|
{getInitials({ name: author?.name, email: author?.email })}
|
|
67
66
|
</AvatarFallback>
|
|
68
67
|
</Avatar>
|
|
69
68
|
|
|
70
|
-
<div
|
|
69
|
+
<div
|
|
70
|
+
data-slot="comment-composer-shell"
|
|
71
|
+
className={cn(
|
|
72
|
+
"min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]",
|
|
73
|
+
open ? "overflow-hidden shadow-sm" : "shadow-none"
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
71
76
|
<Textarea
|
|
72
77
|
data-slot="comment-composer-input"
|
|
73
78
|
value={text}
|
|
74
79
|
onChange={(e) => setText(e.target.value)}
|
|
75
80
|
onFocus={() => setFocused(true)}
|
|
76
81
|
placeholder={placeholder}
|
|
77
|
-
rows={open ?
|
|
82
|
+
rows={open ? 4 : 1}
|
|
78
83
|
className={cn(
|
|
79
|
-
"resize-none border-0 bg-transparent px-
|
|
80
|
-
|
|
84
|
+
"resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
|
|
85
|
+
open ? "min-h-32" : "min-h-14"
|
|
81
86
|
)}
|
|
82
87
|
onKeyDown={(e) => {
|
|
83
88
|
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
@@ -88,15 +93,16 @@ function CommentComposer({
|
|
|
88
93
|
/>
|
|
89
94
|
|
|
90
95
|
{open ? (
|
|
91
|
-
<div className="
|
|
92
|
-
<span className="
|
|
93
|
-
<Lock size={
|
|
96
|
+
<div className="flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4">
|
|
97
|
+
<span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
|
|
98
|
+
<Lock size={16} strokeWidth={1.75} /> {hint}
|
|
94
99
|
</span>
|
|
95
|
-
<span className="flex items-center gap-
|
|
100
|
+
<span className="flex items-center gap-3">
|
|
96
101
|
<Button
|
|
97
102
|
type="button"
|
|
98
103
|
variant="ghost"
|
|
99
104
|
size="sm"
|
|
105
|
+
className="px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
100
106
|
onClick={() => {
|
|
101
107
|
setText("")
|
|
102
108
|
setFocused(false)
|
|
@@ -104,9 +110,15 @@ function CommentComposer({
|
|
|
104
110
|
>
|
|
105
111
|
Cancel
|
|
106
112
|
</Button>
|
|
107
|
-
<Button
|
|
113
|
+
<Button
|
|
114
|
+
type="button"
|
|
115
|
+
size="sm"
|
|
116
|
+
disabled={!canPost}
|
|
117
|
+
onClick={post}
|
|
118
|
+
className="rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90"
|
|
119
|
+
>
|
|
108
120
|
Comment
|
|
109
|
-
<kbd className="
|
|
121
|
+
<kbd className="ml-1 rounded px-1 text-[10px] text-background/70">⌘↵</kbd>
|
|
110
122
|
</Button>
|
|
111
123
|
</span>
|
|
112
124
|
</div>
|
|
@@ -298,34 +298,19 @@ function PersonAvatar({ person, size = "sm" }: { person: ConvParticipant; size?:
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
const STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {
|
|
301
|
-
responded: { label: "
|
|
301
|
+
responded: { label: "New reply", cls: "bg-status-active-bg text-status-active-fg border-status-active-border" },
|
|
302
302
|
draft: { label: "Draft", cls: "bg-background text-foreground/80 border-border" },
|
|
303
|
-
awaiting: { label: "
|
|
303
|
+
awaiting: { label: "Awaiting", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
|
|
304
304
|
viewing: { label: "Viewing", cls: "bg-muted text-muted-foreground border-border" },
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
const STATUS_DOT: Record<ConvStatus, string> = {
|
|
308
|
-
responded: "bg-status-
|
|
308
|
+
responded: "bg-status-active-fg",
|
|
309
309
|
draft: "bg-status-pending-fg",
|
|
310
|
-
awaiting: "bg-status-
|
|
310
|
+
awaiting: "bg-status-pending-fg",
|
|
311
311
|
viewing: "bg-muted-foreground/50",
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
const THREAD_ROW_ACCENT: Record<ConvStatus, string> = {
|
|
315
|
-
responded: "border-l-4 border-l-status-warning-border bg-status-warning-bg/25",
|
|
316
|
-
awaiting: "border-l-4 border-l-status-info-border bg-status-info-bg/25",
|
|
317
|
-
draft: "border-l-4 border-l-status-pending-border bg-status-pending-bg/20",
|
|
318
|
-
viewing: "",
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const RECEIPT_CHIP: Record<NonNullable<ConvMessage["receipt"]>["kind"], string> = {
|
|
322
|
-
new: "border-status-warning-border bg-status-warning-bg text-status-warning-fg",
|
|
323
|
-
read: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
324
|
-
opened: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
325
|
-
sent: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
326
|
-
draft: "border-status-pending-border bg-status-pending-bg text-status-pending-fg",
|
|
327
|
-
}
|
|
328
|
-
|
|
329
314
|
function effectiveStatus(t: ConversationThread): ConvStatus {
|
|
330
315
|
return t.canReply === false ? "viewing" : t.status
|
|
331
316
|
}
|
|
@@ -447,10 +432,10 @@ function MessageView({
|
|
|
447
432
|
</span>
|
|
448
433
|
<span className="flex shrink-0 items-center gap-2">
|
|
449
434
|
{message.receipt ? (
|
|
450
|
-
<span className=
|
|
435
|
+
<span className="text-muted-foreground inline-flex items-center gap-1 text-[11px]">
|
|
451
436
|
{message.receipt.kind === "new" ? (
|
|
452
437
|
<CornerUpLeft size={11} />
|
|
453
|
-
) : message.receipt.kind === "read"
|
|
438
|
+
) : message.receipt.kind === "read" ? (
|
|
454
439
|
<CheckCheck size={11} />
|
|
455
440
|
) : message.receipt.kind === "draft" ? (
|
|
456
441
|
<FilePenLine size={11} />
|
|
@@ -826,10 +811,10 @@ function ThreadBody({
|
|
|
826
811
|
return (
|
|
827
812
|
<div data-slot="conv-thread-body" className="space-y-2">
|
|
828
813
|
{canReply && thread.paused ? (
|
|
829
|
-
<div className="border-status-
|
|
814
|
+
<div className="border-status-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]">
|
|
830
815
|
<Pause size={13} className="mt-0.5 shrink-0" />
|
|
831
816
|
<span>
|
|
832
|
-
<b>
|
|
817
|
+
<b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send
|
|
833
818
|
automatically while this conversation is live. Continue it in {tenantName ?? "the app"} or Gmail.
|
|
834
819
|
</span>
|
|
835
820
|
</div>
|
|
@@ -945,12 +930,7 @@ function ThreadRow({
|
|
|
945
930
|
const pill = STATUS_PILL[status]
|
|
946
931
|
|
|
947
932
|
return (
|
|
948
|
-
<div
|
|
949
|
-
data-slot="conv-thread"
|
|
950
|
-
data-status={status}
|
|
951
|
-
data-open={open ? "true" : undefined}
|
|
952
|
-
className={cn("border-border border-b last:border-b-0", THREAD_ROW_ACCENT[status])}
|
|
953
|
-
>
|
|
933
|
+
<div data-slot="conv-thread" data-open={open ? "true" : undefined} className="border-border border-b last:border-b-0">
|
|
954
934
|
<button
|
|
955
935
|
type="button"
|
|
956
936
|
onClick={onToggleOpen}
|
|
@@ -1011,13 +991,11 @@ function ConversationPanel({
|
|
|
1011
991
|
const draft = threads.filter((t) => effectiveStatus(t) === "draft").length
|
|
1012
992
|
const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length
|
|
1013
993
|
const anyPaused = threads.some((t) => t.paused)
|
|
1014
|
-
const hubGmailThread = threads.find((t) => canOpenInGmail(t, onOpenInGmail))
|
|
1015
|
-
const firstAwaiting = threads.find((t) => effectiveStatus(t) === "awaiting")
|
|
1016
994
|
|
|
1017
995
|
const [hubOpen, setHubOpen] = React.useState(true)
|
|
1018
996
|
const [openId, setOpenId] = React.useState<string | null>(() => {
|
|
1019
997
|
if (defaultOpenThreadId) return defaultOpenThreadId
|
|
1020
|
-
const firstActionable = threads.find((t) => ["responded", "draft"
|
|
998
|
+
const firstActionable = threads.find((t) => ["responded", "draft"].includes(t.status) && t.canReply !== false)
|
|
1021
999
|
return firstActionable ? firstActionable.threadId : null
|
|
1022
1000
|
})
|
|
1023
1001
|
|
|
@@ -1026,11 +1004,11 @@ function ConversationPanel({
|
|
|
1026
1004
|
// Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.
|
|
1027
1005
|
const badge =
|
|
1028
1006
|
responded > 0
|
|
1029
|
-
? { label: "Email response detected", dot: "bg-status-
|
|
1007
|
+
? { label: "Email response detected", dot: "bg-status-active-fg", ring: "bg-status-active-fg/30" }
|
|
1030
1008
|
: draft > 0
|
|
1031
1009
|
? { label: "Draft ready", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
|
|
1032
1010
|
: awaiting > 0
|
|
1033
|
-
? { label: "
|
|
1011
|
+
? { label: "Awaiting response", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
|
|
1034
1012
|
: { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" }
|
|
1035
1013
|
|
|
1036
1014
|
const headTitle =
|
|
@@ -1039,56 +1017,30 @@ function ConversationPanel({
|
|
|
1039
1017
|
: draft > 0
|
|
1040
1018
|
? `Draft ready on ${draft} ${draft === 1 ? "thread" : "threads"}`
|
|
1041
1019
|
: awaiting > 0
|
|
1042
|
-
? awaiting === 1
|
|
1043
|
-
? `Awaiting a response from ${firstName(displayParticipant(firstAwaiting.contact).name)}`
|
|
1044
|
-
: `Awaiting responses on ${awaiting} threads`
|
|
1020
|
+
? `Awaiting response on ${awaiting} ${awaiting === 1 ? "thread" : "threads"}`
|
|
1045
1021
|
: `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`
|
|
1046
1022
|
|
|
1047
|
-
const panelState = responded > 0 ? "responded" : draft > 0 ? "draft" : awaiting > 0 ? "awaiting" : "viewing"
|
|
1048
|
-
|
|
1049
1023
|
return (
|
|
1050
1024
|
<section
|
|
1051
1025
|
data-slot="conversation-panel"
|
|
1052
1026
|
data-responded={responded > 0 ? "true" : undefined}
|
|
1053
|
-
|
|
1054
|
-
className={cn(
|
|
1055
|
-
"bg-background overflow-hidden rounded-xl border",
|
|
1056
|
-
panelState === "responded"
|
|
1057
|
-
? "border-status-warning-border"
|
|
1058
|
-
: panelState === "awaiting"
|
|
1059
|
-
? "border-status-info-border"
|
|
1060
|
-
: "border-border",
|
|
1061
|
-
className,
|
|
1062
|
-
)}
|
|
1027
|
+
className={cn("border-border bg-background overflow-hidden rounded-xl border", className)}
|
|
1063
1028
|
>
|
|
1064
|
-
<
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
? "bg-status-warning-bg/45"
|
|
1070
|
-
: panelState === "awaiting"
|
|
1071
|
-
? "bg-status-info-bg/55"
|
|
1072
|
-
: "bg-background",
|
|
1073
|
-
)}
|
|
1029
|
+
<button
|
|
1030
|
+
type="button"
|
|
1031
|
+
onClick={() => setHubOpen((v) => !v)}
|
|
1032
|
+
aria-expanded={hubOpen}
|
|
1033
|
+
className="flex w-full items-center gap-3 px-3 py-2.5 text-left"
|
|
1074
1034
|
>
|
|
1075
|
-
<button
|
|
1076
|
-
type="button"
|
|
1077
|
-
onClick={() => setHubOpen((v) => !v)}
|
|
1078
|
-
aria-expanded={hubOpen}
|
|
1079
|
-
className="flex min-w-0 flex-1 items-center gap-3 text-left"
|
|
1080
|
-
>
|
|
1081
1035
|
<span
|
|
1082
1036
|
data-slot="conversation-badge"
|
|
1083
1037
|
className={cn(
|
|
1084
1038
|
"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
|
|
1085
1039
|
responded > 0
|
|
1086
|
-
? "bg-status-
|
|
1087
|
-
: awaiting > 0
|
|
1088
|
-
? "bg-status-
|
|
1089
|
-
:
|
|
1090
|
-
? "bg-status-pending-bg text-status-pending-fg"
|
|
1091
|
-
: "bg-muted text-muted-foreground"
|
|
1040
|
+
? "bg-status-active-bg text-status-active-fg"
|
|
1041
|
+
: draft > 0 || awaiting > 0
|
|
1042
|
+
? "bg-status-pending-bg text-status-pending-fg"
|
|
1043
|
+
: "bg-muted text-muted-foreground"
|
|
1092
1044
|
)}
|
|
1093
1045
|
>
|
|
1094
1046
|
<span className="relative inline-flex size-2">
|
|
@@ -1104,18 +1056,12 @@ function ConversationPanel({
|
|
|
1104
1056
|
{anyPaused ? <> · <b>playbook stopped</b></> : null}
|
|
1105
1057
|
</span>
|
|
1106
1058
|
</span>
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
{hubGmailThread ? (
|
|
1114
|
-
<div className="shrink-0" onClick={(event) => event.stopPropagation()}>
|
|
1115
|
-
<OpenInGmailButton thread={hubGmailThread} onOpenInGmail={onOpenInGmail} />
|
|
1116
|
-
</div>
|
|
1117
|
-
) : null}
|
|
1118
|
-
</div>
|
|
1059
|
+
{hubOpen ? (
|
|
1060
|
+
<ChevronUp size={16} className="text-muted-foreground shrink-0" />
|
|
1061
|
+
) : (
|
|
1062
|
+
<ChevronDown size={16} className="text-muted-foreground shrink-0" />
|
|
1063
|
+
)}
|
|
1064
|
+
</button>
|
|
1119
1065
|
|
|
1120
1066
|
{hubOpen ? (
|
|
1121
1067
|
<div className="border-border border-t">
|
|
@@ -58,7 +58,6 @@ export interface TimelineEvent {
|
|
|
58
58
|
content?: React.ReactNode
|
|
59
59
|
source?: {
|
|
60
60
|
label: string
|
|
61
|
-
actionLabel?: string
|
|
62
61
|
url: string
|
|
63
62
|
}
|
|
64
63
|
defaultExpanded?: boolean
|
|
@@ -471,8 +470,6 @@ function SourceAction({
|
|
|
471
470
|
onSourceClick?: () => void
|
|
472
471
|
className: string
|
|
473
472
|
}) {
|
|
474
|
-
const actionLabel = source.actionLabel ?? `Open in ${source.label}`
|
|
475
|
-
|
|
476
473
|
if (onSourceClick) {
|
|
477
474
|
return (
|
|
478
475
|
<button
|
|
@@ -480,7 +477,7 @@ function SourceAction({
|
|
|
480
477
|
onClick={(e) => { e.stopPropagation(); onSourceClick(); }}
|
|
481
478
|
className={className}
|
|
482
479
|
>
|
|
483
|
-
{
|
|
480
|
+
Open in {source.label}
|
|
484
481
|
<ExternalLink className="h-3 w-3" />
|
|
485
482
|
</button>
|
|
486
483
|
)
|
|
@@ -493,7 +490,7 @@ function SourceAction({
|
|
|
493
490
|
rel="noreferrer noopener"
|
|
494
491
|
className={className}
|
|
495
492
|
>
|
|
496
|
-
{
|
|
493
|
+
Open in {source.label}
|
|
497
494
|
<ExternalLink className="h-3 w-3" />
|
|
498
495
|
</a>
|
|
499
496
|
)
|
|
@@ -538,28 +535,13 @@ function EmailCard({
|
|
|
538
535
|
|
|
539
536
|
<TimelineEmailBody email={event.email} />
|
|
540
537
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
{event.source ? (
|
|
549
|
-
<SourceAction
|
|
550
|
-
source={event.source}
|
|
551
|
-
onSourceClick={event.onSourceClick}
|
|
552
|
-
className="mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
|
|
553
|
-
/>
|
|
554
|
-
) : null}
|
|
555
|
-
<ShowLessButton
|
|
556
|
-
onClick={(e) => {
|
|
557
|
-
e.stopPropagation()
|
|
558
|
-
setExpanded(false)
|
|
559
|
-
}}
|
|
560
|
-
className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
|
|
561
|
-
/>
|
|
562
|
-
</div>
|
|
538
|
+
<ShowLessButton
|
|
539
|
+
onClick={(e) => {
|
|
540
|
+
e.stopPropagation()
|
|
541
|
+
setExpanded(false)
|
|
542
|
+
}}
|
|
543
|
+
className="mt-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
|
|
544
|
+
/>
|
|
563
545
|
</div>
|
|
564
546
|
) : (
|
|
565
547
|
<CollapsedEmailPreview
|
|
@@ -588,24 +570,9 @@ function EmailCard({
|
|
|
588
570
|
|
|
589
571
|
<div className={classes.cardBody} data-slot="timeline-card-body">
|
|
590
572
|
<TimelineEmailBody email={event.email} />
|
|
591
|
-
{event.content ? (
|
|
592
|
-
<div className="mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground">
|
|
593
|
-
{event.content}
|
|
594
|
-
</div>
|
|
595
|
-
) : null}
|
|
596
573
|
</div>
|
|
597
574
|
|
|
598
|
-
<div
|
|
599
|
-
className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? "justify-between" : "justify-end")}
|
|
600
|
-
data-slot="timeline-card-footer"
|
|
601
|
-
>
|
|
602
|
-
{event.source ? (
|
|
603
|
-
<SourceAction
|
|
604
|
-
source={event.source}
|
|
605
|
-
onSourceClick={event.onSourceClick}
|
|
606
|
-
className={classes.actionLink}
|
|
607
|
-
/>
|
|
608
|
-
) : null}
|
|
575
|
+
<div className={cn(classes.cardFooter, classes.actionLinkRow)} data-slot="timeline-card-footer">
|
|
609
576
|
<ShowLessButton
|
|
610
577
|
type="button"
|
|
611
578
|
onClick={(e) => {
|
|
@@ -101,7 +101,7 @@ describe("DetailView case-panel-v2 section layout", () => {
|
|
|
101
101
|
screen.getByText("Cash movement"),
|
|
102
102
|
screen.getByText("Approve action"),
|
|
103
103
|
screen.getByText("After-score marker"),
|
|
104
|
-
screen.getByText(
|
|
104
|
+
screen.getByText(/activity timeline/i),
|
|
105
105
|
screen.getByText("Legacy detail extra marker"),
|
|
106
106
|
)
|
|
107
107
|
})
|
|
@@ -125,7 +125,7 @@ describe("DetailView case-panel-v2 section layout", () => {
|
|
|
125
125
|
screen.getByText("Opportunity marker"),
|
|
126
126
|
screen.getByText("Primary action marker"),
|
|
127
127
|
screen.getByText("Comment area marker"),
|
|
128
|
-
screen.getByText(
|
|
128
|
+
screen.getByText(/activity timeline/i),
|
|
129
129
|
)
|
|
130
130
|
})
|
|
131
131
|
|
|
@@ -148,7 +148,7 @@ describe("DetailView case-panel-v2 section layout", () => {
|
|
|
148
148
|
|
|
149
149
|
expectInDocumentOrder(
|
|
150
150
|
screen.getByText("Comment composer marker"),
|
|
151
|
-
screen.getByText(
|
|
151
|
+
screen.getByText(/activity timeline/i),
|
|
152
152
|
)
|
|
153
153
|
})
|
|
154
154
|
|
|
@@ -209,20 +209,22 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
209
209
|
const badge = container.querySelector('[data-testid="hidden-count-badge"]')
|
|
210
210
|
expect(badge).not.toBeNull()
|
|
211
211
|
expect(badge?.textContent).toBe("2")
|
|
212
|
-
expect(badge).toHaveClass("min-w-[
|
|
212
|
+
expect(badge).toHaveClass("min-w-[22px]")
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
-
it("calls localStorage.setItem when toggle changes and shows
|
|
215
|
+
it("calls localStorage.setItem when toggle changes and shows the active pill style", () => {
|
|
216
216
|
const { container } = render(<DetailView {...baseProps()} />)
|
|
217
217
|
expandTimeline(container)
|
|
218
218
|
const toggle = container.querySelector(
|
|
219
219
|
'[data-testid="system-events-toggle"]',
|
|
220
220
|
) as HTMLElement
|
|
221
221
|
expect(toggle).toHaveAttribute("aria-pressed", "false")
|
|
222
|
+
expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
|
|
222
223
|
fireEvent.click(toggle)
|
|
223
224
|
expect(toggle).toHaveAttribute("aria-pressed", "true")
|
|
224
|
-
expect(toggle
|
|
225
|
-
expect(toggle
|
|
225
|
+
expect(toggle).toHaveClass("border-foreground")
|
|
226
|
+
expect(toggle).toHaveClass("bg-foreground")
|
|
227
|
+
expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
|
|
226
228
|
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
227
229
|
"test-show-score-changes",
|
|
228
230
|
"true",
|
|
@@ -312,18 +314,19 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
312
314
|
expect(toggle).toBeNull()
|
|
313
315
|
})
|
|
314
316
|
|
|
315
|
-
it("
|
|
317
|
+
it("does not render a footer hint and uses the hidden hint as toggle help", () => {
|
|
316
318
|
const { container } = render(<DetailView {...baseProps()} />)
|
|
317
319
|
expandTimeline(container)
|
|
318
320
|
const timeline = container.querySelector('[data-variant="case-panel"]')
|
|
319
321
|
const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
|
|
322
|
+
const toggle = container.querySelector('[data-testid="system-events-toggle"]')
|
|
320
323
|
expect(timeline).not.toBeNull()
|
|
321
|
-
expect(hint).
|
|
322
|
-
expect(
|
|
323
|
-
expect(
|
|
324
|
+
expect(hint).toBeNull()
|
|
325
|
+
expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
|
|
326
|
+
expect(toggle).toHaveAttribute("aria-label", "Score changes are hidden.")
|
|
324
327
|
})
|
|
325
328
|
|
|
326
|
-
it("
|
|
329
|
+
it("uses visible footer hint text as toggle help with count when system events are shown", () => {
|
|
327
330
|
const { container } = render(<DetailView {...baseProps()} />)
|
|
328
331
|
expandTimeline(container)
|
|
329
332
|
// Toggle on
|
|
@@ -332,8 +335,8 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
332
335
|
) as HTMLElement
|
|
333
336
|
fireEvent.click(toggle)
|
|
334
337
|
const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
|
|
335
|
-
expect(hint).
|
|
336
|
-
expect(
|
|
338
|
+
expect(hint).toBeNull()
|
|
339
|
+
expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
|
|
337
340
|
})
|
|
338
341
|
|
|
339
342
|
// --- Toggle always renders when system-noise events exist (review fix #1) ---
|
|
@@ -414,10 +417,10 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
414
417
|
const toggle = container.querySelector('[data-testid="system-events-toggle"]')
|
|
415
418
|
expect(toggle).not.toBeNull()
|
|
416
419
|
expect(toggle?.textContent).toContain("Legacy label")
|
|
417
|
-
//
|
|
420
|
+
// Deprecated hint props are accepted and exposed as toggle help.
|
|
418
421
|
expandTimeline(container)
|
|
419
422
|
const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
|
|
420
|
-
expect(hint).
|
|
421
|
-
expect(
|
|
423
|
+
expect(hint).toBeNull()
|
|
424
|
+
expect(toggle).toHaveAttribute("title", "Legacy hidden hint.")
|
|
422
425
|
})
|
|
423
426
|
})
|