@handled-ai/design-system 0.18.38 → 0.18.40
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/charts/chart.d.ts +1 -1
- package/dist/components/draft-feedback-inline.d.ts +1 -1
- package/dist/components/draft-feedback-inline.js +10 -10
- package/dist/components/draft-feedback-inline.js.map +1 -1
- package/dist/components/email-recipient-field.js +107 -166
- package/dist/components/email-recipient-field.js.map +1 -1
- package/dist/components/related-record-action-card.d.ts +19 -0
- package/dist/components/related-record-action-card.js +150 -0
- package/dist/components/related-record-action-card.js.map +1 -0
- package/dist/components/score-feedback.js +6 -6
- package/dist/components/score-feedback.js.map +1 -1
- package/dist/components/suggested-actions.js +17 -5
- package/dist/components/suggested-actions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/draft-feedback-inline.test.tsx +72 -0
- package/src/components/__tests__/email-recipient-field.test.tsx +116 -1
- package/src/components/__tests__/related-record-action-card.test.tsx +123 -0
- package/src/components/__tests__/suggested-actions-feedback-header.test.tsx +86 -0
- package/src/components/draft-feedback-inline.tsx +13 -13
- package/src/components/email-recipient-field.tsx +55 -101
- package/src/components/related-record-action-card.tsx +169 -0
- package/src/components/score-feedback.tsx +7 -7
- package/src/components/suggested-actions.tsx +19 -5
- package/src/index.ts +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import {
|
|
4
|
+
import { Popover as PopoverPrimitive } from "radix-ui"
|
|
5
5
|
import {
|
|
6
6
|
Check,
|
|
7
7
|
ChevronDown,
|
|
@@ -110,82 +110,27 @@ function RecipientChipPill({
|
|
|
110
110
|
)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Contents of the contact picker dropdown. Rendered inside a Radix
|
|
114
|
+
// `Popover.Content` so its focus scope pushes onto the focus-scope stack and
|
|
115
|
+
// PAUSES any parent modal's scope (e.g. the quick-action Dialog). This is what
|
|
116
|
+
// makes the search input typeable: a plain `createPortal(..., document.body)`
|
|
117
|
+
// element renders outside the Dialog's `DialogContent`, so the Dialog's
|
|
118
|
+
// FocusScope kept yanking focus back (input un-typeable) and its modal
|
|
119
|
+
// `pointer-events: none` on <body> left the portal click-dead. A stacked Radix
|
|
120
|
+
// Popover layer gets `pointer-events: auto` and its own (paused-parent) focus
|
|
121
|
+
// scope, fixing both. See WIT-800 / WIT-770.
|
|
122
|
+
function ContactPickerContents({
|
|
115
123
|
contacts,
|
|
116
124
|
addedEmails,
|
|
117
125
|
onSelect,
|
|
118
126
|
onAddEmail,
|
|
119
|
-
onClose,
|
|
120
127
|
}: {
|
|
121
|
-
triggerRef: React.RefObject<HTMLElement | null>
|
|
122
128
|
contacts: SuggestedContact[]
|
|
123
129
|
addedEmails: Set<string>
|
|
124
130
|
onSelect: (contact: SuggestedContact) => void
|
|
125
131
|
onAddEmail: (email: string) => void
|
|
126
|
-
onClose: () => void
|
|
127
132
|
}) {
|
|
128
|
-
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
129
|
-
const searchRef = React.useRef<HTMLInputElement>(null)
|
|
130
133
|
const [query, setQuery] = React.useState("")
|
|
131
|
-
const [style, setStyle] = React.useState<React.CSSProperties>({
|
|
132
|
-
position: "fixed",
|
|
133
|
-
top: -9999,
|
|
134
|
-
left: -9999,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
React.useEffect(() => {
|
|
138
|
-
const trigger = triggerRef.current
|
|
139
|
-
if (!trigger) return
|
|
140
|
-
const rect = trigger.getBoundingClientRect()
|
|
141
|
-
const width = Math.min(448, window.innerWidth - 32)
|
|
142
|
-
let left = rect.left
|
|
143
|
-
if (left + width > window.innerWidth - 16) {
|
|
144
|
-
left = window.innerWidth - 16 - width
|
|
145
|
-
}
|
|
146
|
-
if (left < 16) left = 16
|
|
147
|
-
const popoverHeight = 280
|
|
148
|
-
const spaceBelow = window.innerHeight - rect.bottom - 4
|
|
149
|
-
const spaceAbove = rect.top - 4
|
|
150
|
-
const placeAbove = spaceBelow < popoverHeight && spaceAbove > spaceBelow
|
|
151
|
-
let top = placeAbove ? rect.top - popoverHeight - 4 : rect.bottom + 4
|
|
152
|
-
if (top < 16) top = 16
|
|
153
|
-
setStyle({ position: "fixed", top, left, width })
|
|
154
|
-
}, [triggerRef])
|
|
155
|
-
|
|
156
|
-
React.useEffect(() => {
|
|
157
|
-
searchRef.current?.focus()
|
|
158
|
-
const trigger = triggerRef.current
|
|
159
|
-
return () => {
|
|
160
|
-
if (trigger && typeof trigger.focus === "function") {
|
|
161
|
-
trigger.focus()
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}, [triggerRef])
|
|
165
|
-
|
|
166
|
-
React.useEffect(() => {
|
|
167
|
-
function handleMouseDown(event: MouseEvent) {
|
|
168
|
-
if (
|
|
169
|
-
containerRef.current &&
|
|
170
|
-
!containerRef.current.contains(event.target as Node) &&
|
|
171
|
-
!triggerRef.current?.contains(event.target as Node)
|
|
172
|
-
) {
|
|
173
|
-
onClose()
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function handleKeyDown(event: KeyboardEvent) {
|
|
177
|
-
if (event.key === "Escape") {
|
|
178
|
-
event.stopPropagation()
|
|
179
|
-
onClose()
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
document.addEventListener("mousedown", handleMouseDown)
|
|
183
|
-
document.addEventListener("keydown", handleKeyDown)
|
|
184
|
-
return () => {
|
|
185
|
-
document.removeEventListener("mousedown", handleMouseDown)
|
|
186
|
-
document.removeEventListener("keydown", handleKeyDown)
|
|
187
|
-
}
|
|
188
|
-
}, [onClose, triggerRef])
|
|
189
134
|
|
|
190
135
|
const normalizedQuery = query.trim().toLowerCase()
|
|
191
136
|
const filtered = normalizedQuery
|
|
@@ -209,16 +154,11 @@ function ContactPickerPopover({
|
|
|
209
154
|
}
|
|
210
155
|
}
|
|
211
156
|
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
ref={containerRef}
|
|
215
|
-
style={style}
|
|
216
|
-
className="bg-background border rounded-lg shadow-xl z-50 pointer-events-auto"
|
|
217
|
-
>
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
218
159
|
<div className="flex items-center gap-2 px-3 py-2.5 border-b border-border/50">
|
|
219
160
|
<Search className="size-4 text-muted-foreground shrink-0" />
|
|
220
161
|
<input
|
|
221
|
-
ref={searchRef}
|
|
222
162
|
autoFocus
|
|
223
163
|
value={query}
|
|
224
164
|
onChange={(event) => setQuery(event.target.value)}
|
|
@@ -229,7 +169,16 @@ function ContactPickerPopover({
|
|
|
229
169
|
</div>
|
|
230
170
|
|
|
231
171
|
<div role="listbox" className="max-h-[208px] overflow-y-auto p-1">
|
|
232
|
-
{
|
|
172
|
+
{contacts.length === 0 ? (
|
|
173
|
+
<div className="px-3 py-5 text-center text-[13px] text-muted-foreground">
|
|
174
|
+
<div className="font-medium text-foreground/80">
|
|
175
|
+
No contacts for this account
|
|
176
|
+
</div>
|
|
177
|
+
<div className="mt-1">
|
|
178
|
+
Type an email address above and press Enter to add a recipient.
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
) : filtered.length === 0 ? (
|
|
233
182
|
<div className="px-3 py-4 text-center text-[13px] text-muted-foreground">
|
|
234
183
|
<div>No contact matches ‘{query}’.</div>
|
|
235
184
|
{queryIsEmail ? (
|
|
@@ -296,8 +245,7 @@ function ContactPickerPopover({
|
|
|
296
245
|
<CornerDownLeft className="size-3 shrink-0" />
|
|
297
246
|
<span>Type an address and press Enter to add someone not listed.</span>
|
|
298
247
|
</div>
|
|
299
|
-
|
|
300
|
-
document.body,
|
|
248
|
+
</>
|
|
301
249
|
)
|
|
302
250
|
}
|
|
303
251
|
|
|
@@ -317,7 +265,6 @@ export function EmailRecipientField({
|
|
|
317
265
|
}: EmailRecipientFieldProps) {
|
|
318
266
|
const [value, setValue] = React.useState("")
|
|
319
267
|
const [pickerOpen, setPickerOpen] = React.useState(false)
|
|
320
|
-
const contactsTriggerRef = React.useRef<HTMLButtonElement>(null)
|
|
321
268
|
|
|
322
269
|
const hasUnconfirmed = recipients.some((r) => !r.confirmed)
|
|
323
270
|
const state: "default" | "amber" =
|
|
@@ -418,16 +365,37 @@ export function EmailRecipientField({
|
|
|
418
365
|
{showPicker || showCcBcc ? (
|
|
419
366
|
<div className="flex gap-1.5 mt-2">
|
|
420
367
|
{showPicker ? (
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
368
|
+
<PopoverPrimitive.Root open={pickerOpen} onOpenChange={setPickerOpen}>
|
|
369
|
+
<PopoverPrimitive.Trigger asChild>
|
|
370
|
+
<button
|
|
371
|
+
type="button"
|
|
372
|
+
className="inline-flex items-center gap-1 h-6 px-2.5 rounded-md border border-border bg-background text-[11px] font-medium text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors duration-[120ms]"
|
|
373
|
+
>
|
|
374
|
+
<Users className="size-3" />
|
|
375
|
+
Contacts
|
|
376
|
+
<ChevronDown className="size-3" />
|
|
377
|
+
</button>
|
|
378
|
+
</PopoverPrimitive.Trigger>
|
|
379
|
+
<PopoverPrimitive.Portal>
|
|
380
|
+
<PopoverPrimitive.Content
|
|
381
|
+
side="bottom"
|
|
382
|
+
align="start"
|
|
383
|
+
sideOffset={4}
|
|
384
|
+
collisionPadding={16}
|
|
385
|
+
className="z-50 w-[min(448px,calc(100vw-2rem))] rounded-lg border bg-background shadow-xl p-0"
|
|
386
|
+
>
|
|
387
|
+
<ContactPickerContents
|
|
388
|
+
contacts={contacts}
|
|
389
|
+
addedEmails={added}
|
|
390
|
+
onSelect={selectContact}
|
|
391
|
+
onAddEmail={(email) => {
|
|
392
|
+
addEmail(email)
|
|
393
|
+
setPickerOpen(false)
|
|
394
|
+
}}
|
|
395
|
+
/>
|
|
396
|
+
</PopoverPrimitive.Content>
|
|
397
|
+
</PopoverPrimitive.Portal>
|
|
398
|
+
</PopoverPrimitive.Root>
|
|
431
399
|
) : null}
|
|
432
400
|
{showCcBcc ? (
|
|
433
401
|
<button
|
|
@@ -441,20 +409,6 @@ export function EmailRecipientField({
|
|
|
441
409
|
) : null}
|
|
442
410
|
</div>
|
|
443
411
|
) : null}
|
|
444
|
-
|
|
445
|
-
{pickerOpen ? (
|
|
446
|
-
<ContactPickerPopover
|
|
447
|
-
triggerRef={contactsTriggerRef}
|
|
448
|
-
contacts={contacts}
|
|
449
|
-
addedEmails={added}
|
|
450
|
-
onSelect={selectContact}
|
|
451
|
-
onAddEmail={(email) => {
|
|
452
|
-
addEmail(email)
|
|
453
|
-
setPickerOpen(false)
|
|
454
|
-
}}
|
|
455
|
-
onClose={() => setPickerOpen(false)}
|
|
456
|
-
/>
|
|
457
|
-
) : null}
|
|
458
412
|
</div>
|
|
459
413
|
</div>
|
|
460
414
|
)
|
|
@@ -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-
|
|
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-
|
|
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"
|
|
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-
|
|
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"
|
|
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-
|
|
223
|
-
: "bg-red-
|
|
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={() =>
|
|
1027
|
+
onClick={() => handleThumbClick("up")}
|
|
1020
1028
|
className={`p-1.5 rounded transition-colors ${
|
|
1021
|
-
feedbackOpen
|
|
1022
|
-
? "bg-
|
|
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={() =>
|
|
1030
|
-
className=
|
|
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
|
@@ -46,6 +46,7 @@ export * from "./components/dialog"
|
|
|
46
46
|
export * from "./components/dropdown-menu"
|
|
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"
|