@handled-ai/design-system 0.19.0-rc.2 → 0.19.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/detail-view.js +1 -1
- package/dist/components/detail-view.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-feedback-inline.d.ts +2 -0
- package/dist/components/signal-feedback-inline.js +21 -4
- package/dist/components/signal-feedback-inline.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/signal-priority-popover.js +16 -7
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/virtualized-data-table.js +4 -4
- package/dist/components/virtualized-data-table.js.map +1 -1
- package/dist/index.d.ts +1 -1
- 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 +1 -1
- package/dist/prototype/prototype-inbox-view.js +17 -1
- 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-QJngMAj7.d.ts → signal-priority-popover-CZitE9xq.d.ts} +11 -2
- package/package.json +1 -1
- package/src/components/__tests__/signal-priority-popover.test.tsx +41 -4
- package/src/components/__tests__/virtualized-data-table-resize.test.tsx +18 -0
- package/src/components/detail-view.tsx +3 -1
- package/src/components/signal-feedback-inline.tsx +18 -1
- package/src/components/signal-priority-popover.tsx +19 -6
- package/src/components/virtualized-data-table.tsx +4 -4
- package/src/index.ts +1 -1
- package/src/prototype/__tests__/detail-view-opportunity-preview.test.tsx +76 -1
- package/src/prototype/__tests__/detail-view-score-why.test.tsx +34 -0
- package/src/prototype/__tests__/detail-view-title-slots.test.tsx +2 -30
- package/src/prototype/prototype-config.ts +5 -1
- package/src/prototype/prototype-inbox-view.tsx +10 -0
|
@@ -50,8 +50,14 @@ export interface PriorityFactor {
|
|
|
50
50
|
rationale: string
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export type SignalPriorityScoreDisplay = "label" | "number"
|
|
54
|
+
|
|
53
55
|
export interface SignalPriorityPopoverProps {
|
|
54
56
|
score: number
|
|
57
|
+
/** Controls whether the overall score number is shown in the popover header. @default "number" */
|
|
58
|
+
scoreDisplay?: SignalPriorityScoreDisplay
|
|
59
|
+
/** Short formula/context label shown beside the contributing factors heading. @default "Priority factors" */
|
|
60
|
+
formulaLabel?: string
|
|
55
61
|
urgencyLabel?: SignalScoreUrgencyLabel
|
|
56
62
|
/** Synthesis sentence displayed in the popover head. */
|
|
57
63
|
urgencyExplanation?: string
|
|
@@ -282,6 +288,8 @@ export function SignalPriorityPopover({
|
|
|
282
288
|
initialFactorFeedback,
|
|
283
289
|
onFactorFeedback,
|
|
284
290
|
initialPriorityFeedback,
|
|
291
|
+
scoreDisplay = "number",
|
|
292
|
+
formulaLabel = "Priority factors",
|
|
285
293
|
}: SignalPriorityPopoverProps) {
|
|
286
294
|
const urgencyLabel = providedLabel ?? getUrgencyLevel(score)
|
|
287
295
|
const scoreRange = getUrgencyRange(urgencyLabel)
|
|
@@ -330,15 +338,20 @@ export function SignalPriorityPopover({
|
|
|
330
338
|
data-testid="priority-popover-content"
|
|
331
339
|
>
|
|
332
340
|
{/* Head section */}
|
|
333
|
-
<div className="p-4 pb-3">
|
|
341
|
+
<div className="p-4 pb-3" data-testid="priority-popover-header">
|
|
334
342
|
<div className="flex items-start justify-between gap-3">
|
|
335
343
|
<p className="text-sm font-semibold text-foreground">
|
|
336
344
|
Why this is {urgencyLabel.toLowerCase()} priority
|
|
337
345
|
</p>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
346
|
+
{scoreDisplay === "number" && (
|
|
347
|
+
<span
|
|
348
|
+
className="text-2xl font-bold tabular-nums text-foreground"
|
|
349
|
+
data-testid="priority-overall-score"
|
|
350
|
+
>
|
|
351
|
+
{score}
|
|
352
|
+
<span className="text-sm font-normal text-muted-foreground">/100</span>
|
|
353
|
+
</span>
|
|
354
|
+
)}
|
|
342
355
|
</div>
|
|
343
356
|
|
|
344
357
|
{/* Band indicator */}
|
|
@@ -369,7 +382,7 @@ export function SignalPriorityPopover({
|
|
|
369
382
|
</span>
|
|
370
383
|
<span className="flex items-center gap-1 text-[10px] text-muted-foreground">
|
|
371
384
|
<Info className="h-3 w-3" />
|
|
372
|
-
|
|
385
|
+
{formulaLabel}
|
|
373
386
|
</span>
|
|
374
387
|
</div>
|
|
375
388
|
|
|
@@ -375,10 +375,10 @@ export function VirtualizedDataTable<TData>({
|
|
|
375
375
|
onMouseDown={header.getResizeHandler()}
|
|
376
376
|
onTouchStart={header.getResizeHandler()}
|
|
377
377
|
className={cn(
|
|
378
|
-
"absolute right-0 top-0 h-full w-
|
|
379
|
-
"after:absolute after:right-
|
|
380
|
-
"after:bg-
|
|
381
|
-
header.column.getIsResizing() && "after:bg-primary/
|
|
378
|
+
"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none",
|
|
379
|
+
"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full",
|
|
380
|
+
"after:bg-border/70 after:transition-colors hover:after:bg-primary/60",
|
|
381
|
+
header.column.getIsResizing() && "after:bg-primary/70",
|
|
382
382
|
)}
|
|
383
383
|
role="separator"
|
|
384
384
|
aria-orientation="vertical"
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ export * from "./components/related-record-action-card"
|
|
|
52
52
|
export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from "./components/feedback-primitives"
|
|
53
53
|
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from "./components/feedback-primitives"
|
|
54
54
|
export { SignalPriorityPopover } from "./components/signal-priority-popover"
|
|
55
|
-
export type { SignalPriorityPopoverProps, PriorityFactor } from "./components/signal-priority-popover"
|
|
55
|
+
export type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from "./components/signal-priority-popover"
|
|
56
56
|
export * from "./components/filter-chip"
|
|
57
57
|
export * from "./components/inbox-row"
|
|
58
58
|
export * from "./components/inbox-toolbar"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { describe, expect, it, vi } from "vitest"
|
|
3
3
|
import { fireEvent, render, screen } from "@testing-library/react"
|
|
4
4
|
|
|
5
5
|
import { DetailView, type DetailViewProps } from "../prototype-inbox-view"
|
|
@@ -66,6 +66,7 @@ describe("DetailView opportunity approval preview", () => {
|
|
|
66
66
|
description: "Initial description",
|
|
67
67
|
churnType: "Churn Risk",
|
|
68
68
|
churnTypeOptions: ["Churn Risk", "Win Back"],
|
|
69
|
+
nextStep: "Initial next step",
|
|
69
70
|
})
|
|
70
71
|
},
|
|
71
72
|
})}
|
|
@@ -84,7 +85,81 @@ describe("DetailView opportunity approval preview", () => {
|
|
|
84
85
|
expect((await screen.findByLabelText("Close Date") as HTMLInputElement).value).toBe("2026-06-30")
|
|
85
86
|
expect((screen.getByLabelText("Amount") as HTMLInputElement).value).toBe("$75,000")
|
|
86
87
|
expect((screen.getByLabelText("Churn Type") as HTMLSelectElement).value).toBe("Churn Risk")
|
|
88
|
+
expect((screen.getByLabelText("Next Step") as HTMLTextAreaElement).value).toBe("Initial next step")
|
|
87
89
|
expect((screen.getByLabelText("Description") as HTMLTextAreaElement).value).toBe("Initial description")
|
|
88
90
|
expect((screen.getByRole("button", { name: /confirm/i }) as HTMLButtonElement).disabled).toBe(false)
|
|
89
91
|
})
|
|
92
|
+
|
|
93
|
+
it("passes edited next step with the opportunity draft on confirm", async () => {
|
|
94
|
+
const onSignalApprove = vi.fn()
|
|
95
|
+
|
|
96
|
+
render(
|
|
97
|
+
<DetailView
|
|
98
|
+
{...baseProps({
|
|
99
|
+
getSignalApprovalState: () => "confirming",
|
|
100
|
+
opportunityPreview: {
|
|
101
|
+
name: "Churn Risk - WIT-825 Fixture Account",
|
|
102
|
+
accountName: "WIT-825 Fixture Account",
|
|
103
|
+
stage: "Prospecting",
|
|
104
|
+
closeDate: "Jun 30, 2026",
|
|
105
|
+
closeDateValue: "2026-06-30",
|
|
106
|
+
amount: "$75,000",
|
|
107
|
+
amountValue: 75000,
|
|
108
|
+
description: "Initial description",
|
|
109
|
+
churnType: "Churn Risk",
|
|
110
|
+
churnTypeOptions: ["Churn Risk", "Win Back"],
|
|
111
|
+
nextStep: "Initial next step",
|
|
112
|
+
},
|
|
113
|
+
onSignalApprove,
|
|
114
|
+
})}
|
|
115
|
+
/>,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const nextStepInput = screen.getByLabelText("Next Step") as HTMLTextAreaElement
|
|
119
|
+
fireEvent.change(nextStepInput, { target: { value: "Schedule validation call" } })
|
|
120
|
+
fireEvent.click(screen.getByRole("button", { name: /confirm/i }))
|
|
121
|
+
|
|
122
|
+
expect(onSignalApprove).toHaveBeenCalledWith(baseItem, {
|
|
123
|
+
closeDate: "2026-06-30",
|
|
124
|
+
amount: "$75,000",
|
|
125
|
+
churnType: "Churn Risk",
|
|
126
|
+
description: "Initial description",
|
|
127
|
+
nextStep: "Schedule validation call",
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("omits untouched empty next step from the opportunity draft on confirm", async () => {
|
|
132
|
+
const onSignalApprove = vi.fn()
|
|
133
|
+
|
|
134
|
+
render(
|
|
135
|
+
<DetailView
|
|
136
|
+
{...baseProps({
|
|
137
|
+
getSignalApprovalState: () => "confirming",
|
|
138
|
+
opportunityPreview: {
|
|
139
|
+
name: "Churn Risk - WIT-825 Fixture Account",
|
|
140
|
+
accountName: "WIT-825 Fixture Account",
|
|
141
|
+
stage: "Prospecting",
|
|
142
|
+
closeDate: "Jun 30, 2026",
|
|
143
|
+
closeDateValue: "2026-06-30",
|
|
144
|
+
amount: "$75,000",
|
|
145
|
+
amountValue: 75000,
|
|
146
|
+
description: "Initial description",
|
|
147
|
+
churnType: "Churn Risk",
|
|
148
|
+
churnTypeOptions: ["Churn Risk", "Win Back"],
|
|
149
|
+
},
|
|
150
|
+
onSignalApprove,
|
|
151
|
+
})}
|
|
152
|
+
/>,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
expect((screen.getByLabelText("Next Step") as HTMLTextAreaElement).value).toBe("")
|
|
156
|
+
fireEvent.click(screen.getByRole("button", { name: /confirm/i }))
|
|
157
|
+
|
|
158
|
+
expect(onSignalApprove).toHaveBeenCalledWith(baseItem, {
|
|
159
|
+
closeDate: "2026-06-30",
|
|
160
|
+
amount: "$75,000",
|
|
161
|
+
churnType: "Churn Risk",
|
|
162
|
+
description: "Initial description",
|
|
163
|
+
})
|
|
164
|
+
})
|
|
90
165
|
})
|
|
@@ -89,6 +89,40 @@ describe("DetailView corrected compact score WHY UX", () => {
|
|
|
89
89
|
expect(screen.queryByText("How's this score?")).toBeNull();
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
|
|
93
|
+
it("passes priorityScoreDisplay and priorityFormulaLabel through to the priority popover", () => {
|
|
94
|
+
render(
|
|
95
|
+
<DetailView
|
|
96
|
+
{...baseProps({
|
|
97
|
+
getSignalScore: () =>
|
|
98
|
+
makeSignalScore({
|
|
99
|
+
urgencyLabel: "High",
|
|
100
|
+
priorityScoreDisplay: "label",
|
|
101
|
+
priorityFormulaLabel: "Priority = weighted signals + calibration",
|
|
102
|
+
priorityFactors: [
|
|
103
|
+
{
|
|
104
|
+
key: "treasury",
|
|
105
|
+
label: "Treasury activity",
|
|
106
|
+
icon: "wallet",
|
|
107
|
+
tone: "alert",
|
|
108
|
+
direction: "raises",
|
|
109
|
+
score: 85,
|
|
110
|
+
rationale: "Full liquidation detected.",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
})}
|
|
115
|
+
/>,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
fireEvent.click(screen.getByRole("button", { name: /high priority/i }));
|
|
119
|
+
|
|
120
|
+
expect(screen.queryByTestId("priority-overall-score")).toBeNull();
|
|
121
|
+
expect(screen.getByTestId("priority-popover-header").textContent).not.toContain("82/100");
|
|
122
|
+
expect(screen.getByText("Priority = weighted signals + calibration")).toBeInTheDocument();
|
|
123
|
+
expect(screen.getByTestId("factor-row-treasury").textContent).toContain("85/100");
|
|
124
|
+
});
|
|
125
|
+
|
|
92
126
|
it("keeps signal WHY chips collapsed by default and excludes factor-only buckets", () => {
|
|
93
127
|
render(
|
|
94
128
|
<DetailView
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { render, screen } from "@testing-library/react"
|
|
2
|
+
import { describe, expect, it, vi } from "vitest"
|
|
3
3
|
|
|
4
4
|
import { DetailView } from "../prototype-inbox-view"
|
|
5
5
|
import type { DetailViewProps } from "../prototype-inbox-view"
|
|
@@ -14,10 +14,6 @@ const baseItem = {
|
|
|
14
14
|
tag1: "Signal",
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
cleanup()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
17
|
function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
|
|
22
18
|
const props: DetailViewProps = {
|
|
23
19
|
item: baseItem,
|
|
@@ -43,30 +39,6 @@ function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
|
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
describe("DetailView title slots", () => {
|
|
46
|
-
it("does not render the inert detail Back button while preserving title and metadata slots", () => {
|
|
47
|
-
renderDetailView({
|
|
48
|
-
renderTitleActionRow: () => (
|
|
49
|
-
<button type="button">Full-width quick action</button>
|
|
50
|
-
),
|
|
51
|
-
renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
|
|
52
|
-
accountDetailsButtonLabel: (item) => (
|
|
53
|
-
<>
|
|
54
|
-
<span>View account details</span>
|
|
55
|
-
<span>{item.company}</span>
|
|
56
|
-
</>
|
|
57
|
-
),
|
|
58
|
-
getAccountDetailsButtonAriaLabel: (item) => `View account details for ${item.company}`,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
expect(screen.queryByRole("button", { name: "Back" })).toBeNull()
|
|
62
|
-
expect(screen.getByRole("heading", { name: "Churn risk case title" })).toBeTruthy()
|
|
63
|
-
expect(screen.getAllByText("Acme Corp").length).toBeGreaterThan(0)
|
|
64
|
-
expect(screen.getByRole("button", { name: "Full-width quick action" })).toBeTruthy()
|
|
65
|
-
expect(screen.getByTestId("metadata-extra").textContent).toBe("Owner: Lee")
|
|
66
|
-
expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
|
|
67
|
-
expect(screen.getByRole("button", { name: "View account details for Acme Corp" })).toBeTruthy()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
42
|
it("renders supporting title text below the main title", () => {
|
|
71
43
|
renderDetailView({
|
|
72
44
|
renderTitleSubtext: (item) => <p data-testid="title-subtext">Full title: {item.title}</p>,
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
import type { TimelineEvent } from "../components/timeline-activity"
|
|
16
16
|
import type { ApprovalState, OpportunityDraft } from "../components/signal-feedback-inline"
|
|
17
17
|
import type { LucideIcon } from "lucide-react"
|
|
18
|
-
import type { PriorityFactor } from "../components/signal-priority-popover"
|
|
18
|
+
import type { PriorityFactor, SignalPriorityScoreDisplay } from "../components/signal-priority-popover"
|
|
19
19
|
import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "../components/feedback-primitives"
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
@@ -109,6 +109,10 @@ export interface SignalScoreData {
|
|
|
109
109
|
confidence: number
|
|
110
110
|
urgencyLabel?: SignalScoreUrgencyLabel
|
|
111
111
|
urgencyExplanation?: string
|
|
112
|
+
/** Controls whether the priority popover header shows the raw overall score number. @default "number" */
|
|
113
|
+
priorityScoreDisplay?: SignalPriorityScoreDisplay
|
|
114
|
+
/** Formula/context label shown in the priority popover factor section. @default "Priority factors" */
|
|
115
|
+
priorityFormulaLabel?: string
|
|
112
116
|
explanationBuckets?: SignalScoreExplanationBucket[]
|
|
113
117
|
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
114
118
|
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
@@ -488,6 +488,14 @@ export function DetailView({
|
|
|
488
488
|
<div className="pb-8">
|
|
489
489
|
{/* Header */}
|
|
490
490
|
<div className="mb-4 flex items-center gap-2">
|
|
491
|
+
<button
|
|
492
|
+
type="button"
|
|
493
|
+
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
494
|
+
>
|
|
495
|
+
<ArrowLeft className="h-3.5 w-3.5" />
|
|
496
|
+
Back
|
|
497
|
+
</button>
|
|
498
|
+
<span className="text-muted-foreground/40">·</span>
|
|
491
499
|
<span className="text-xs text-muted-foreground">{item.company}</span>
|
|
492
500
|
</div>
|
|
493
501
|
|
|
@@ -511,6 +519,8 @@ export function DetailView({
|
|
|
511
519
|
score={signalData.score}
|
|
512
520
|
urgencyLabel={signalData.urgencyLabel}
|
|
513
521
|
urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
|
|
522
|
+
scoreDisplay={signalData.priorityScoreDisplay}
|
|
523
|
+
formulaLabel={signalData.priorityFormulaLabel}
|
|
514
524
|
factors={signalData.priorityFactors ?? []}
|
|
515
525
|
metaText={undefined}
|
|
516
526
|
feedbackChips={signalData.priorityFeedbackChips}
|