@handled-ai/design-system 0.18.37 → 0.18.39

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 (30) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/components/badge.d.ts +1 -1
  3. package/dist/components/button.d.ts +1 -1
  4. package/dist/components/draft-feedback-inline.d.ts +1 -1
  5. package/dist/components/draft-feedback-inline.js +10 -10
  6. package/dist/components/draft-feedback-inline.js.map +1 -1
  7. package/dist/components/email-recipient-field.js +3 -0
  8. package/dist/components/email-recipient-field.js.map +1 -1
  9. package/dist/components/pill.d.ts +1 -1
  10. package/dist/components/related-record-action-card.d.ts +19 -0
  11. package/dist/components/related-record-action-card.js +150 -0
  12. package/dist/components/related-record-action-card.js.map +1 -0
  13. package/dist/components/score-feedback.js +6 -6
  14. package/dist/components/score-feedback.js.map +1 -1
  15. package/dist/components/suggested-actions.js +17 -5
  16. package/dist/components/suggested-actions.js.map +1 -1
  17. package/dist/components/tabs.d.ts +1 -1
  18. package/dist/index.d.ts +5 -4
  19. package/dist/index.js +5 -4
  20. package/dist/index.js.map +1 -1
  21. package/package.json +1 -5
  22. package/src/components/__tests__/draft-feedback-inline.test.tsx +72 -0
  23. package/src/components/__tests__/related-record-action-card.test.tsx +123 -0
  24. package/src/components/__tests__/suggested-actions-feedback-header.test.tsx +86 -0
  25. package/src/components/draft-feedback-inline.tsx +13 -13
  26. package/src/components/email-recipient-field.tsx +5 -0
  27. package/src/components/related-record-action-card.tsx +169 -0
  28. package/src/components/score-feedback.tsx +7 -7
  29. package/src/components/suggested-actions.tsx +19 -5
  30. package/src/index.ts +5 -4
@@ -0,0 +1,169 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ExternalLink } from "lucide-react"
5
+
6
+ import { BRAND_ICONS } from "../lib/icons"
7
+ import { cn } from "../lib/utils"
8
+
9
+ export type RelatedRecordActionCardKind = "case" | "account" | "opportunity" | "salesforce" | "generic"
10
+
11
+ export type RelatedRecordActionIcon = "salesforce" | React.ReactNode
12
+
13
+ export interface RelatedRecordActionCardProps {
14
+ kind: RelatedRecordActionCardKind
15
+ label: string
16
+ subtitle?: string
17
+ disabledReason?: string
18
+ href?: string
19
+ external?: boolean
20
+ icon?: RelatedRecordActionIcon
21
+ onClick?: React.MouseEventHandler<HTMLButtonElement>
22
+ className?: string
23
+ testId?: string
24
+ }
25
+
26
+ function renderActionIcon(icon: RelatedRecordActionIcon | undefined, kind: RelatedRecordActionCardKind) {
27
+ if (icon === "salesforce" || kind === "salesforce") {
28
+ return (
29
+ <img
30
+ src={BRAND_ICONS.salesforce}
31
+ alt="Salesforce"
32
+ className="h-4 w-4 object-contain"
33
+ draggable={false}
34
+ />
35
+ )
36
+ }
37
+
38
+ if (icon) {
39
+ return icon
40
+ }
41
+
42
+ return <span aria-hidden="true">{kind.slice(0, 1).toUpperCase()}</span>
43
+ }
44
+
45
+ export function RelatedRecordActionCard({
46
+ kind,
47
+ label,
48
+ subtitle,
49
+ disabledReason,
50
+ href,
51
+ external,
52
+ icon,
53
+ onClick,
54
+ className,
55
+ testId,
56
+ }: RelatedRecordActionCardProps) {
57
+ const isDisabled = Boolean(disabledReason) || (!href && !onClick)
58
+
59
+ const content = (
60
+ <>
61
+ <span
62
+ data-slot="related-record-action-card-icon"
63
+ className={cn(
64
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-md border text-xs font-semibold transition-colors",
65
+ isDisabled
66
+ ? "border-border/60 bg-muted/40 text-muted-foreground"
67
+ : "border-border bg-muted/30 text-muted-foreground group-hover:bg-muted/60 group-active:text-primary"
68
+ )}
69
+ >
70
+ {renderActionIcon(icon, kind)}
71
+ </span>
72
+ <span className="min-w-0 flex-1">
73
+ <span
74
+ data-slot="related-record-action-card-label"
75
+ className={cn(
76
+ "block truncate text-sm font-medium transition-colors",
77
+ isDisabled ? "text-muted-foreground" : "text-foreground group-active:text-primary"
78
+ )}
79
+ >
80
+ {label}
81
+ </span>
82
+ {subtitle && (
83
+ <span
84
+ data-slot="related-record-action-card-subtitle"
85
+ className="mt-0.5 block truncate text-xs text-muted-foreground"
86
+ >
87
+ {subtitle}
88
+ </span>
89
+ )}
90
+ {disabledReason && (
91
+ <span
92
+ data-slot="related-record-action-card-disabled-reason"
93
+ className="mt-1 block text-xs text-muted-foreground"
94
+ >
95
+ {disabledReason}
96
+ </span>
97
+ )}
98
+ </span>
99
+ {external && (
100
+ <>
101
+ <span className="sr-only">opens in a new tab</span>
102
+ <ExternalLink
103
+ aria-hidden="true"
104
+ data-slot="related-record-action-card-external-icon"
105
+ className={cn(
106
+ "h-3.5 w-3.5 shrink-0 text-muted-foreground transition-colors",
107
+ isDisabled ? "" : "group-active:text-primary"
108
+ )}
109
+ />
110
+ </>
111
+ )}
112
+ </>
113
+ )
114
+
115
+ if (isDisabled) {
116
+ return (
117
+ <div
118
+ aria-disabled="true"
119
+ data-kind={kind}
120
+ data-slot="related-record-action-card"
121
+ data-testid={testId}
122
+ className={cn(
123
+ "group flex w-full items-center gap-3 rounded-lg border px-3 py-2 text-left transition-colors",
124
+ "cursor-not-allowed border-border/60 bg-muted/20 text-muted-foreground opacity-70",
125
+ className
126
+ )}
127
+ >
128
+ {content}
129
+ </div>
130
+ )
131
+ }
132
+
133
+ if (href) {
134
+ return (
135
+ <a
136
+ href={href}
137
+ target={external ? "_blank" : undefined}
138
+ rel={external ? "noopener noreferrer" : undefined}
139
+ data-kind={kind}
140
+ data-slot="related-record-action-card"
141
+ data-testid={testId}
142
+ className={cn(
143
+ "group flex w-full items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 text-left transition-colors",
144
+ "cursor-pointer hover:bg-muted/50 hover:border-border/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:text-primary",
145
+ className
146
+ )}
147
+ >
148
+ {content}
149
+ </a>
150
+ )
151
+ }
152
+
153
+ return (
154
+ <button
155
+ type="button"
156
+ onClick={onClick}
157
+ data-kind={kind}
158
+ data-slot="related-record-action-card"
159
+ data-testid={testId}
160
+ className={cn(
161
+ "group flex w-full items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 text-left transition-colors",
162
+ "cursor-pointer hover:bg-muted/50 hover:border-border/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:text-primary",
163
+ className
164
+ )}
165
+ >
166
+ {content}
167
+ </button>
168
+ )
169
+ }
@@ -149,7 +149,7 @@ function Trigger({ className }: { className?: string }) {
149
149
  className,
150
150
  )}
