@handled-ai/design-system 0.18.36 → 0.18.38

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.
Files changed (42) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/components/draft-feedback-inline.d.ts +1 -1
  3. package/dist/components/draft-feedback-inline.js +10 -10
  4. package/dist/components/draft-feedback-inline.js.map +1 -1
  5. package/dist/components/email-composer-row.d.ts +11 -0
  6. package/dist/components/email-composer-row.js +82 -0
  7. package/dist/components/email-composer-row.js.map +1 -0
  8. package/dist/components/email-preview-card.d.ts +17 -0
  9. package/dist/components/email-preview-card.js +71 -0
  10. package/dist/components/email-preview-card.js.map +1 -0
  11. package/dist/components/email-recipient-field.d.ts +26 -0
  12. package/dist/components/email-recipient-field.js +403 -0
  13. package/dist/components/email-recipient-field.js.map +1 -0
  14. package/dist/components/email-send-bar.d.ts +22 -0
  15. package/dist/components/email-send-bar.js +66 -0
  16. package/dist/components/email-send-bar.js.map +1 -0
  17. package/dist/components/entity-panel.d.ts +1 -15
  18. package/dist/components/entity-panel.js +1 -74
  19. package/dist/components/entity-panel.js.map +1 -1
  20. package/dist/components/score-feedback.js +6 -6
  21. package/dist/components/score-feedback.js.map +1 -1
  22. package/dist/components/suggested-actions.js +5 -17
  23. package/dist/components/suggested-actions.js.map +1 -1
  24. package/dist/index.d.ts +5 -1
  25. package/dist/index.js +4 -0
  26. package/dist/index.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/__tests__/email-composer-row.test.tsx +51 -0
  29. package/src/components/__tests__/email-preview-card.test.tsx +62 -0
  30. package/src/components/__tests__/email-recipient-field.test.tsx +256 -0
  31. package/src/components/__tests__/email-send-bar.test.tsx +80 -0
  32. package/src/components/draft-feedback-inline.tsx +13 -13
  33. package/src/components/email-composer-row.tsx +47 -0
  34. package/src/components/email-preview-card.tsx +94 -0
  35. package/src/components/email-recipient-field.tsx +461 -0
  36. package/src/components/email-send-bar.tsx +95 -0
  37. package/src/components/entity-panel.tsx +0 -117
  38. package/src/components/score-feedback.tsx +7 -7
  39. package/src/components/suggested-actions.tsx +5 -19
  40. package/src/index.ts +4 -0
  41. package/src/components/__tests__/draft-feedback-inline.test.tsx +0 -72
  42. package/src/components/__tests__/suggested-actions-feedback-header.test.tsx +0 -86
@@ -149,7 +149,7 @@ function Trigger({ className }: { className?: string }) {
149
149
  className,
150
150
  )}
151
151
  >
152
- <Check className="w-3 h-3 text-foreground" />
152
+ <Check className="w-3 h-3 text-emerald-500" />
153
153
  <span className="text-[11px] text-muted-foreground">{label}</span>
154
154
  </button>
155
155
  )
@@ -163,11 +163,11 @@ function Trigger({ className }: { className?: string }) {
163
163
  className={cn(
164
164
  "p-1.5 rounded transition-colors",
165
165
  thumbState === "up"
166
- ? "bg-muted text-foreground"
166
+ ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
167
167
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
168
168
  )}
169
169
  >
170
- <ThumbsUp className="w-3.5 h-3.5" />
170
+ <ThumbsUp className="w-3.5 h-3.5" fill={thumbState === "up" ? "currentColor" : "none"} />
171
171
  </button>
172
172
  <button
173
173
  type="button"
@@ -175,11 +175,11 @@ function Trigger({ className }: { className?: string }) {
175
175
  className={cn(
176
176
  "p-1.5 rounded transition-colors",
177
177
  thumbState === "down"
178
- ? "bg-muted text-foreground"
178
+ ? "bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
179
179
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
180
180
  )}
181
181
  >
182
- <ThumbsDown className="w-3.5 h-3.5" />
182
+ <ThumbsDown className="w-3.5 h-3.5" fill={thumbState === "down" ? "currentColor" : "none"} />
183
183
  </button>
184
184
  </div>
185
185
  )
@@ -219,8 +219,8 @@ function Panel({ className }: { className?: string }) {
219
219
  "px-2.5 py-1 rounded-full text-[11px] font-medium border transition-colors",
220
220
  selectedPills.includes(pill)
221
221
  ? thumbState === "up"
222
- ? "bg-muted text-foreground border-border"
223
- : "bg-red-50 text-red-700 border-red-200 dark:bg-red-950/30 dark:text-red-300 dark:border-red-800"
222
+ ? "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800"
223
+ : "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800"
224
224
  : "bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground"
225
225
  )}
