@handled-ai/design-system 0.19.1 → 0.20.0
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/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/conversation-panel.d.ts +2 -2
- package/dist/components/conversation-panel.js +10 -6
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/index.d.ts +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 +9 -2
- package/dist/prototype/prototype-inbox-view.js +60 -56
- 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-CZitE9xq.d.ts → signal-priority-popover-Cg9XPJsp.d.ts} +5 -0
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +31 -0
- package/src/components/conversation-panel.tsx +22 -12
- package/src/prototype/__tests__/detail-view-metadata-layout.test.tsx +97 -0
- package/src/prototype/prototype-config.ts +5 -0
- package/src/prototype/prototype-inbox-view.tsx +94 -65
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
CornerUpLeft,
|
|
29
29
|
CheckCheck,
|
|
30
30
|
MailOpen,
|
|
31
|
+
FilePenLine,
|
|
31
32
|
Reply,
|
|
32
33
|
ReplyAll,
|
|
33
34
|
Eye,
|
|
@@ -75,7 +76,7 @@ export interface ConvMessage {
|
|
|
75
76
|
date: string
|
|
76
77
|
/** Relative label, e.g. "2 days ago". */
|
|
77
78
|
ago?: string
|
|
78
|
-
receipt?: { kind: "new" | "read" | "opened" | "sent"; label: string }
|
|
79
|
+
receipt?: { kind: "new" | "read" | "opened" | "sent" | "draft"; label: string }
|
|
79
80
|
/** HTML body (preferred). Sanitized by the component before rendering. */
|
|
80
81
|
bodyHtml?: string
|
|
81
82
|
/** Plain-text fallback when `bodyHtml` is absent. */
|
|
@@ -84,7 +85,7 @@ export interface ConvMessage {
|
|
|
84
85
|
quoted?: { attr: string; html: string }
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
export type ConvStatus = "responded" | "awaiting" | "viewing"
|
|
88
|
+
export type ConvStatus = "responded" | "awaiting" | "viewing" | "draft"
|
|
88
89
|
|
|
89
90
|
export interface ConversationThread {
|
|
90
91
|
threadId: string
|
|
@@ -181,12 +182,14 @@ function firstName(name: string): string {
|
|
|
181
182
|
|
|
182
183
|
const STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {
|
|
183
184
|
responded: { label: "New reply", cls: "bg-status-active-bg text-status-active-fg border-status-active-border" },
|
|
185
|
+
draft: { label: "Draft", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
|
|
184
186
|
awaiting: { label: "Awaiting", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
|
|
185
187
|
viewing: { label: "Viewing", cls: "bg-muted text-muted-foreground border-border" },
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
const STATUS_DOT: Record<ConvStatus, string> = {
|
|
189
191
|
responded: "bg-status-active-fg",
|
|
192
|
+
draft: "bg-status-pending-fg",
|
|
190
193
|
awaiting: "bg-status-pending-fg",
|
|
191
194
|
viewing: "bg-muted-foreground/50",
|
|
192
195
|
}
|
|
@@ -257,6 +260,8 @@ function MessageView({
|
|
|
257
260
|
<CornerUpLeft size={11} />
|
|
258
261
|
) : message.receipt.kind === "read" ? (
|
|
259
262
|
<CheckCheck size={11} />
|
|
263
|
+
) : message.receipt.kind === "draft" ? (
|
|
264
|
+
<FilePenLine size={11} />
|
|
260
265
|
) : (
|
|
261
266
|
<MailOpen size={11} />
|
|
262
267
|
)}
|
|
@@ -696,32 +701,37 @@ function ConversationPanel({
|
|
|
696
701
|
className,
|
|
697
702
|
}: ConversationPanelProps) {
|
|
698
703
|
const responded = threads.filter((t) => t.status === "responded" && t.canReply !== false).length
|
|
704
|
+
const draft = threads.filter((t) => effectiveStatus(t) === "draft").length
|
|
699
705
|
const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length
|
|
700
706
|
const anyPaused = threads.some((t) => t.paused)
|
|
701
707
|
|
|
702
708
|
const [hubOpen, setHubOpen] = React.useState(true)
|
|
703
709
|
const [openId, setOpenId] = React.useState<string | null>(() => {
|
|
704
710
|
if (defaultOpenThreadId) return defaultOpenThreadId
|
|
705
|
-
const
|
|
706
|
-
return
|
|
711
|
+
const firstActionable = threads.find((t) => ["responded", "draft"].includes(t.status) && t.canReply !== false)
|
|
712
|
+
return firstActionable ? firstActionable.threadId : null
|
|
707
713
|
})
|
|
708
714
|
|
|
709
715
|
if (!threads.length) return null
|
|
710
716
|
|
|
711
|
-
// Header badge state: a responded reply leads
|
|
717
|
+
// Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.
|
|
712
718
|
const badge =
|
|
713
719
|
responded > 0
|
|
714
720
|
? { label: "Email response detected", dot: "bg-status-active-fg", ring: "bg-status-active-fg/30" }
|
|
715
|
-
:
|
|
716
|
-
? { label: "
|
|
717
|
-
:
|
|
721
|
+
: draft > 0
|
|
722
|
+
? { label: "Draft ready", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
|
|
723
|
+
: awaiting > 0
|
|
724
|
+
? { label: "Awaiting response", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
|
|
725
|
+
: { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" }
|
|
718
726
|
|
|
719
727
|
const headTitle =
|
|
720
728
|
responded > 0
|
|
721
729
|
? `${responded} ${responded === 1 ? "reply needs" : "replies need"} your response`
|
|
722
|
-
:
|
|
723
|
-
? `
|
|
724
|
-
:
|
|
730
|
+
: draft > 0
|
|
731
|
+
? `Draft ready on ${draft} ${draft === 1 ? "thread" : "threads"}`
|
|
732
|
+
: awaiting > 0
|
|
733
|
+
? `Awaiting response on ${awaiting} ${awaiting === 1 ? "thread" : "threads"}`
|
|
734
|
+
: `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`
|
|
725
735
|
|
|
726
736
|
return (
|
|
727
737
|
<section
|
|
@@ -741,7 +751,7 @@ function ConversationPanel({
|
|
|
741
751
|
"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
|
|
742
752
|
responded > 0
|
|
743
753
|
? "bg-status-active-bg text-status-active-fg"
|
|
744
|
-
: awaiting > 0
|
|
754
|
+
: draft > 0 || awaiting > 0
|
|
745
755
|
? "bg-status-pending-bg text-status-pending-fg"
|
|
746
756
|
: "bg-muted text-muted-foreground"
|
|
747
757
|
)}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { cleanup, render, screen } from "@testing-library/react"
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import { DetailView } from "../prototype-inbox-view"
|
|
5
|
+
import type { DetailViewProps } from "../prototype-inbox-view"
|
|
6
|
+
import type { SignalScoreData } from "../prototype-config"
|
|
7
|
+
|
|
8
|
+
const baseItem = {
|
|
9
|
+
id: "case-1",
|
|
10
|
+
title: "Churn risk case title",
|
|
11
|
+
details: "Case details",
|
|
12
|
+
statusColor: "red",
|
|
13
|
+
time: "1h ago",
|
|
14
|
+
company: "Acme Corp",
|
|
15
|
+
tag1: "Signal",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
cleanup()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function renderDetailView(
|
|
23
|
+
overrides: Partial<DetailViewProps> = {},
|
|
24
|
+
scoreOverrides: Partial<SignalScoreData> = {},
|
|
25
|
+
) {
|
|
26
|
+
const props: DetailViewProps = {
|
|
27
|
+
item: baseItem,
|
|
28
|
+
sections: { signalBrief: true, suggestedActions: false, timeline: false },
|
|
29
|
+
getSignalScore: () => ({
|
|
30
|
+
score: 72,
|
|
31
|
+
factors: [],
|
|
32
|
+
whyNow: "Why now",
|
|
33
|
+
evidence: [],
|
|
34
|
+
confidence: 80,
|
|
35
|
+
urgencyLabel: "High",
|
|
36
|
+
urgencyExplanation: "High priority",
|
|
37
|
+
signalBrief: "BRIEF BODY TEXT",
|
|
38
|
+
timeChipLabel: "3 days left",
|
|
39
|
+
...scoreOverrides,
|
|
40
|
+
}),
|
|
41
|
+
buildSuggestedActions: () => [],
|
|
42
|
+
buildSourceItems: () => [],
|
|
43
|
+
accountContacts: [],
|
|
44
|
+
emailSignature: "",
|
|
45
|
+
iconMap: {},
|
|
46
|
+
renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
|
|
47
|
+
...overrides,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return render(<DetailView {...props} />)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const PRECEDES = Node.DOCUMENT_POSITION_PRECEDING
|
|
54
|
+
const FOLLOWS = Node.DOCUMENT_POSITION_FOLLOWING
|
|
55
|
+
|
|
56
|
+
describe("DetailView metadataLayout", () => {
|
|
57
|
+
it("renders the metadata chips above the brief by default", () => {
|
|
58
|
+
renderDetailView()
|
|
59
|
+
|
|
60
|
+
const brief = screen.getByText("BRIEF BODY TEXT")
|
|
61
|
+
const meta = screen.getByTestId("metadata-extra")
|
|
62
|
+
// metadata-extra comes before the brief body in document order
|
|
63
|
+
expect(brief.compareDocumentPosition(meta) & PRECEDES).toBeTruthy()
|
|
64
|
+
expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("relocates the metadata chips below the brief when metadataLayout='below-brief'", () => {
|
|
68
|
+
renderDetailView({ metadataLayout: "below-brief" })
|
|
69
|
+
|
|
70
|
+
const brief = screen.getByText("BRIEF BODY TEXT")
|
|
71
|
+
const meta = screen.getByTestId("metadata-extra")
|
|
72
|
+
// metadata-extra now follows the brief body
|
|
73
|
+
expect(brief.compareDocumentPosition(meta) & FOLLOWS).toBeTruthy()
|
|
74
|
+
// chips themselves (priority + account + deadline) render exactly once
|
|
75
|
+
expect(screen.getAllByTestId("priority-popover-trigger")).toHaveLength(1)
|
|
76
|
+
expect(screen.getByText("3 days left")).toBeTruthy()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("DetailView deadline chip tone", () => {
|
|
81
|
+
it("applies the red tone when timeChipTone='red'", () => {
|
|
82
|
+
renderDetailView({}, { timeChipTone: "red" })
|
|
83
|
+
expect(screen.getByText("3 days left").className).toContain("text-red-700")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("applies the amber tone when timeChipTone='amber'", () => {
|
|
87
|
+
renderDetailView({}, { timeChipTone: "amber" })
|
|
88
|
+
expect(screen.getByText("3 days left").className).toContain("text-amber-700")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("uses a neutral tone when timeChipTone is omitted", () => {
|
|
92
|
+
renderDetailView()
|
|
93
|
+
const chip = screen.getByText("3 days left")
|
|
94
|
+
expect(chip.className).not.toContain("text-red-700")
|
|
95
|
+
expect(chip.className).not.toContain("text-amber-700")
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -142,6 +142,11 @@ export interface SignalScoreData {
|
|
|
142
142
|
timeChipLabel?: string
|
|
143
143
|
/** Expanded detail for time chip popover (e.g., "Day 2 of 14 · Event detected on May 1"). */
|
|
144
144
|
timeChipDetail?: string
|
|
145
|
+
/**
|
|
146
|
+
* Visual urgency tone for the deadline chip. The consumer computes this from the live deadline
|
|
147
|
+
* (e.g. amber when a few days remain, red when due/overdue). Defaults to neutral when omitted.
|
|
148
|
+
*/
|
|
149
|
+
timeChipTone?: "neutral" | "amber" | "red"
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
// ---------------------------------------------------------------------------
|
|
@@ -169,6 +169,13 @@ export interface DetailViewProps {
|
|
|
169
169
|
renderTitleSubtext?: (item: QueueItem) => React.ReactNode
|
|
170
170
|
/** Render extra metadata chips (e.g. assignee) inside the chips row below the title. */
|
|
171
171
|
renderMetadataExtra?: (item: QueueItem) => React.ReactNode
|
|
172
|
+
/**
|
|
173
|
+
* Where the metadata chips row (priority · deadline · account · renderMetadataExtra) sits.
|
|
174
|
+
* - "default": above the signal brief (legacy layout, unchanged for existing consumers).
|
|
175
|
+
* - "below-brief": moved beneath the brief text, just before `renderBeforeScore`
|
|
176
|
+
* (case-panel redesign — chips + owners read as one block under the brief).
|
|
177
|
+
*/
|
|
178
|
+
metadataLayout?: "default" | "below-brief"
|
|
172
179
|
/** Override the built-in account details metadata button label. */
|
|
173
180
|
accountDetailsButtonLabel?: (item: QueueItem) => React.ReactNode
|
|
174
181
|
/** Accessible label for the built-in account details metadata button. */
|
|
@@ -352,6 +359,7 @@ export function DetailView({
|
|
|
352
359
|
renderTitleActionRow,
|
|
353
360
|
renderTitleSubtext,
|
|
354
361
|
renderMetadataExtra,
|
|
362
|
+
metadataLayout = "default",
|
|
355
363
|
accountDetailsButtonLabel,
|
|
356
364
|
getAccountDetailsButtonAriaLabel,
|
|
357
365
|
onOpenSignalBucket,
|
|
@@ -465,6 +473,84 @@ export function DetailView({
|
|
|
465
473
|
[suggestedActions],
|
|
466
474
|
)
|
|
467
475
|
|
|
476
|
+
// Deadline chip tone (case-panel redesign): amber as the deadline nears, red when due/overdue.
|
|
477
|
+
const timeChipToneClass =
|
|
478
|
+
signalData.timeChipTone === "red"
|
|
479
|
+
? "border-red-300 bg-red-50 text-red-700 hover:bg-red-50"
|
|
480
|
+
: signalData.timeChipTone === "amber"
|
|
481
|
+
? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50"
|
|
482
|
+
: "hover:bg-muted/50"
|
|
483
|
+
|
|
484
|
+
// The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above
|
|
485
|
+
// the brief by default, or beneath it when `metadataLayout === "below-brief"` (case-panel redesign).
|
|
486
|
+
const metadataChips = (
|
|
487
|
+
<>
|
|
488
|
+
<SignalPriorityPopover
|
|
489
|
+
score={signalData.score}
|
|
490
|
+
urgencyLabel={signalData.urgencyLabel}
|
|
491
|
+
urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
|
|
492
|
+
scoreDisplay={signalData.priorityScoreDisplay}
|
|
493
|
+
formulaLabel={signalData.priorityFormulaLabel}
|
|
494
|
+
factors={signalData.priorityFactors ?? []}
|
|
495
|
+
metaText={undefined}
|
|
496
|
+
feedbackChips={signalData.priorityFeedbackChips}
|
|
497
|
+
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
498
|
+
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
499
|
+
onFactorFeedback={signalData.onFactorFeedback}
|
|
500
|
+
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
501
|
+
/>
|
|
502
|
+
{signalData.timeChipLabel && (
|
|
503
|
+
<Badge
|
|
504
|
+
variant="outline"
|
|
505
|
+
title={signalData.timeChipDetail ?? undefined}
|
|
506
|
+
className={`transition-colors ${timeChipToneClass}`}
|
|
507
|
+
>
|
|
508
|
+
{signalData.timeChipLabel}
|
|
509
|
+
</Badge>
|
|
510
|
+
)}
|
|
511
|
+
<TooltipProvider delayDuration={300}>
|
|
512
|
+
<Tooltip>
|
|
513
|
+
<TooltipTrigger asChild>
|
|
514
|
+
<button
|
|
515
|
+
type="button"
|
|
516
|
+
onClick={onOpenEntityPanel}
|
|
517
|
+
className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
|
|
518
|
+
aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
|
|
519
|
+
>
|
|
520
|
+
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
|
|
521
|
+
{getCompanyInitials(item.company)}
|
|
522
|
+
</span>
|
|
523
|
+
{accountDetailsButtonLabel ? (
|
|
524
|
+
<span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
|
|
525
|
+
{accountDetailsButtonLabel(item)}
|
|
526
|
+
</span>
|
|
527
|
+
) : (
|
|
528
|
+
<span className="truncate text-xs font-medium text-foreground">{item.company}</span>
|
|
529
|
+
)}
|
|
530
|
+
<span className="flex shrink-0 items-center gap-1.5">
|
|
531
|
+
<span
|
|
532
|
+
aria-hidden
|
|
533
|
+
className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
|
|
534
|
+
/>
|
|
535
|
+
{/* "View account" expands in on hover via an animated grid column */}
|
|
536
|
+
<span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
|
|
537
|
+
<span className="overflow-hidden">
|
|
538
|
+
<span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
|
|
539
|
+
View account
|
|
540
|
+
</span>
|
|
541
|
+
</span>
|
|
542
|
+
</span>
|
|
543
|
+
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
|
|
544
|
+
</span>
|
|
545
|
+
</button>
|
|
546
|
+
</TooltipTrigger>
|
|
547
|
+
<TooltipContent>Open account panel</TooltipContent>
|
|
548
|
+
</Tooltip>
|
|
549
|
+
</TooltipProvider>
|
|
550
|
+
{renderMetadataExtra?.(item)}
|
|
551
|
+
</>
|
|
552
|
+
)
|
|
553
|
+
|
|
468
554
|
return (
|
|
469
555
|
<SignalApproval.Root
|
|
470
556
|
key={item.id}
|
|
@@ -506,71 +592,9 @@ export function DetailView({
|
|
|
506
592
|
) : null}
|
|
507
593
|
</div>
|
|
508
594
|
|
|
509
|
-
|
|
510
|
-
<
|
|
511
|
-
|
|
512
|
-
urgencyLabel={signalData.urgencyLabel}
|
|
513
|
-
urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
|
|
514
|
-
scoreDisplay={signalData.priorityScoreDisplay}
|
|
515
|
-
formulaLabel={signalData.priorityFormulaLabel}
|
|
516
|
-
factors={signalData.priorityFactors ?? []}
|
|
517
|
-
metaText={undefined}
|
|
518
|
-
feedbackChips={signalData.priorityFeedbackChips}
|
|
519
|
-
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
520
|
-
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
521
|
-
onFactorFeedback={signalData.onFactorFeedback}
|
|
522
|
-
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
523
|
-
/>
|
|
524
|
-
{signalData.timeChipLabel && (
|
|
525
|
-
<Badge
|
|
526
|
-
variant="outline"
|
|
527
|
-
title={signalData.timeChipDetail ?? undefined}
|
|
528
|
-
className="transition-colors hover:bg-muted/50"
|
|
529
|
-
>
|
|
530
|
-
{signalData.timeChipLabel}
|
|
531
|
-
</Badge>
|
|
532
|
-
)}
|
|
533
|
-
<TooltipProvider delayDuration={300}>
|
|
534
|
-
<Tooltip>
|
|
535
|
-
<TooltipTrigger asChild>
|
|
536
|
-
<button
|
|
537
|
-
type="button"
|
|
538
|
-
onClick={onOpenEntityPanel}
|
|
539
|
-
className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
|
|
540
|
-
aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
|
|
541
|
-
>
|
|
542
|
-
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
|
|
543
|
-
{getCompanyInitials(item.company)}
|
|
544
|
-
</span>
|
|
545
|
-
{accountDetailsButtonLabel ? (
|
|
546
|
-
<span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
|
|
547
|
-
{accountDetailsButtonLabel(item)}
|
|
548
|
-
</span>
|
|
549
|
-
) : (
|
|
550
|
-
<span className="truncate text-xs font-medium text-foreground">{item.company}</span>
|
|
551
|
-
)}
|
|
552
|
-
<span className="flex shrink-0 items-center gap-1.5">
|
|
553
|
-
<span
|
|
554
|
-
aria-hidden
|
|
555
|
-
className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
|
|
556
|
-
/>
|
|
557
|
-
{/* "View account" expands in on hover via an animated grid column */}
|
|
558
|
-
<span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
|
|
559
|
-
<span className="overflow-hidden">
|
|
560
|
-
<span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
|
|
561
|
-
View account
|
|
562
|
-
</span>
|
|
563
|
-
</span>
|
|
564
|
-
</span>
|
|
565
|
-
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
|
|
566
|
-
</span>
|
|
567
|
-
</button>
|
|
568
|
-
</TooltipTrigger>
|
|
569
|
-
<TooltipContent>Open account panel</TooltipContent>
|
|
570
|
-
</Tooltip>
|
|
571
|
-
</TooltipProvider>
|
|
572
|
-
{renderMetadataExtra?.(item)}
|
|
573
|
-
</div>
|
|
595
|
+
{metadataLayout === "default" ? (
|
|
596
|
+
<div className="mb-6 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
597
|
+
) : null}
|
|
574
598
|
|
|
575
599
|
{/* Signal Brief */}
|
|
576
600
|
{sections.signalBrief && (() => {
|
|
@@ -612,6 +636,11 @@ export function DetailView({
|
|
|
612
636
|
</p>
|
|
613
637
|
)}
|
|
614
638
|
|
|
639
|
+
{/* Metadata chips relocated beneath the brief (case-panel redesign). */}
|
|
640
|
+
{metadataLayout === "below-brief" ? (
|
|
641
|
+
<div className="mb-4 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
642
|
+
) : null}
|
|
643
|
+
|
|
615
644
|
{/* Before-score content slot (e.g. "Signals on Case" chips) */}
|
|
616
645
|
{renderBeforeScore?.(item)}
|
|
617
646
|
|