@handled-ai/design-system 0.18.56 → 0.18.58
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/data-table-filter.d.ts +18 -1
- package/dist/components/data-table-filter.js +20 -6
- package/dist/components/data-table-filter.js.map +1 -1
- package/dist/components/email-preview-card.js +17 -24
- package/dist/components/email-preview-card.js.map +1 -1
- package/dist/components/pill.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/tabs.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.js +15 -1
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/data-table-filter.test.tsx +72 -0
- package/src/components/__tests__/email-preview-card.test.tsx +7 -13
- package/src/components/data-table-filter.tsx +53 -10
- package/src/components/email-preview-card.tsx +13 -19
- package/src/components/signal-feedback-inline.tsx +18 -1
- package/src/prototype/__tests__/detail-view-opportunity-preview.test.tsx +76 -1
- package/src/prototype/__tests__/detail-view-title-slots.test.tsx +2 -23
- package/src/prototype/prototype-inbox-view.tsx +8 -0
|
@@ -43,7 +43,7 @@ export function EmailPreviewCard({
|
|
|
43
43
|
signatureHtml,
|
|
44
44
|
className,
|
|
45
45
|
}: EmailPreviewCardProps) {
|
|
46
|
-
const recipientLabel = to
|
|
46
|
+
const recipientLabel = to ? `${to}'s` : "the recipient's"
|
|
47
47
|
const bodyHtml = htmlBody ?? (textBody ? escapeHtml(textBody) : "")
|
|
48
48
|
|
|
49
49
|
return (
|
|
@@ -51,7 +51,8 @@ export function EmailPreviewCard({
|
|
|
51
51
|
<div className="flex items-start gap-2 mb-3 py-2.5 px-3 border rounded-lg bg-background text-[11.5px] text-muted-foreground">
|
|
52
52
|
<Eye className="size-4 shrink-0 mt-0.5" />
|
|
53
53
|
<span>
|
|
54
|
-
This is
|
|
54
|
+
This is how your email lands in {recipientLabel} inbox. Nothing has
|
|
55
|
+
been sent yet.
|
|
55
56
|
</span>
|
|
56
57
|
</div>
|
|
57
58
|
|
|
@@ -76,24 +77,17 @@ export function EmailPreviewCard({
|
|
|
76
77
|
<div className="text-xs text-muted-foreground shrink-0">just now</div>
|
|
77
78
|
</div>
|
|
78
79
|
|
|
79
|
-
<div
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
className="text-[13.5px] leading-relaxed whitespace-pre-wrap"
|
|
84
|
-
data-testid="email-preview-body"
|
|
85
|
-
dangerouslySetInnerHTML={{ __html: bodyHtml }}
|
|
86
|
-
/>
|
|
80
|
+
<div
|
|
81
|
+
className="px-[18px] py-2 ml-[47px] text-[13.5px] leading-relaxed whitespace-pre-wrap"
|
|
82
|
+
dangerouslySetInnerHTML={{ __html: bodyHtml }}
|
|
83
|
+
/>
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
) : null}
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
85
|
+
{signatureHtml ? (
|
|
86
|
+
<div
|
|
87
|
+
className="ml-[47px] px-[18px] pt-3 mt-3 border-t border-border/50 pb-4 text-xs leading-relaxed text-muted-foreground"
|
|
88
|
+
dangerouslySetInnerHTML={{ __html: signatureHtml }}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
97
91
|
</div>
|
|
98
92
|
</div>
|
|
99
93
|
)
|
|
@@ -90,6 +90,7 @@ interface OpportunityPreview {
|
|
|
90
90
|
description?: string | null
|
|
91
91
|
churnType?: string | null
|
|
92
92
|
churnTypeOptions?: Array<string | OpportunityPreviewOption>
|
|
93
|
+
nextStep?: string | null
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
interface OpportunityDraft {
|
|
@@ -97,6 +98,7 @@ interface OpportunityDraft {
|
|
|
97
98
|
amount: string
|
|
98
99
|
description: string
|
|
99
100
|
churnType: string
|
|
101
|
+
nextStep?: string
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
interface SignalApprovalLabels {
|
|
@@ -459,7 +461,7 @@ function formatAmountDraftValue(value: string | number | null | undefined): stri
|
|
|
459
461
|
}
|
|
460
462
|
|
|
461
463
|
function buildOpportunityDraft(preview?: OpportunityPreview): OpportunityDraft {
|
|
462
|
-
|
|
464
|
+
const draft: OpportunityDraft = {
|
|
463
465
|
closeDate: preview?.closeDateValue ?? preview?.closeDate ?? "",
|
|
464
466
|
amount: preview?.amountValue === undefined
|
|
465
467
|
? preview?.amount ?? ""
|
|
@@ -467,6 +469,10 @@ function buildOpportunityDraft(preview?: OpportunityPreview): OpportunityDraft {
|
|
|
467
469
|
description: preview?.description ?? "",
|
|
468
470
|
churnType: preview?.churnType ?? "",
|
|
469
471
|
}
|
|
472
|
+
if (preview?.nextStep != null) {
|
|
473
|
+
draft.nextStep = preview.nextStep
|
|
474
|
+
}
|
|
475
|
+
return draft
|
|
470
476
|
}
|
|
471
477
|
|
|
472
478
|
function hasEditableOpportunityPreview(preview?: OpportunityPreview): boolean {
|
|
@@ -898,6 +904,17 @@ function Actions() {
|
|
|
898
904
|
)}
|
|
899
905
|
</label>
|
|
900
906
|
|
|
907
|
+
<label className="space-y-1 text-xs">
|
|
908
|
+
<span className="font-medium text-muted-foreground">Next Step</span>
|
|
909
|
+
<textarea
|
|
910
|
+
value={opportunityDraft.nextStep ?? ""}
|
|
911
|
+
onChange={(event) => setOpportunityDraft((draft) => ({ ...draft, nextStep: event.target.value }))}
|
|
912
|
+
rows={2}
|
|
913
|
+
placeholder="No next step set"
|
|
914
|
+
className="w-full resize-none rounded-md border border-border bg-background px-2 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring"
|
|
915
|
+
/>
|
|
916
|
+
</label>
|
|
917
|
+
|
|
901
918
|
<label className="space-y-1 text-xs">
|
|
902
919
|
<span className="font-medium text-muted-foreground">Description</span>
|
|
903
920
|
<textarea
|
|
@@ -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
|
})
|
|
@@ -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,23 +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
|
-
})
|
|
53
|
-
|
|
54
|
-
expect(screen.queryByRole("button", { name: "Back" })).toBeNull()
|
|
55
|
-
expect(screen.getByRole("heading", { name: "Churn risk case title" })).toBeTruthy()
|
|
56
|
-
expect(screen.getAllByText("Acme Corp").length).toBeGreaterThan(0)
|
|
57
|
-
expect(screen.getByRole("button", { name: "Full-width quick action" })).toBeTruthy()
|
|
58
|
-
expect(screen.getByTestId("metadata-extra").textContent).toBe("Owner: Lee")
|
|
59
|
-
expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
|
|
60
|
-
expect(screen.getByRole("button", { name: "View account details for Acme Corp" })).toBeTruthy()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
42
|
it("renders supporting title text below the main title", () => {
|
|
64
43
|
renderDetailView({
|
|
65
44
|
renderTitleSubtext: (item) => <p data-testid="title-subtext">Full title: {item.title}</p>,
|
|
@@ -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
|
|