@handled-ai/design-system 0.18.56 → 0.18.57

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.
@@ -43,7 +43,7 @@ export function EmailPreviewCard({
43
43
  signatureHtml,
44
44
  className,
45
45
  }: EmailPreviewCardProps) {
46
- const recipientLabel = to || "the recipient"
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 a preview for {recipientLabel}. Nothing has been sent yet.
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 className="flex gap-3 px-[18px] pt-3 pb-4">
80
- <div className="size-9 shrink-0" aria-hidden="true" />
81
- <div className="min-w-0 flex-1 space-y-3">
82
- <div
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
- {signatureHtml ? (
89
- <div
90
- className="border-t border-border/50 pt-3 text-xs leading-relaxed text-muted-foreground"
91
- data-testid="email-preview-signature"
92
- dangerouslySetInnerHTML={{ __html: signatureHtml }}
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 {
@@ -466,6 +468,7 @@ function buildOpportunityDraft(preview?: OpportunityPreview): OpportunityDraft {
466
468
  : formatAmountDraftValue(preview.amountValue),
467
469
  description: preview?.description ?? "",
468
470
  churnType: preview?.churnType ?? "",
471
+ nextStep: preview?.nextStep ?? "",
469
472
  }
470
473
  }
471
474
 
@@ -898,6 +901,17 @@ function Actions() {
898
901
  )}
899
902
  </label>
900
903
 
904
+ <label className="space-y-1 text-xs">
905
+ <span className="font-medium text-muted-foreground">Next Step</span>
906
+ <textarea
907
+ value={opportunityDraft.nextStep}
908
+ onChange={(event) => setOpportunityDraft((draft) => ({ ...draft, nextStep: event.target.value }))}
909
+ rows={2}
910
+ placeholder="No next step set"
911
+ 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"
912
+ />
913
+ </label>
914
+
901
915
  <label className="space-y-1 text-xs">
902
916
  <span className="font-medium text-muted-foreground">Description</span>
903
917
  <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,46 @@ 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
+ })
90
130
  })
@@ -1,5 +1,5 @@
1
- import { cleanup, render, screen } from "@testing-library/react"
2
- import { afterEach, describe, expect, it, vi } from "vitest"
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">&middot;</span>
491
499
  <span className="text-xs text-muted-foreground">{item.company}</span>
492
500
  </div>
493
501