@handled-ai/design-system 0.20.28 → 0.20.29
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/account-contacts-popover.d.ts +1 -1
- package/dist/components/account-contacts-popover.js +25 -20
- package/dist/components/account-contacts-popover.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/email-recipient-field.d.ts +9 -1
- package/dist/components/email-recipient-field.js +30 -4
- package/dist/components/email-recipient-field.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/suggested-actions.d.ts +2 -1
- package/dist/components/suggested-actions.js.map +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/timeline-activity.js +51 -41
- package/dist/components/timeline-activity.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/account-contacts-popover.test.tsx +71 -0
- package/src/components/__tests__/email-recipient-field.test.tsx +123 -0
- package/src/components/__tests__/timeline-activity.test.tsx +16 -0
- package/src/components/account-contacts-popover.tsx +33 -19
- package/src/components/email-recipient-field.tsx +45 -0
- package/src/components/suggested-actions.tsx +4 -3
- package/src/components/timeline-activity.tsx +7 -1
|
@@ -44,6 +44,22 @@ describe("TimelineActivity", () => {
|
|
|
44
44
|
expect(container.firstElementChild).toHaveAttribute("data-variant", "default")
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
it("renders each timeline item wrapper with scroll/focus anchors and test ids", () => {
|
|
48
|
+
const events = [minimal({ id: "evt-a" }), minimal({ id: "evt-b" })]
|
|
49
|
+
const { container } = render(<TimelineActivity events={events} />)
|
|
50
|
+
|
|
51
|
+
for (const id of ["evt-a", "evt-b"]) {
|
|
52
|
+
const wrapper = container.querySelector(`#timeline-event-${id}`)
|
|
53
|
+
expect(wrapper).not.toBeNull()
|
|
54
|
+
expect(wrapper).toHaveAttribute("data-testid", `timeline-event-${id}`)
|
|
55
|
+
expect(wrapper).toHaveAttribute("data-activity-id", id)
|
|
56
|
+
expect(wrapper).toHaveAttribute("tabindex", "-1")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The test id is also reachable via screen for click-through targeting.
|
|
60
|
+
expect(screen.getByTestId("timeline-event-evt-a")).toBeTruthy()
|
|
61
|
+
})
|
|
62
|
+
|
|
47
63
|
it("marks the root and interactive cards with the case-panel variant", () => {
|
|
48
64
|
const event = minimal({
|
|
49
65
|
isInteractive: true,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Clock,
|
|
6
6
|
ExternalLink,
|
|
7
7
|
} from "lucide-react"
|
|
8
|
+
import { cn } from "../lib/utils"
|
|
8
9
|
import type { SuggestedContact, SuggestedActionsIconMap } from "./suggested-actions"
|
|
9
10
|
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
@@ -37,7 +38,7 @@ export interface AccountContactsPopoverProps {
|
|
|
37
38
|
/** Optional replacement-selection callback. When provided, row clicks call this instead of additive onSelect/onSelectTo. */
|
|
38
39
|
onSelectSwitch?: (contact: SuggestedContact) => void
|
|
39
40
|
onViewAll?: () => void
|
|
40
|
-
onOpenRecentActivity?: () => void
|
|
41
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
41
42
|
trigger: React.ReactNode
|
|
42
43
|
iconMap?: SuggestedActionsIconMap
|
|
43
44
|
}
|
|
@@ -117,24 +118,37 @@ export function AccountContactsPopover({
|
|
|
117
118
|
<div className="truncate text-xs text-muted-foreground leading-tight">
|
|
118
119
|
{c.role} · {c.email ?? c.emails?.[0] ?? c.phone ?? c.phones?.[0] ?? ""}
|
|
119
120
|
</div>
|
|
120
|
-
{c.lastActivity && (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
{c.lastActivity && (() => {
|
|
122
|
+
const activityContent = (
|
|
123
|
+
<>
|
|
124
|
+
<Clock className="w-3 h-3 shrink-0" />
|
|
125
|
+
<span className="shrink-0 font-medium">Last activity</span>
|
|
126
|
+
<span className="shrink-0 text-muted-foreground/60">·</span>
|
|
127
|
+
<span className="shrink-0">{c.lastActivity.date}</span>
|
|
128
|
+
<span className="shrink-0 text-muted-foreground/60">·</span>
|
|
129
|
+
<span className="truncate capitalize">{c.lastActivity.type}</span>
|
|
130
|
+
</>
|
|
131
|
+
)
|
|
132
|
+
const chipBaseClass =
|
|
133
|
+
"mt-1.5 flex max-w-full items-center gap-1.5 overflow-hidden rounded-md border border-border/70 bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground"
|
|
134
|
+
return onOpenRecentActivity && c.lastActivity.timelineEventId ? (
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={(e) => {
|
|
138
|
+
e.stopPropagation()
|
|
139
|
+
onOpenRecentActivity(c)
|
|
140
|
+
setOpen(false)
|
|
141
|
+
}}
|
|
142
|
+
className={cn(chipBaseClass, "hover:text-foreground hover:bg-muted/50 transition-colors")}
|
|
143
|
+
>
|
|
144
|
+
{activityContent}
|
|
145
|
+
</button>
|
|
146
|
+
) : (
|
|
147
|
+
<div className={chipBaseClass}>
|
|
148
|
+
{activityContent}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
})()}
|
|
138
152
|
</div>
|
|
139
153
|
<div className="ml-2 flex items-center gap-1.5 shrink-0">
|
|
140
154
|
{resolvedDefaultSelectLabel && (
|
|
@@ -4,6 +4,7 @@ import * as React from "react"
|
|
|
4
4
|
import { Popover as PopoverPrimitive } from "radix-ui"
|
|
5
5
|
import {
|
|
6
6
|
ChevronDown,
|
|
7
|
+
Clock,
|
|
7
8
|
CornerDownLeft,
|
|
8
9
|
Plus,
|
|
9
10
|
Search,
|
|
@@ -63,6 +64,14 @@ export interface EmailRecipientFieldProps {
|
|
|
63
64
|
onSearch?: (query: string) => void
|
|
64
65
|
/** Shows a "Searching contacts..." indicator while async results load. */
|
|
65
66
|
searchLoading?: boolean
|
|
67
|
+
/**
|
|
68
|
+
* Opens the recent activity for a contact in the Account Panel timeline.
|
|
69
|
+
* When provided AND the contact's `lastActivity.timelineEventId` exists, the
|
|
70
|
+
* last-activity line renders as a button that calls this callback (and stops
|
|
71
|
+
* propagation so it never adds/selects the recipient). Otherwise the line
|
|
72
|
+
* renders as non-clickable text.
|
|
73
|
+
*/
|
|
74
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
function RecipientChipPill({
|
|
@@ -146,6 +155,7 @@ function ContactPickerContents({
|
|
|
146
155
|
onAddEmail,
|
|
147
156
|
onSearch,
|
|
148
157
|
searchLoading = false,
|
|
158
|
+
onOpenRecentActivity,
|
|
149
159
|
}: {
|
|
150
160
|
contacts: SuggestedContact[]
|
|
151
161
|
addedEmails: Set<string>
|
|
@@ -153,6 +163,7 @@ function ContactPickerContents({
|
|
|
153
163
|
onAddEmail: (email: string) => void
|
|
154
164
|
onSearch?: (query: string) => void
|
|
155
165
|
searchLoading?: boolean
|
|
166
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
156
167
|
}) {
|
|
157
168
|
const [query, setQuery] = React.useState("")
|
|
158
169
|
|
|
@@ -279,6 +290,38 @@ function ContactPickerContents({
|
|
|
279
290
|
{email}
|
|
280
291
|
</div>
|
|
281
292
|
) : null}
|
|
293
|
+
{contact.lastActivity ? (() => {
|
|
294
|
+
const activityContent = (
|
|
295
|
+
<>
|
|
296
|
+
<Clock className="size-3 shrink-0" />
|
|
297
|
+
<span className="shrink-0">Last activity</span>
|
|
298
|
+
<span className="shrink-0 text-muted-foreground/60">·</span>
|
|
299
|
+
<span className="shrink-0">{contact.lastActivity.date}</span>
|
|
300
|
+
<span className="shrink-0 text-muted-foreground/60">·</span>
|
|
301
|
+
<span className="truncate capitalize">{contact.lastActivity.type}</span>
|
|
302
|
+
</>
|
|
303
|
+
)
|
|
304
|
+
return onOpenRecentActivity && contact.lastActivity.timelineEventId ? (
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
onClick={(event) => {
|
|
308
|
+
event.stopPropagation()
|
|
309
|
+
onOpenRecentActivity(contact)
|
|
310
|
+
}}
|
|
311
|
+
// `pointer-events-auto` keeps the activity button clickable even
|
|
312
|
+
// when the parent row is disabled (no email / already added) and
|
|
313
|
+
// gets `pointer-events-none` — the timeline activity is still
|
|
314
|
+
// openable for those contacts.
|
|
315
|
+
className="mt-1 flex max-w-full items-center gap-1 overflow-hidden text-[11px] text-muted-foreground hover:text-foreground transition-colors pointer-events-auto"
|
|
316
|
+
>
|
|
317
|
+
{activityContent}
|
|
318
|
+
</button>
|
|
319
|
+
) : (
|
|
320
|
+
<div className="mt-1 flex max-w-full items-center gap-1 overflow-hidden text-[11px] text-muted-foreground">
|
|
321
|
+
{activityContent}
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
})() : null}
|
|
282
325
|
</div>
|
|
283
326
|
{alreadyAdded ? (
|
|
284
327
|
<span className="shrink-0 text-[10.5px] font-medium text-muted-foreground">
|
|
@@ -318,6 +361,7 @@ export function EmailRecipientField({
|
|
|
318
361
|
contactToRecipient,
|
|
319
362
|
onSearch,
|
|
320
363
|
searchLoading,
|
|
364
|
+
onOpenRecentActivity,
|
|
321
365
|
}: EmailRecipientFieldProps) {
|
|
322
366
|
const [value, setValue] = React.useState("")
|
|
323
367
|
const [pickerOpen, setPickerOpen] = React.useState(false)
|
|
@@ -465,6 +509,7 @@ export function EmailRecipientField({
|
|
|
465
509
|
}}
|
|
466
510
|
onSearch={onSearch}
|
|
467
511
|
searchLoading={searchLoading}
|
|
512
|
+
onOpenRecentActivity={onOpenRecentActivity}
|
|
468
513
|
/>
|
|
469
514
|
</PopoverPrimitive.Content>
|
|
470
515
|
</PopoverPrimitive.Portal>
|
|
@@ -84,6 +84,7 @@ export interface SuggestedContact {
|
|
|
84
84
|
lastActivity?: {
|
|
85
85
|
date: string
|
|
86
86
|
type: string
|
|
87
|
+
timelineEventId?: string
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -547,7 +548,7 @@ function EmailHeader({
|
|
|
547
548
|
onBccAdd?: (contact: SuggestedContact) => void
|
|
548
549
|
onBccRemove?: (index: number) => void
|
|
549
550
|
onOpenAccountDetails?: () => void
|
|
550
|
-
onOpenRecentActivity?: () => void
|
|
551
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
551
552
|
iconMap?: SuggestedActionsIconMap
|
|
552
553
|
showSubject?: boolean
|
|
553
554
|
accountDetailsLabel?: string
|
|
@@ -910,7 +911,7 @@ function SuggestedActionCard({
|
|
|
910
911
|
signature?: string | React.ReactNode
|
|
911
912
|
onDuplicate?: (id: number | string) => void
|
|
912
913
|
onOpenAccountDetails?: () => void
|
|
913
|
-
onOpenRecentActivity?: () => void
|
|
914
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
914
915
|
onMarkComplete?: (id: number | string) => void
|
|
915
916
|
onDispatchAgent?: (id: number | string, editedContent?: string, settings?: { aiDisclosureEnabled?: boolean; maxDurationMinutes?: string; callRecordingEnabled?: boolean; recordingNoticeEnabled?: boolean }) => void
|
|
916
917
|
onFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
|
|
@@ -1571,7 +1572,7 @@ export interface SuggestedActionsProps {
|
|
|
1571
1572
|
signature?: string | React.ReactNode
|
|
1572
1573
|
onDuplicate?: (id: number | string) => void
|
|
1573
1574
|
onOpenAccountDetails?: () => void
|
|
1574
|
-
onOpenRecentActivity?: () => void
|
|
1575
|
+
onOpenRecentActivity?: (contact: SuggestedContact) => void
|
|
1575
1576
|
onMarkComplete?: (id: number | string) => void
|
|
1576
1577
|
onDispatchAgent?: (id: number | string, editedContent?: string, settings?: { aiDisclosureEnabled?: boolean; maxDurationMinutes?: string; callRecordingEnabled?: boolean; recordingNoticeEnabled?: boolean }) => void
|
|
1577
1578
|
iconMap?: SuggestedActionsIconMap
|
|
@@ -298,7 +298,13 @@ function TimelineItem({
|
|
|
298
298
|
const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES
|
|
299
299
|
|
|
300
300
|
return (
|
|
301
|
-
<div
|
|
301
|
+
<div
|
|
302
|
+
id={`timeline-event-${event.id}`}
|
|
303
|
+
data-testid={`timeline-event-${event.id}`}
|
|
304
|
+
data-activity-id={event.id}
|
|
305
|
+
tabIndex={-1}
|
|
306
|
+
className={classes.outerRowGap}
|
|
307
|
+
>
|
|
302
308
|
{!isLast && (
|
|
303
309
|
<div className={classes.connector} />
|
|
304
310
|
)}
|