151
151
  >
152
- <Check className="w-3 h-3 text-emerald-500" />
152
+ <Check className="w-3 h-3 text-foreground" />
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-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
166
+ ? "bg-muted text-foreground"
167
167
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
168
168
  )}
169
169
  >
170
- <ThumbsUp className="w-3.5 h-3.5" fill={thumbState === "up" ? "currentColor" : "none"} />
170
+ <ThumbsUp className="w-3.5 h-3.5" />
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-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
178
+ ? "bg-muted text-foreground"
179
179
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
180
180
  )}
181
181
  >
182
- <ThumbsDown className="w-3.5 h-3.5" fill={thumbState === "down" ? "currentColor" : "none"} />
182
+ <ThumbsDown className="w-3.5 h-3.5" />
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-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"
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"
224
224
  : "bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground"
225
225
  )}
226
226
  >
@@ -925,6 +925,14 @@ 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
+ }
928
936
  const [followUpEnabled, setFollowUpEnabled] = React.useState(action.followUp?.enabled ?? false)
929
937
  const [threadExpanded, setThreadExpanded] = React.useState(false)
930
938
  const [expandedMessageId, setExpandedMessageId] = React.useState<string | null>(null)
@@ -1016,18 +1024,22 @@ function SuggestedActionCard({
1016
1024
  </div>
1017
1025
  <div className="flex items-center gap-1.5">
1018
1026
  <button
1019
- onClick={() => setFeedbackOpen(!feedbackOpen)}
1027
+ onClick={() => handleThumbClick("up")}
1020
1028
  className={`p-1.5 rounded transition-colors ${
1021
- feedbackOpen
1022
- ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
1029
+ feedbackOpen && feedbackDirection === "up"
1030
+ ? "bg-muted text-foreground"
1023
1031
  : "hover:bg-muted text-muted-foreground hover:text-foreground"
1024
1032
  }`}
1025
1033
  >
1026
1034
  <ThumbsUp className="w-3.5 h-3.5" />
1027
1035
  </button>
1028
1036
  <button
1029
- onClick={() => setFeedbackOpen(!feedbackOpen)}
1030
- className="p-1.5 rounded transition-colors hover:bg-muted text-muted-foreground hover:text-foreground"
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
+ }`}
1031
1043
  >
1032
1044
  <ThumbsDown className="w-3.5 h-3.5" />
1033
1045
  </button>
@@ -1047,6 +1059,8 @@ function SuggestedActionCard({
1047
1059
  {feedbackOpen && (
1048
1060
  <div className="px-5 py-3 border-b border-border/40 animate-in fade-in slide-in-from-top-2 duration-200">
1049
1061
  <DraftFeedbackInline
1062
+ key={`feedback-${feedbackDirection}`}
1063
+ initialDirection={feedbackDirection}
1050
1064
  onRegenerateRequest={(pills, detail) => {
1051
1065
  onFeedback?.("down", pills, detail)
1052
1066
  }}
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"
@@ -40,12 +44,9 @@ export * from "./components/detail-view"
40
44
  export * from "./components/detail-drawer"
41
45
  export * from "./components/dialog"
42
46
  export * from "./components/dropdown-menu"
43
- export * from "./components/email-composer-row"
44
- export * from "./components/email-preview-card"
45
- export * from "./components/email-recipient-field"
46
- export * from "./components/email-send-bar"
47
47
  export * from "./components/empty-state"
48
48
  export * from "./components/entity-panel"
49
+ export * from "./components/related-record-action-card"
49
50
  export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from "./components/feedback-primitives"
50
51
  export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from "./components/feedback-primitives"
51
52
  export { SignalPriorityPopover } from "./components/signal-priority-popover"