226
226
  >
@@ -925,14 +925,6 @@ function SuggestedActionCard({
925
925
  )
926
926
  const [showAiEdit, setShowAiEdit] = React.useState(false)
927
927
  const [feedbackOpen, setFeedbackOpen] = React.useState(false)
928
- const [feedbackDirection, setFeedbackDirection] = React.useState<"up" | "down" | null>(null)
929
- const handleThumbClick = (dir: "up" | "down") => {
930
- if (feedbackOpen && feedbackDirection === dir) {
931
- setFeedbackOpen(false); setFeedbackDirection(null)
932
- } else {
933
- setFeedbackDirection(dir); setFeedbackOpen(true)
934
- }
935
- }
936
928
  const [followUpEnabled, setFollowUpEnabled] = React.useState(action.followUp?.enabled ?? false)
937
929
  const [threadExpanded, setThreadExpanded] = React.useState(false)
938
930
  const [expandedMessageId, setExpandedMessageId] = React.useState<string | null>(null)
@@ -1024,22 +1016,18 @@ function SuggestedActionCard({
1024
1016
  </div>
1025
1017
  <div className="flex items-center gap-1.5">
1026
1018
  <button
1027
- onClick={() => handleThumbClick("up")}
1019
+ onClick={() => setFeedbackOpen(!feedbackOpen)}
1028
1020
  className={`p-1.5 rounded transition-colors ${
1029
- feedbackOpen && feedbackDirection === "up"
1030
- ? "bg-muted text-foreground"
1021
+ feedbackOpen
1022
+ ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
1031
1023
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
1032
1024
  }`}
1033
1025
  >
1034
1026
  <ThumbsUp className="w-3.5 h-3.5" />
1035
1027
  </button>
1036
1028
  <button
1037
- onClick={() => handleThumbClick("down")}
1038
- className={`p-1.5 rounded transition-colors ${
1039
- feedbackOpen && feedbackDirection === "down"
1040
- ? "bg-muted text-foreground"
1041
- : "hover:bg-muted text-muted-foreground hover:text-foreground"
1042
- }`}
1029
+ onClick={() => setFeedbackOpen(!feedbackOpen)}
1030
+ className="p-1.5 rounded transition-colors hover:bg-muted text-muted-foreground hover:text-foreground"
1043
1031
  >
1044
1032
  <ThumbsDown className="w-3.5 h-3.5" />
1045
1033
  </button>
@@ -1059,8 +1047,6 @@ function SuggestedActionCard({
1059
1047
  {feedbackOpen && (
1060
1048
  <div className="px-5 py-3 border-b border-border/40 animate-in fade-in slide-in-from-top-2 duration-200">
1061
1049
  <DraftFeedbackInline
1062
- key={`feedback-${feedbackDirection}`}
1063
- initialDirection={feedbackDirection}
1064
1050
  onRegenerateRequest={(pills, detail) => {
1065
1051
  onFeedback?.("down", pills, detail)
1066
1052
  }}
package/src/index.ts CHANGED
@@ -21,6 +21,10 @@ export * from "./components/badge"
21
21
  export * from "./components/button"
22
22
  export * from "./components/card"
23
23
  export * from "./components/case-panel-email-composer"
24
+ export * from "./components/email-composer-row"
25
+ export * from "./components/email-preview-card"
26
+ export * from "./components/email-recipient-field"
27
+ export * from "./components/email-send-bar"
24
28
  export * from "./components/case-panel-activity-timeline"
25
29
  export * from "./components/case-panel-detail"
26
30
  export * from "./components/case-panel-why"
@@ -1,72 +0,0 @@
1
- /**
2
- * Tests for DraftFeedbackInline.
3
- *
4
- * Covers:
5
- * - Clicking the up thumb activates the positive state (monochrome bg-muted)
6
- * - Clicking the down thumb activates the negative state (restrained red)
7
- * - Thumb icons render outline-only (never fill="currentColor")
8
- * - initialDirection="down" renders the down state pre-selected
9
- * - initialDirection="up" renders the up state pre-selected
10
- */
11
-
12
- import { describe, it, expect } from "vitest"
13
- import React from "react"
14
- import { render, screen, fireEvent } from "@testing-library/react"
15
- import { DraftFeedbackInline } from "../draft-feedback-inline"
16
-
17
- describe("DraftFeedbackInline", () => {
18
- it("renders idle with no expanded feedback area", () => {
19
- render(<DraftFeedbackInline />)
20
- expect(screen.getByText("How's this draft?")).toBeTruthy()
21
- expect(screen.queryByText("What worked well?")).toBeNull()
22
- expect(screen.queryByText("What needs improvement?")).toBeNull()
23
- })
24
-
25
- it("activates the positive state with monochrome tokens when up is clicked", () => {
26
- const { container } = render(<DraftFeedbackInline />)
27
- const buttons = container.querySelectorAll("button")
28
- const upButton = buttons[0] as HTMLButtonElement
29
- fireEvent.click(upButton)
30
-
31
- expect(screen.getByText("What worked well?")).toBeTruthy()
32
- expect(upButton.className).toContain("bg-muted")
33
- expect(upButton.className).toContain("text-foreground")
34
- expect(upButton.className).not.toContain("emerald")
35
- })
36
-
37
- it("activates the negative state with restrained red when down is clicked", () => {
38
- const { container } = render(<DraftFeedbackInline />)
39
- const buttons = container.querySelectorAll("button")
40
- const downButton = buttons[1] as HTMLButtonElement
41
- fireEvent.click(downButton)
42
-
43
- expect(screen.getByText("What needs improvement?")).toBeTruthy()
44
- expect(downButton.className).toContain("bg-muted")
45
- expect(downButton.className).toContain("text-foreground")
46
- expect(downButton.className).not.toContain("bg-red")
47
- })
48
-
49
- it("renders thumb icons outline-only (never fill=currentColor)", () => {
50
- const { container } = render(<DraftFeedbackInline />)
51
- const buttons = container.querySelectorAll("button")
52
- // Activate up so the icon would be "filled" under the old behavior.
53
- fireEvent.click(buttons[0] as HTMLButtonElement)
54
-
55
- const svgs = container.querySelectorAll("svg")
56
- svgs.forEach((svg) => {
57
- expect(svg.getAttribute("fill")).not.toBe("currentColor")
58
- })
59
- })
60
-
61
- it("renders the down state pre-selected with initialDirection='down'", () => {
62
- render(<DraftFeedbackInline initialDirection="down" />)
63
- expect(screen.getByText("What needs improvement?")).toBeTruthy()
64
- expect(screen.queryByText("What worked well?")).toBeNull()
65
- })
66
-
67
- it("renders the up state pre-selected with initialDirection='up'", () => {
68
- render(<DraftFeedbackInline initialDirection="up" />)
69
- expect(screen.getByText("What worked well?")).toBeTruthy()
70
- expect(screen.queryByText("What needs improvement?")).toBeNull()
71
- })
72
- })
@@ -1,86 +0,0 @@
1
- /**
2
- * Tests for the SuggestedActions card header thumbs (feedback direction).
3
- *
4
- * Covers:
5
- * - Clicking up opens the feedback panel with direction "up"
6
- * - Clicking down opens the feedback panel with direction "down"
7
- * - Clicking the same direction again closes the panel
8
- * - Switching direction remounts DraftFeedbackInline (panel stays open, content switches)
9
- */
10
-
11
- import { describe, it, expect } from "vitest"
12
- import React from "react"
13
- import { render, screen, fireEvent } from "@testing-library/react"
14
- import { SuggestedActions } from "../suggested-actions"
15
- import type { SuggestedAction } from "../suggested-actions"
16
-
17
- const action: SuggestedAction = {
18
- id: 1,
19
- type: "email",
20
- label: "Send follow-up email",
21
- status: "pending",
22
- content: "<p>Hello there.</p>",
23
- }
24
-
25
- // The header thumbs are the only buttons that wrap a thumbs-up/down SVG and
26
- // live in the card header. We resolve them by locating the lucide icon class.
27
- function getHeaderThumbs(container: HTMLElement) {
28
- const up = container.querySelector("button .lucide-thumbs-up")?.closest("button")
29
- const down = container.querySelector("button .lucide-thumbs-down")?.closest("button")
30
- return { up: up as HTMLButtonElement, down: down as HTMLButtonElement }
31
- }
32
-
33
- describe("SuggestedActions header thumbs", () => {
34
- it("does not show the feedback panel initially", () => {
35
- render(<SuggestedActions actions={[action]} />)
36
- expect(screen.queryByText("How's this draft?")).toBeNull()
37
- })
38
-
39
- it("opens the feedback panel with direction up when up is clicked", () => {
40
- const { container } = render(<SuggestedActions actions={[action]} />)
41
- const { up } = getHeaderThumbs(container)
42
- fireEvent.click(up)
43
-
44
- expect(screen.getByText("How's this draft?")).toBeTruthy()
45
- expect(screen.getByText("What worked well?")).toBeTruthy()
46
- expect(screen.queryByText("What needs improvement?")).toBeNull()
47
- expect(up.className).toContain("bg-muted")
48
- expect(up.className).toContain("text-foreground")
49
- })
50
-
51
- it("opens the feedback panel with direction down when down is clicked", () => {
52
- const { container } = render(<SuggestedActions actions={[action]} />)
53
- const { down } = getHeaderThumbs(container)
54
- fireEvent.click(down)
55
-
56
- expect(screen.getByText("How's this draft?")).toBeTruthy()
57
- expect(screen.getByText("What needs improvement?")).toBeTruthy()
58
- expect(screen.queryByText("What worked well?")).toBeNull()
59
- expect(down.className).toContain("bg-muted")
60
- expect(down.className).toContain("text-foreground")
61
- })
62
-
63
- it("closes the panel when the same direction is clicked again", () => {
64
- const { container } = render(<SuggestedActions actions={[action]} />)
65
- const { up } = getHeaderThumbs(container)
66
- fireEvent.click(up)
67
- expect(screen.getByText("How's this draft?")).toBeTruthy()
68
-
69
- fireEvent.click(up)
70
- expect(screen.queryByText("How's this draft?")).toBeNull()
71
- })
72
-
73
- it("switches direction (remounts DraftFeedbackInline) while keeping the panel open", () => {
74
- const { container } = render(<SuggestedActions actions={[action]} />)
75
- const { up, down } = getHeaderThumbs(container)
76
-
77
- fireEvent.click(up)
78
- expect(screen.getByText("What worked well?")).toBeTruthy()
79
-
80
- fireEvent.click(down)
81
- // Panel still open, but now showing the negative (down) variant.
82
- expect(screen.getByText("How's this draft?")).toBeTruthy()
83
- expect(screen.getByText("What needs improvement?")).toBeTruthy()
84
- expect(screen.queryByText("What worked well?")).toBeNull()
85
- })
86
- })