@handled-ai/design-system 0.20.21 → 0.20.22
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/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/timeline-activity.d.ts +45 -0
- package/dist/components/timeline-activity.js +401 -52
- package/dist/components/timeline-activity.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/timeline-activity.test.tsx +329 -0
- package/src/components/timeline-activity.tsx +516 -39
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-center justify-between border-b border-border/60 bg-muted/30 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div className={classes.outerRowGap}>\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const display = getTimelineEmailDisplay(email)\n\n return (\n <>\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n <div className=\"mt-0.5 flex items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">\n To {display.to || \"no recipient yet\"}\n {!showAllRecipients && (display.cc || display.bcc) ? (\n <>, ...</>\n ) : null}\n {showAllRecipients && display.cc ? (\n <>, <span className=\"text-muted-foreground/40\">cc</span> {display.cc}</>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <> <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}</>\n ) : null}\n </span>\n {(display.cc || display.bcc) ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n </>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div>\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink, Phone } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n /**\n * Sender signature HTML, split out of the main body by the consuming app's\n * email pipeline. When present, it is hidden behind a subtle \"Show\n * signature\" toggle (collapsed by default) so signatures don't add noise to\n * the timeline. Sanitized before rendering. Optional — when absent the body\n * renders exactly as before. Mirrors `signatureHtml` on EmailPreviewCard.\n */\n signatureHtml?: string | null\n /**\n * Quoted / trailing thread content (the \"On <date> X wrote:\" history) split\n * out of the main body. When present, it is hidden behind a subtle \"Show\n * quoted text\" toggle (collapsed by default). Sanitized before rendering.\n * Optional — when absent the body renders exactly as before.\n */\n quotedHtml?: string | null\n }\n /**\n * Gong call payload. When present, the card renders a dedicated, first-class\n * Gong call card (mirroring the `email` treatment) instead of generic\n * content: a collapsed duration + brief-snippet preview, and an expanded\n * card with the call title, start time, direction/outcome chips, the AI\n * brief, key points, next steps, and a \"View in Gong\" link. Opt-in: when\n * absent, existing consumers are unaffected. Every field except `title` is\n * optional/nullable and renders nothing when absent (no empty sections,\n * no \"null\"/\"undefined\", no \"Invalid Date\").\n */\n gongCall?: {\n /** Gong call title. */\n title: string\n /** ISO datetime of the call start. */\n startTime?: string | null\n /** Call length in seconds. Formatted to a human duration when present. */\n durationSeconds?: number | null\n /** Call direction, e.g. \"Outbound\" / \"Inbound\". */\n direction?: string | null\n /** Call outcome, e.g. \"Connected\". */\n outcome?: string | null\n /** Gong AI call brief (plain text, may be multi-paragraph). */\n brief?: string | null\n /** Gong AI key points (plain text, often newline-delimited bullets). */\n keyPoints?: string | null\n /** Gong AI highlights / next steps (plain text). */\n nextSteps?: string | null\n /** Deep link to the call in Gong. */\n url?: string | null\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-start justify-between border-b border-border/60 bg-muted/30 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const hasGongCall = !!event.gongCall\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div className={classes.outerRowGap}>\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail || hasGongCall) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasGongCall ? (\n <GongCallCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n ) : hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineGongCall = NonNullable<TimelineEvent[\"gongCall\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\n/**\n * Formats a call length in seconds to a compact human duration:\n * \"38 min\", \">= 60 min\" → \"1 h 12 min\". Returns \"\" for absent/invalid\n * input so callers can omit the chip entirely (never render \"0 min\").\n */\nfunction formatCallDuration(durationSeconds?: number | null): string {\n if (durationSeconds == null || !Number.isFinite(durationSeconds) || durationSeconds <= 0) return \"\"\n const totalMinutes = Math.round(durationSeconds / 60)\n if (totalMinutes < 1) return \"< 1 min\"\n if (totalMinutes < 60) return `${totalMinutes} min`\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`\n}\n\nfunction cleanGongText(value?: string | null): string {\n if (!value) return \"\"\n return decodeEmailDisplayText(value).trim()\n}\n\nfunction getTimelineGongCallDisplay(gongCall: TimelineGongCall) {\n const title = cleanGongText(gongCall.title)\n const startTime = formatEmailTimestamp(gongCall.startTime) ?? \"\"\n const duration = formatCallDuration(gongCall.durationSeconds)\n const direction = cleanGongText(gongCall.direction)\n const outcome = cleanGongText(gongCall.outcome)\n const brief = cleanGongText(gongCall.brief)\n const keyPoints = cleanGongText(gongCall.keyPoints)\n const nextSteps = cleanGongText(gongCall.nextSteps)\n const url = gongCall.url?.trim() || \"\"\n const snippet = brief.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n\n return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet }\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n const hasRecipients = Boolean(to || cc || bcc)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet, hasRecipients }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const display = getTimelineEmailDisplay(email)\n const hasExpandableRecipients = Boolean(display.cc || display.bcc)\n\n return (\n <>\n {/* Row 1: sender name + email, with the date right-aligned. The `gap-4`\n guarantees whitespace between the sender block and the date so they\n never visually jam together. */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n {/* Recipient lines live on their own full-width, stacked rows below the\n sender/date row. Each line truncates independently. The To-segment is\n suppressed entirely when there is no recipient data (history emails)\n rather than rendering a meaningless \"no recipient yet\" placeholder. */}\n {display.hasRecipients ? (\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <span className=\"min-w-0 flex-1 truncate\">\n {display.to ? (\n <>To {display.to}</>\n ) : (\n <>\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc || display.bcc}\n </>\n )}\n {display.to && !showAllRecipients && hasExpandableRecipients ? (\n <>, …</>\n ) : null}\n </span>\n {hasExpandableRecipients ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n aria-label={showAllRecipients ? \"Hide cc and bcc\" : \"Show cc and bcc\"}\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n {showAllRecipients && display.to && display.cc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc}\n </div>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction SuppressedHtmlSection({\n html,\n showLabel,\n hideLabel,\n slot,\n}: {\n html: string\n showLabel: string\n hideLabel: string\n slot: string\n}) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <div className=\"mt-2\" data-slot={slot}>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setOpen((value) => !value)\n }}\n aria-expanded={open}\n className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground\"\n >\n {open ? hideLabel : showLabel}\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open ? (\n <div\n data-slot={`${slot}-content`}\n className=\"mt-2 border-l-2 border-border pl-3 text-sm leading-relaxed text-muted-foreground\"\n >\n <SharedEmailBody html={html} variant=\"history\" collapseDetails={false} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n const hasSignature = Boolean(email.signatureHtml && email.signatureHtml.trim())\n const hasQuoted = Boolean(email.quotedHtml && email.quotedHtml.trim())\n\n // Fast path: no signature/quoted slots → identical output to before.\n if (!hasSignature && !hasQuoted) {\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n }\n\n return (\n <div data-slot={email.bodyHtml ? \"timeline-email-html\" : undefined}>\n {body}\n {hasSignature ? (\n <SuppressedHtmlSection\n html={email.signatureHtml as string}\n showLabel=\"Show signature\"\n hideLabel=\"Hide signature\"\n slot=\"timeline-email-signature\"\n />\n ) : null}\n {hasQuoted ? (\n <SuppressedHtmlSection\n html={email.quotedHtml as string}\n showLabel=\"Show quoted text\"\n hideLabel=\"Hide quoted text\"\n slot=\"timeline-email-quoted\"\n />\n ) : null}\n </div>\n )\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\n/**\n * Top-of-card collapse affordance. Long expanded emails are tedious to collapse\n * from the bottom alone, so the header row also carries a \"Show less\" control.\n */\nfunction CollapseTopButton({\n onClick,\n className,\n}: {\n onClick: React.MouseEventHandler<HTMLButtonElement>\n className?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Show less\"\n data-slot=\"timeline-collapse-top\"\n className={cn(\n \"shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-foreground\",\n className,\n )}\n >\n <span className=\"sr-only sm:not-sr-only\">Show less</span>\n <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction GongLinkAction({\n url,\n className,\n}: {\n url: string\n className: string\n}) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={className}\n data-slot=\"timeline-gong-link\"\n >\n View in Gong\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\n/**\n * The expanded Gong call card body — call title + start time header, optional\n * direction/outcome chips, the AI brief as primary body text, and labeled\n * Key points / Next steps subsections. Every section renders only when its\n * content is present.\n */\nfunction TimelineGongCallBody({\n display,\n labelClassName,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n labelClassName: string\n}) {\n return (\n <div className=\"space-y-3\" data-slot=\"timeline-gong-body\">\n {display.brief ? (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.brief}\n </div>\n ) : null}\n {display.keyPoints ? (\n <div data-slot=\"timeline-gong-key-points\">\n <div className={labelClassName}>Key points</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.keyPoints}\n </div>\n </div>\n ) : null}\n {display.nextSteps ? (\n <div data-slot=\"timeline-gong-next-steps\">\n <div className={labelClassName}>Next steps</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.nextSteps}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction GongCallHeader({\n display,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n}) {\n return (\n <>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/60\" />\n <span className=\"min-w-0 truncate font-semibold text-foreground text-[13px]\">{display.title}</span>\n </div>\n {display.startTime ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.startTime}</span>\n ) : null}\n </div>\n {display.duration || display.direction || display.outcome ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {display.duration ? <span data-slot=\"timeline-gong-duration\">{display.duration}</span> : null}\n {display.direction ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.direction}</span>\n ) : null}\n {display.outcome ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.outcome}</span>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction CollapsedGongCallPreview({\n display,\n className,\n actionClassName,\n onClick,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px] text-muted-foreground\">\n {display.duration ? (\n <>\n <span className=\"inline-flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {display.duration}\n </span>\n {display.snippet ? <span className=\"mx-1.5 text-muted-foreground/40\">·</span> : null}\n </>\n ) : null}\n {display.snippet ? <span>{display.snippet}</span> : null}\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction GongCallCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n const gongCall = event.gongCall as TimelineGongCall\n const display = getTimelineGongCallDisplay(gongCall)\n\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\",\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n\n <div className=\"mt-2 flex items-center gap-3\">\n {display.url ? (\n <GongLinkAction\n url={display.url}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n {expanded ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, display.url ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {display.url ? <GongLinkAction url={display.url} className={classes.actionLink} /> : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";AAsOQ,SAsOQ,UAtOR,KAgBF,YAhBE;AApOR,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,aAAa,WAAW,cAAc,aAAa;AAC5D,SAAS,aAAa,uBAAuB;AAC7C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA6GA,MAAM,eAGT;AAAA,EACF,KAAK;AAAA,IACH,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,YAAY;AAAA,IACV,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AACF;AAEA,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAqB7B,MAAM,2BAAoF;AAAA,EACxF,SAAS;AAAA,IACP,aAAa;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,uBAAuB;AAAA,EACzB;AAAA,EACA,cAAc;AAAA,IACZ,aAAa;AAAA,IACb,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,uBAAuB;AAAA,EACzB;AACF;AAQO,SAAS,iBAAiB,EAAE,QAAQ,WAAW,UAAU,UAAU,GAA0B;AAClG,SACE,oBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GAAG,gBAAc,SACvD,iBAAO,IAAI,CAAC,OAAO,UAClB;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA,QAAQ,UAAU,OAAO,SAAS;AAAA,MAClC;AAAA;AAAA,IAHK,MAAM;AAAA,EAIb,CACD,GACH;AAEJ;AAEA,SAAS,YAAY,EAAE,OAAO,KAAK,GAAgD;AAjPnF;AAkPE,MAAI,MAAM,SAAS,SAAU,QAAO;AAEpC,MAAI,MAAM,SAAS,eAAe;AAChC,WACE,qBAAC,SAAI,WAAU,gEAA+D,eAAY,gBACxF;AAAA,0BAAC,UAAK,yBAAW;AAAA,MACjB,oBAAC,UAAK,WAAU,4BAA2B,kBAAQ;AAAA,MACnD,oBAAC,UAAM,gBAAK;AAAA,OACd;AAAA,EAEJ;AAGA,QAAM,QAAO,WAAM,SAAN,YAAc;AAC3B,QAAM,mBAAkB,WAAM,aAAN,YAAmB,MAAM,OAAO,MAAM,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI;AAE7F,SACE,qBAAC,SAAI,WAAU,gEAA+D,eAAY,gBACvF;AAAA,UAAM,YACL;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,MAAM;AAAA,QACX,MAAK,WAAM,SAAN,YAAc;AAAA,QACnB,WAAU;AAAA;AAAA,IACZ,IAEA,oBAAC,UAAK,WAAU,+HACb,2BACH;AAAA,IAED,MAAM,QAAQ,oBAAC,UAAK,WAAU,+BAA+B,gBAAM,MAAK;AAAA,IACzE,oBAAC,UAAM,gBAAK;AAAA,IACZ,oBAAC,UAAK,WAAU,4BAA2B,kBAAQ;AAAA,IACnD,oBAAC,UAAM,gBAAK;AAAA,KACd;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AA/RH;AAgSE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,UAAS,WAAM,oBAAN,YAAyB,KAAK;AAC7E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,aAAa,CAAC,CAAC,MAAM;AAC3B,QAAM,WAAW,CAAC,CAAC,MAAM;AACzB,QAAM,cAAc,CAAC,CAAC,MAAM;AAC5B,QAAM,UAAU,yBAAyB,OAAO;AAEhD,QAAM,YAAY,MAAM,OAAO,aAAa,MAAM,IAAI,IAAI;AAC1D,QAAM,aAAa,YAAY,UAAU,MAAM;AAC/C,QAAM,cAAc,YAAY,UAAU,OAAO;AAEjD,SACE,qBAAC,SAAI,WAAW,QAAQ,aACrB;AAAA,KAAC,UACA,oBAAC,SAAI,WAAW,QAAQ,WAAW;AAAA,IAGrC,oBAAC,SAAI,WAAW,QAAQ,gBACtB,8BAAC,SAAI,WAAW,GAAG,QAAQ,KAAK,YAAY,WAAW,GAAG,eAAY,gBACnE,gBAAM,MACT,GACF;AAAA,IAEA,qBAAC,SAAI,WAAW,QAAQ,gBACtB;AAAA,2BAAC,SAAI,WAAW,QAAQ,iBACtB;AAAA,4BAAC,SAAI,WAAW,QAAQ,OACrB,gBAAM,OACT;AAAA,QACA,oBAAC,UAAK,WAAW,QAAQ,MACtB,gBAAM,MACT;AAAA,SACF;AAAA,MAEC,MAAM,SAAS,oBAAC,eAAY,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM;AAAA,OAEjE,cAAc,YAAY,gBAC1B,oBAAC,SAAI,WAAU,QACZ,gBAAM,gBACL,cACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,IACE,WACF;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,IAEA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,IAGF,oBAAC,SAAI,WAAW,QAAQ,uBACrB,gBAAM,SACT,GAEJ;AAAA,OAEJ;AAAA,KACF;AAEJ;AAMA,SAAS,uBAAuB,OAAgC;AAC9D,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAAU,QAAO,uBAAuB,OAAO,KAAK,CAAC;AACvG,SAAO;AACT;AAOA,SAAS,mBAAmB,iBAAyC;AACnE,MAAI,mBAAmB,QAAQ,CAAC,OAAO,SAAS,eAAe,KAAK,mBAAmB,EAAG,QAAO;AACjG,QAAM,eAAe,KAAK,MAAM,kBAAkB,EAAE;AACpD,MAAI,eAAe,EAAG,QAAO;AAC7B,MAAI,eAAe,GAAI,QAAO,GAAG,YAAY;AAC7C,QAAM,QAAQ,KAAK,MAAM,eAAe,EAAE;AAC1C,QAAM,UAAU,eAAe;AAC/B,SAAO,YAAY,IAAI,GAAG,KAAK,OAAO,GAAG,KAAK,MAAM,OAAO;AAC7D;AAEA,SAAS,cAAc,OAA+B;AACpD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,uBAAuB,KAAK,EAAE,KAAK;AAC5C;AAEA,SAAS,2BAA2B,UAA4B;AA1YhE;AA2YE,QAAM,QAAQ,cAAc,SAAS,KAAK;AAC1C,QAAM,aAAY,0BAAqB,SAAS,SAAS,MAAvC,YAA4C;AAC9D,QAAM,WAAW,mBAAmB,SAAS,eAAe;AAC5D,QAAM,YAAY,cAAc,SAAS,SAAS;AAClD,QAAM,UAAU,cAAc,SAAS,OAAO;AAC9C,QAAM,QAAQ,cAAc,SAAS,KAAK;AAC1C,QAAM,YAAY,cAAc,SAAS,SAAS;AAClD,QAAM,YAAY,cAAc,SAAS,SAAS;AAClD,QAAM,QAAM,cAAS,QAAT,mBAAc,WAAU;AACpC,QAAM,WAAU,iBAAM,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,MAA5C,mBAA+C,WAA/C,YAAyD;AAEzE,SAAO,EAAE,OAAO,WAAW,UAAU,WAAW,SAAS,OAAO,WAAW,WAAW,KAAK,QAAQ;AACrG;AAEA,SAAS,wBAAwB,OAAsB;AAzZvD;AA0ZE,QAAM,SAAS,qBAAqB,EAAE,MAAM,MAAM,MAAM,OAAO,MAAM,UAAU,CAAC;AAChF,QAAM,KAAK,kBAAkB,MAAM,EAAE,KAAK,wBAAuB,WAAM,OAAN,YAAY,EAAE;AAC/E,QAAM,KAAK,kBAAkB,MAAM,EAAE,KAAK,wBAAuB,WAAM,OAAN,YAAY,EAAE;AAC/E,QAAM,MAAM,kBAAkB,MAAM,GAAG,KAAK,wBAAuB,WAAM,QAAN,YAAa,EAAE;AAClF,QAAM,QAAO,0BAAqB,MAAM,IAAI,MAA/B,YAAoC,wBAAuB,WAAM,SAAN,YAAc,EAAE;AACxF,QAAM,UAAU,MAAM,UAAU,uBAAuB,MAAM,OAAO,IAAI;AACxE,QAAM,WAAW,uBAAuB,MAAM,IAAI;AAClD,QAAM,UAAU,iBAAiB,EAAE,UAAU,MAAM,UAAU,MAAM,SAAS,GAAG,GAAG;AAClF,QAAM,gBAAgB,QAAQ,MAAM,MAAM,GAAG;AAE7C,SAAO,EAAE,QAAQ,IAAI,IAAI,KAAK,MAAM,SAAS,UAAU,SAAS,cAAc;AAChF;AAEA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,UAAU,wBAAwB,KAAK;AAC7C,QAAM,0BAA0B,QAAQ,QAAQ,MAAM,QAAQ,GAAG;AAEjE,SACE,iCAIE;AAAA,yBAAC,SAAI,WAAU,2CACb;AAAA,2BAAC,SAAI,WAAU,uCACb;AAAA,4BAAC,UAAK,WAAU,+DAA+D,kBAAQ,OAAO,MAAK;AAAA,QAClG,QAAQ,OAAO,QACd,oBAAC,UAAK,WAAU,6CAA6C,kBAAQ,OAAO,OAAM,IAChF;AAAA,SACN;AAAA,MACC,QAAQ,OACP,oBAAC,UAAK,WAAU,+DAA+D,kBAAQ,MAAK,IAC1F;AAAA,OACN;AAAA,IAKC,QAAQ,gBACP,qBAAC,SAAI,WAAU,wCACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,6BAAC,UAAK,WAAU,2BACb;AAAA,kBAAQ,KACP,iCAAE;AAAA;AAAA,YAAI,QAAQ;AAAA,aAAG,IAEjB,iCACE;AAAA,gCAAC,UAAK,WAAU,4BAA2B,gBAAE;AAAA,YAAO;AAAA,YAAE,QAAQ,MAAM,QAAQ;AAAA,aAC9E;AAAA,UAED,QAAQ,MAAM,CAAC,qBAAqB,0BACnC,gCAAE,sBAAU,IACV;AAAA,WACN;AAAA,QACC,0BACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,mCAAqB,CAAC,SAAS,CAAC,IAAI;AAAA,YACtC;AAAA,YACA,WAAU;AAAA,YACV,cAAY,oBAAoB,oBAAoB;AAAA,YAEpD,8BAAC,eAAY,WAAW,GAAG,gCAAgC,qBAAqB,YAAY,GAAG;AAAA;AAAA,QACjG,IACE;AAAA,SACN;AAAA,MACC,qBAAqB,QAAQ,MAAM,QAAQ,KAC1C,qBAAC,SAAI,WAAU,YACb;AAAA,4BAAC,UAAK,WAAU,4BAA2B,gBAAE;AAAA,QAAO;AAAA,QAAE,QAAQ;AAAA,SAChE,IACE;AAAA,MACH,qBAAqB,QAAQ,MAC5B,qBAAC,SAAI,WAAU,YACb;AAAA,4BAAC,UAAK,WAAU,4BAA2B,iBAAG;AAAA,QAAO;AAAA,QAAE,QAAQ;AAAA,SACjE,IACE;AAAA,OACN,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAE5C,SACE,qBAAC,SAAI,WAAU,QAAO,aAAW,MAC/B;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,CAAC,MAAM;AACd,YAAE,gBAAgB;AAClB,kBAAQ,CAAC,UAAU,CAAC,KAAK;AAAA,QAC3B;AAAA,QACA,iBAAe;AAAA,QACf,WAAU;AAAA,QAET;AAAA,iBAAO,YAAY;AAAA,UACpB,oBAAC,eAAY,WAAW,GAAG,gCAAgC,QAAQ,YAAY,GAAG;AAAA;AAAA;AAAA,IACpF;AAAA,IACC,OACC;AAAA,MAAC;AAAA;AAAA,QACC,aAAW,GAAG,IAAI;AAAA,QAClB,WAAU;AAAA,QAEV,8BAAC,mBAAgB,MAAY,SAAQ,WAAU,iBAAiB,OAAO;AAAA;AAAA,IACzE,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,kBAAkB,EAAE,MAAM,GAA6B;AAC9D,QAAM,UAAU,wBAAwB,KAAK;AAC7C,QAAM,eAAe,QAAQ,aAAa,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAExF,MAAI,CAAC,MAAM,YAAY,CAAC,gBAAgB,MAAM,MAAM;AAClD,WAAO,oBAAC,SAAI,WAAU,kEAAkE,gBAAM,MAAK;AAAA,EACrG;AAEA,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,MAAM;AAAA,MACZ,MAAM;AAAA,MACN,SAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,WAAU;AAAA;AAAA,EACZ;AAGF,QAAM,eAAe,QAAQ,MAAM,iBAAiB,MAAM,cAAc,KAAK,CAAC;AAC9E,QAAM,YAAY,QAAQ,MAAM,cAAc,MAAM,WAAW,KAAK,CAAC;AAGrE,MAAI,CAAC,gBAAgB,CAAC,WAAW;AAC/B,WAAO,MAAM,WAAW,oBAAC,SAAI,aAAU,uBAAuB,gBAAK,IAAS;AAAA,EAC9E;AAEA,SACE,qBAAC,SAAI,aAAW,MAAM,WAAW,wBAAwB,QACtD;AAAA;AAAA,IACA,eACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,MAAM;AAAA,QACZ,WAAU;AAAA,QACV,WAAU;AAAA,QACV,MAAK;AAAA;AAAA,IACP,IACE;AAAA,IACH,YACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,MAAM;AAAA,QACZ,WAAU;AAAA,QACV,WAAU;AAAA,QACV,MAAK;AAAA;AAAA,IACP,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,qBAAqB,SAA4C;AACxE,MAAI,OAAO,YAAY,YAAY,OAAO,YAAY,SAAU,QAAO,uBAAuB,OAAO,OAAO,CAAC;AAC7G,SAAO;AACT;AAEA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,UAAU,QAAQ,wBAAwB,KAAK,IAAI;AACzD,QAAM,kBAAiB,mCAAS,YAAW,qBAAqB,OAAO;AAEvE,SACE,qBAAC,SAAI,WAAsB,SACzB;AAAA,yBAAC,UAAK,WAAU,gDACd;AAAA,0BAAC,UAAK,WAAU,yBAAyB,6CAAS,OAAO,MAAK;AAAA,MAC9D,oBAAC,UAAK,WAAU,mCAAkC,kBAAQ;AAAA,OACzD,mCAAS,WACR,iCACE;AAAA,4BAAC,UAAK,WAAU,yBAAyB,kBAAQ,SAAQ;AAAA,QACzD,oBAAC,UAAK,WAAU,mCAAkC,kBAAQ;AAAA,SAC5D,IACE;AAAA,MACJ,oBAAC,UAAK,WAAU,yBAAyB,0BAAe;AAAA,OAC1D;AAAA,IACA,qBAAC,YAAO,MAAK,UAAS,WAAW,iBAAiB;AAAA;AAAA,MACzC,oBAAC,eAAY,WAAU,WAAU;AAAA,OAC1C;AAAA,KACF;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,qBAAC,YAAO,MAAY,SAAkB,WAAsB;AAAA;AAAA,IAChD,oBAAC,aAAU,WAAU,WAAU;AAAA,KAC3C;AAEJ;AAMA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,cAAW;AAAA,MACX,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,0BAAyB,uBAAS;AAAA,QAClD,oBAAC,aAAU,WAAU,WAAU;AAAA;AAAA;AAAA,EACjC;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AAzqBH;AA0qBE,QAAM,eAAc,YAAO,gBAAP,YAAsB,WAAW,OAAO,KAAK;AAEjE,MAAI,eAAe;AACjB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,CAAC,MAAM;AAAE,YAAE,gBAAgB;AAAG,wBAAc;AAAA,QAAG;AAAA,QACxD;AAAA,QAEC;AAAA;AAAA,UACD,oBAAC,gBAAa,WAAU,WAAU;AAAA;AAAA;AAAA,IACpC;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,OAAO;AAAA,MACb,QAAO;AAAA,MACP,KAAI;AAAA,MACJ;AAAA,MAEC;AAAA;AAAA,QACD,oBAAC,gBAAa,WAAU,WAAU;AAAA;AAAA;AAAA,EACpC;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM;AAAA,MACN,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,MAClC;AAAA,MACA,aAAU;AAAA,MACX;AAAA;AAAA,QAEC,oBAAC,gBAAa,WAAU,WAAU;AAAA;AAAA;AAAA,EACpC;AAEJ;AAQA,SAAS,qBAAqB;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,SACE,qBAAC,SAAI,WAAU,aAAY,aAAU,sBAClC;AAAA,YAAQ,QACP,oBAAC,SAAI,WAAU,kEACZ,kBAAQ,OACX,IACE;AAAA,IACH,QAAQ,YACP,qBAAC,SAAI,aAAU,4BACb;AAAA,0BAAC,SAAI,WAAW,gBAAgB,wBAAU;AAAA,MAC1C,oBAAC,SAAI,WAAU,uEACZ,kBAAQ,WACX;AAAA,OACF,IACE;AAAA,IACH,QAAQ,YACP,qBAAC,SAAI,aAAU,4BACb;AAAA,0BAAC,SAAI,WAAW,gBAAgB,wBAAU;AAAA,MAC1C,oBAAC,SAAI,WAAU,uEACZ,kBAAQ,WACX;AAAA,OACF,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AACF,GAEG;AACD,SACE,iCACE;AAAA,yBAAC,SAAI,WAAU,0CACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA,4BAAC,SAAM,WAAU,iDAAgD;AAAA,QACjE,oBAAC,UAAK,WAAU,8DAA8D,kBAAQ,OAAM;AAAA,SAC9F;AAAA,MACC,QAAQ,YACP,oBAAC,UAAK,WAAU,+DAA+D,kBAAQ,WAAU,IAC/F;AAAA,OACN;AAAA,IACC,QAAQ,YAAY,QAAQ,aAAa,QAAQ,UAChD,qBAAC,SAAI,WAAU,kFACZ;AAAA,cAAQ,WAAW,oBAAC,UAAK,aAAU,0BAA0B,kBAAQ,UAAS,IAAU;AAAA,MACxF,QAAQ,YACP,oBAAC,UAAK,WAAU,oEAAoE,kBAAQ,WAAU,IACpG;AAAA,MACH,QAAQ,UACP,oBAAC,UAAK,WAAU,oEAAoE,kBAAQ,SAAQ,IAClG;AAAA,OACN,IACE;AAAA,KACN;AAEJ;AAEA,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,SACE,qBAAC,SAAI,WAAsB,SACzB;AAAA,yBAAC,UAAK,WAAU,sEACb;AAAA,cAAQ,WACP,iCACE;AAAA,6BAAC,UAAK,WAAU,kCACd;AAAA,8BAAC,SAAM,WAAU,WAAU;AAAA,UAC1B,QAAQ;AAAA,WACX;AAAA,QACC,QAAQ,UAAU,oBAAC,UAAK,WAAU,mCAAkC,kBAAQ,IAAU;AAAA,SACzF,IACE;AAAA,MACH,QAAQ,UAAU,oBAAC,UAAM,kBAAQ,SAAQ,IAAU;AAAA,OACtD;AAAA,IACA,qBAAC,YAAO,MAAK,UAAS,WAAW,iBAAiB;AAAA;AAAA,MACzC,oBAAC,eAAY,WAAU,WAAU;AAAA,OAC1C;AAAA,KACF;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,WAAW,MAAM;AACvB,QAAM,UAAU,2BAA2B,QAAQ;AAEnD,MAAI,YAAY,WAAW;AACzB,WACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SAAS,aAAU,sBACtE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,CAAC,YAAY;AAAA,QACf;AAAA,QACA,SAAS,MAAM,CAAC,YAAY,YAAY,IAAI;AAAA,QAE3C,qBACC,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,0CACb;AAAA,gCAAC,SAAI,WAAU,kBACb,8BAAC,kBAAe,SAAkB,GACpC;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAgB;AAClB,8BAAY,KAAK;AAAA,gBACnB;AAAA,gBACA,WAAU;AAAA;AAAA,YACZ;AAAA,aACF;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA,gBAAe;AAAA;AAAA,UACjB;AAAA,UAEA,qBAAC,SAAI,WAAU,gCACZ;AAAA,oBAAQ,MACP;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK,QAAQ;AAAA,gBACb,WAAU;AAAA;AAAA,YACZ,IACE;AAAA,YACJ;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAgB;AAClB,8BAAY,KAAK;AAAA,gBACnB;AAAA,gBACA,WAAU;AAAA;AAAA,YACZ;AAAA,aACF;AAAA,WACF,IAEA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,WAAU;AAAA,YACV,iBAAgB;AAAA;AAAA,QAClB;AAAA;AAAA,IAEJ,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SAAS,aAAU,sBACrE,qBACC,iCACE;AAAA,yBAAC,SAAI,WAAW,QAAQ,YAAY,aAAU,wBAC5C;AAAA,0BAAC,SAAI,WAAU,8CACb,8BAAC,kBAAe,SAAkB,GACpC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,CAAC,MAAM;AACd,cAAE,gBAAgB;AAClB,wBAAY,KAAK;AAAA,UACnB;AAAA,UACA,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAW,QAAQ,UAAU,aAAU,sBAC1C;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,gBAAe;AAAA;AAAA,IACjB,GACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,QAAQ,YAAY,QAAQ,eAAe,QAAQ,MAAM,oBAAoB,aAAa;AAAA,QACxG,aAAU;AAAA,QAET;AAAA,kBAAQ,MAAM,oBAAC,kBAAe,KAAK,QAAQ,KAAK,WAAW,QAAQ,YAAY,IAAK;AAAA,UACrF;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAgB;AAClB,4BAAY,KAAK;AAAA,cACnB;AAAA,cACA,WAAW,QAAQ;AAAA;AAAA,UACrB;AAAA;AAAA;AAAA,IACF;AAAA,KACF,IAEA;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,QAAQ,kBAAkB,oDAAoD;AAAA,MAC5F,iBAAiB,GAAG,QAAQ,YAAY,UAAU;AAAA,MAClD,SAAS,MAAM,YAAY,IAAI;AAAA;AAAA,EACjC,GAEJ;AAEJ;AAEA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,MAAI,YAAY,WAAW;AACzB,WACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SACnD;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,CAAC,YAAY;AAAA,QACf;AAAA,QACA,SAAS,MAAM,CAAC,YAAY,YAAY,IAAI;AAAA,QAE3C,sBAAY,MAAM,QACjB,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,0CACb;AAAA,gCAAC,SAAI,WAAU,kBACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,MAAM;AAAA,gBACb;AAAA,gBACA;AAAA;AAAA,YACF,GACF;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAgB;AAClB,8BAAY,KAAK;AAAA,gBACnB;AAAA,gBACA,WAAU;AAAA;AAAA,YACZ;AAAA,aACF;AAAA,UAEA,oBAAC,qBAAkB,OAAO,MAAM,OAAO;AAAA,UAEtC,MAAM,UACL,oBAAC,SAAI,WAAU,oEACZ,gBAAM,SACT,IACE;AAAA,UAEJ,qBAAC,SAAI,WAAU,gCACZ;AAAA,kBAAM,SACL;AAAA,cAAC;AAAA;AAAA,gBACC,QAAQ,MAAM;AAAA,gBACd,eAAe,MAAM;AAAA,gBACrB,WAAU;AAAA;AAAA,YACZ,IACE;AAAA,YACJ;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAgB;AAClB,8BAAY,KAAK;AAAA,gBACnB;AAAA,gBACA,WAAU;AAAA;AAAA,YACZ;AAAA,aACF;AAAA,WACF,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,MAAM;AAAA,YACb,SAAS,MAAM;AAAA,YACf,WAAU;AAAA,YACV,iBAAgB;AAAA;AAAA,QAClB;AAAA;AAAA,IAEJ,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SAClD,sBAAY,MAAM,QACjB,iCACE;AAAA,yBAAC,SAAI,WAAW,QAAQ,YAAY,aAAU,wBAC5C;AAAA,0BAAC,SAAI,WAAU,8CACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb;AAAA,UACA;AAAA;AAAA,MACF,GACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,CAAC,MAAM;AACd,cAAE,gBAAgB;AAClB,wBAAY,KAAK;AAAA,UACnB;AAAA,UACA,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAW,QAAQ,UAAU,aAAU,sBAC1C;AAAA,0BAAC,qBAAkB,OAAO,MAAM,OAAO;AAAA,MACtC,MAAM,UACL,oBAAC,SAAI,WAAU,yEACZ,gBAAM,SACT,IACE;AAAA,OACN;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,QAAQ,YAAY,QAAQ,eAAe,MAAM,SAAS,oBAAoB,aAAa;AAAA,QACzG,aAAU;AAAA,QAET;AAAA,gBAAM,SACL;AAAA,YAAC;AAAA;AAAA,cACC,QAAQ,MAAM;AAAA,cACd,eAAe,MAAM;AAAA,cACrB,WAAW,QAAQ;AAAA;AAAA,UACrB,IACE;AAAA,UACJ;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAgB;AAClB,4BAAY,KAAK;AAAA,cACnB;AAAA,cACA,WAAW,QAAQ;AAAA;AAAA,UACrB;AAAA;AAAA;AAAA,IACF;AAAA,KACF,IAEA;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,WAAW,GAAG,QAAQ,kBAAkB,oDAAoD;AAAA,MAC5F,iBAAiB,GAAG,QAAQ,YAAY,UAAU;AAAA,MAClD,SAAS,MAAM,YAAY,IAAI;AAAA;AAAA,EACjC,GAEJ;AAEJ;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AAhmCH;AAimCE,MAAI,YAAY,WAAW;AACzB,WACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SACnD;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,CAAC,YAAY;AAAA,QACf;AAAA,QACA,SAAS,MAAM,CAAC,YAAY,YAAY,IAAI;AAAA,QAE3C,qBACC,qBAAC,SAAI,WAAU,aACZ;AAAA,gBAAM;AAAA,UACP,qBAAC,SAAI,WAAU,gCACZ;AAAA,kBAAM,SACL;AAAA,cAAC;AAAA;AAAA,gBACC,QAAQ,MAAM;AAAA,gBACd,eAAe,MAAM;AAAA,gBACrB,WAAU;AAAA;AAAA,YACZ,IACE;AAAA,YACJ;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AAAE,oBAAE,gBAAgB;AAAG,8BAAY,KAAK;AAAA,gBAAG;AAAA,gBAC3D,WAAU;AAAA;AAAA,YACZ;AAAA,aACF;AAAA,WACF,IAEA,qBAAC,SAAI,WAAU,iEACb;AAAA,8BAAC,UAAK,WAAU,qBACb,sBAAM,YAAN,YAAiB,MAAM,SAC1B;AAAA,UACA,qBAAC,YAAO,WAAU,+HAA8H;AAAA;AAAA,YACvI,oBAAC,eAAY,WAAU,WAAU;AAAA,aAC1C;AAAA,WACF;AAAA;AAAA,IAEJ,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAW,QAAQ,eAAe,gBAAc,SAClD,qBACC,iCACE;AAAA,wBAAC,SAAI,WAAW,QAAQ,UAAU,aAAU,sBACzC,gBAAM,SACT;AAAA,IACA,qBAAC,SAAI,WAAW,GAAG,QAAQ,YAAY,QAAQ,eAAe,MAAM,SAAS,oBAAoB,aAAa,GAAG,aAAU,wBACxH;AAAA,YAAM,SACL;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,MAAM;AAAA,UACd,eAAe,MAAM;AAAA,UACrB,WAAW,QAAQ;AAAA;AAAA,MACrB,IACE;AAAA,MACJ;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,CAAC,MAAM;AAAE,cAAE,gBAAgB;AAAG,wBAAY,KAAK;AAAA,UAAG;AAAA,UAC3D,WAAW,QAAQ;AAAA;AAAA,MACrB;AAAA,OACF;AAAA,KACF,IAEA;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,QAAQ,kBAAkB,oDAAoD;AAAA,MAC5F,SAAS,MAAM,YAAY,IAAI;AAAA,MAE/B;AAAA,4BAAC,UAAK,WAAU,qBACb,sBAAM,YAAN,YAAiB,MAAM,SAC1B;AAAA,QACA,qBAAC,YAAO,MAAK,UAAS,WAAW,GAAG,QAAQ,YAAY,UAAU,GAAG;AAAA;AAAA,UAC5D,oBAAC,eAAY,WAAU,WAAU;AAAA,WAC1C;AAAA;AAAA;AAAA,EACF,GAEJ;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -416,4 +416,333 @@ describe("TimelineActivity", () => {
|
|
|
416
416
|
expectNoVisibleEscapeArtifacts(container.textContent ?? "")
|
|
417
417
|
})
|
|
418
418
|
|
|
419
|
+
// --- Empty-recipient suppression (history emails) ---
|
|
420
|
+
|
|
421
|
+
it.each(["default", "case-panel"] as const)(
|
|
422
|
+
"suppresses the To-segment in the expanded %s card when there is no recipient",
|
|
423
|
+
(variant) => {
|
|
424
|
+
const event = minimal({
|
|
425
|
+
isInteractive: true,
|
|
426
|
+
defaultExpanded: true,
|
|
427
|
+
email: {
|
|
428
|
+
from: "Priya Raman",
|
|
429
|
+
to: "",
|
|
430
|
+
subject: "Synced email",
|
|
431
|
+
body: "historical body",
|
|
432
|
+
},
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
const { container } = render(<TimelineActivity events={[event]} variant={variant} />)
|
|
436
|
+
|
|
437
|
+
expect(container.textContent).not.toContain("no recipient yet")
|
|
438
|
+
// No "To " label renders at all when recipients are empty.
|
|
439
|
+
expect(container.textContent).not.toMatch(/\bTo\s/)
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
it("suppresses the To-segment in the collapsed preview when there is no recipient", () => {
|
|
444
|
+
const event = minimal({
|
|
445
|
+
isInteractive: true,
|
|
446
|
+
defaultExpanded: false,
|
|
447
|
+
email: {
|
|
448
|
+
from: "Priya Raman",
|
|
449
|
+
to: "",
|
|
450
|
+
subject: "Synced email",
|
|
451
|
+
body: "historical body",
|
|
452
|
+
},
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
456
|
+
|
|
457
|
+
expect(container.textContent).not.toContain("no recipient yet")
|
|
458
|
+
// Collapsed preview shows sender + subject + snippet, never a To-segment.
|
|
459
|
+
expect(screen.getByRole("button", { name: /Expand/i })).toBeTruthy()
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it("still renders the recipient on the expanded card when present, stacked below the sender row", () => {
|
|
463
|
+
const event = minimal({
|
|
464
|
+
isInteractive: true,
|
|
465
|
+
defaultExpanded: true,
|
|
466
|
+
email: {
|
|
467
|
+
from: "Priya Raman",
|
|
468
|
+
fromEmail: "priya@example.com",
|
|
469
|
+
to: "Recipient Name <recipient@example.com>",
|
|
470
|
+
date: "2026-06-08T15:30:00.000Z",
|
|
471
|
+
subject: "Re: hi",
|
|
472
|
+
body: "body",
|
|
473
|
+
},
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const { container } = render(<TimelineActivity events={[event]} variant="case-panel" />)
|
|
477
|
+
const header = container.querySelector('[data-slot="timeline-card-header"]')!
|
|
478
|
+
|
|
479
|
+
expect(header.textContent).toContain("Priya Raman")
|
|
480
|
+
expect(header.textContent).toContain("To Recipient Name <recipient@example.com>")
|
|
481
|
+
// Date and the To-line are in separate block rows (no jammed concatenation).
|
|
482
|
+
expect(header.textContent).not.toMatch(/\d(?:AM|PM)To /)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
// --- Top collapse affordance ---
|
|
486
|
+
|
|
487
|
+
it.each(["default", "case-panel"] as const)(
|
|
488
|
+
"collapses the expanded %s email card from the top affordance",
|
|
489
|
+
(variant) => {
|
|
490
|
+
const event = minimal({
|
|
491
|
+
isInteractive: true,
|
|
492
|
+
defaultExpanded: true,
|
|
493
|
+
email: {
|
|
494
|
+
from: "Priya Raman",
|
|
495
|
+
to: "Dana Okafor",
|
|
496
|
+
subject: "Re: hi",
|
|
497
|
+
body: "expanded body content",
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
const { container } = render(<TimelineActivity events={[event]} variant={variant} />)
|
|
502
|
+
|
|
503
|
+
// The top collapse control is present while expanded.
|
|
504
|
+
const topCollapse = container.querySelector('[data-slot="timeline-collapse-top"]')!
|
|
505
|
+
expect(topCollapse).not.toBeNull()
|
|
506
|
+
// While expanded both the top and bottom "Show less" affordances exist.
|
|
507
|
+
expect(screen.getAllByRole("button", { name: /Show less/i }).length).toBe(2)
|
|
508
|
+
|
|
509
|
+
fireEvent.click(topCollapse)
|
|
510
|
+
|
|
511
|
+
// After collapsing, the expanded affordances are gone and the collapsed
|
|
512
|
+
// preview (Expand button) returns.
|
|
513
|
+
expect(container.querySelector('[data-slot="timeline-collapse-top"]')).toBeNull()
|
|
514
|
+
expect(screen.queryAllByRole("button", { name: /Show less/i }).length).toBe(0)
|
|
515
|
+
expect(screen.getByRole("button", { name: /Expand/i })).toBeTruthy()
|
|
516
|
+
},
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
// --- Signature / quoted-content suppression ---
|
|
520
|
+
|
|
521
|
+
it("hides signatureHtml behind a 'Show signature' toggle (collapsed by default)", () => {
|
|
522
|
+
const event = minimal({
|
|
523
|
+
isInteractive: true,
|
|
524
|
+
defaultExpanded: true,
|
|
525
|
+
email: {
|
|
526
|
+
from: "Priya Raman",
|
|
527
|
+
to: "Dana Okafor",
|
|
528
|
+
subject: "Re: hi",
|
|
529
|
+
body: "main body",
|
|
530
|
+
bodyHtml: "<p>main body</p>",
|
|
531
|
+
signatureHtml: "<p>Priya Raman | VP of Sales | 555-1234</p>",
|
|
532
|
+
},
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
536
|
+
|
|
537
|
+
// Signature content is not visible by default.
|
|
538
|
+
expect(container.textContent).not.toContain("VP of Sales")
|
|
539
|
+
const toggle = screen.getByRole("button", { name: /Show signature/i })
|
|
540
|
+
expect(toggle).toBeTruthy()
|
|
541
|
+
|
|
542
|
+
fireEvent.click(toggle)
|
|
543
|
+
expect(container.querySelector('[data-slot="timeline-email-signature-content"]')).not.toBeNull()
|
|
544
|
+
expect(container.textContent).toContain("VP of Sales")
|
|
545
|
+
expect(screen.getByRole("button", { name: /Hide signature/i })).toBeTruthy()
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
it("hides quotedHtml behind a 'Show quoted text' toggle (collapsed by default)", () => {
|
|
549
|
+
const event = minimal({
|
|
550
|
+
isInteractive: true,
|
|
551
|
+
defaultExpanded: true,
|
|
552
|
+
email: {
|
|
553
|
+
from: "Priya Raman",
|
|
554
|
+
to: "Dana Okafor",
|
|
555
|
+
subject: "Re: hi",
|
|
556
|
+
body: "main body",
|
|
557
|
+
bodyHtml: "<p>main body</p>",
|
|
558
|
+
quotedHtml: "<blockquote>On Mon, earlier message text</blockquote>",
|
|
559
|
+
},
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
const { container } = render(<TimelineActivity events={[event]} variant="case-panel" />)
|
|
563
|
+
|
|
564
|
+
expect(container.textContent).not.toContain("earlier message text")
|
|
565
|
+
const toggle = screen.getByRole("button", { name: /Show quoted text/i })
|
|
566
|
+
expect(toggle).toBeTruthy()
|
|
567
|
+
|
|
568
|
+
fireEvent.click(toggle)
|
|
569
|
+
expect(container.querySelector('[data-slot="timeline-email-quoted-content"]')).not.toBeNull()
|
|
570
|
+
expect(container.textContent).toContain("earlier message text")
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
it("renders the plain email path unchanged when signatureHtml/quotedHtml are absent", () => {
|
|
574
|
+
const event = minimal({
|
|
575
|
+
isInteractive: true,
|
|
576
|
+
defaultExpanded: true,
|
|
577
|
+
email: {
|
|
578
|
+
from: "Priya Raman",
|
|
579
|
+
to: "Dana Okafor",
|
|
580
|
+
subject: "Re: hi",
|
|
581
|
+
body: "just the body",
|
|
582
|
+
bodyHtml: "<p>just the body</p>",
|
|
583
|
+
},
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
587
|
+
|
|
588
|
+
expect(container.querySelector('[data-slot="timeline-email-signature"]')).toBeNull()
|
|
589
|
+
expect(container.querySelector('[data-slot="timeline-email-quoted"]')).toBeNull()
|
|
590
|
+
expect(screen.queryByRole("button", { name: /Show signature/i })).toBeNull()
|
|
591
|
+
expect(screen.queryByRole("button", { name: /Show quoted text/i })).toBeNull()
|
|
592
|
+
// The HTML body still renders via the existing slot.
|
|
593
|
+
expect(container.querySelector('[data-slot="timeline-email-html"]')).not.toBeNull()
|
|
594
|
+
expect(container.textContent).toContain("just the body")
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
// --- Gong call card ---
|
|
598
|
+
|
|
599
|
+
function gongEvent(overrides: Partial<NonNullable<TimelineEvent["gongCall"]>> = {}): TimelineEvent {
|
|
600
|
+
return minimal({
|
|
601
|
+
isInteractive: true,
|
|
602
|
+
defaultExpanded: true,
|
|
603
|
+
gongCall: {
|
|
604
|
+
title: "Discovery call with Acme",
|
|
605
|
+
startTime: "2026-06-08T15:30:00.000Z",
|
|
606
|
+
durationSeconds: 2280, // 38 min
|
|
607
|
+
direction: "Outbound",
|
|
608
|
+
outcome: "Connected",
|
|
609
|
+
brief: "Discussed Q3 expansion plans.\nBudget approved by finance.",
|
|
610
|
+
keyPoints: "- Wants SSO\n- 200 seats",
|
|
611
|
+
nextSteps: "Send proposal by Friday",
|
|
612
|
+
url: "https://app.gong.io/call?id=abc123",
|
|
613
|
+
...overrides,
|
|
614
|
+
},
|
|
615
|
+
})
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
it.each(["default", "case-panel"] as const)(
|
|
619
|
+
"renders the Gong call title, duration, and brief in the expanded %s card",
|
|
620
|
+
(variant) => {
|
|
621
|
+
const { container } = render(<TimelineActivity events={[gongEvent()]} variant={variant} />)
|
|
622
|
+
|
|
623
|
+
const card = container.querySelector('[data-slot="timeline-gong-card"]')
|
|
624
|
+
expect(card).not.toBeNull()
|
|
625
|
+
expect(container.textContent).toContain("Discovery call with Acme")
|
|
626
|
+
expect(container.querySelector('[data-slot="timeline-gong-duration"]')?.textContent).toContain("38 min")
|
|
627
|
+
expect(container.textContent).toContain("Discussed Q3 expansion plans.")
|
|
628
|
+
expect(container.textContent).toContain("Outbound")
|
|
629
|
+
expect(container.textContent).toContain("Connected")
|
|
630
|
+
},
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
it("formats long durations as hours and minutes", () => {
|
|
634
|
+
const { container } = render(
|
|
635
|
+
<TimelineActivity events={[gongEvent({ durationSeconds: 4320 })]} />, // 72 min
|
|
636
|
+
)
|
|
637
|
+
expect(container.querySelector('[data-slot="timeline-gong-duration"]')?.textContent).toContain("1 h 12 min")
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it("renders Key points and Next steps sections only when present", () => {
|
|
641
|
+
const { container } = render(<TimelineActivity events={[gongEvent()]} />)
|
|
642
|
+
expect(container.querySelector('[data-slot="timeline-gong-key-points"]')).not.toBeNull()
|
|
643
|
+
expect(container.querySelector('[data-slot="timeline-gong-next-steps"]')).not.toBeNull()
|
|
644
|
+
expect(container.textContent).toContain("Key points")
|
|
645
|
+
expect(container.textContent).toContain("Wants SSO")
|
|
646
|
+
expect(container.textContent).toContain("Next steps")
|
|
647
|
+
expect(container.textContent).toContain("Send proposal by Friday")
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
it("omits Key points and Next steps sections when those fields are absent", () => {
|
|
651
|
+
const { container } = render(
|
|
652
|
+
<TimelineActivity events={[gongEvent({ keyPoints: null, nextSteps: undefined })]} />,
|
|
653
|
+
)
|
|
654
|
+
expect(container.querySelector('[data-slot="timeline-gong-key-points"]')).toBeNull()
|
|
655
|
+
expect(container.querySelector('[data-slot="timeline-gong-next-steps"]')).toBeNull()
|
|
656
|
+
expect(container.textContent).not.toContain("Key points")
|
|
657
|
+
expect(container.textContent).not.toContain("Next steps")
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
it("renders a 'View in Gong' link with href + new-tab attributes when url is present", () => {
|
|
661
|
+
render(<TimelineActivity events={[gongEvent()]} />)
|
|
662
|
+
const link = screen.getByRole("link", { name: /View in Gong/i })
|
|
663
|
+
expect(link).toHaveAttribute("href", "https://app.gong.io/call?id=abc123")
|
|
664
|
+
expect(link).toHaveAttribute("target", "_blank")
|
|
665
|
+
expect(link).toHaveAttribute("rel", "noopener noreferrer")
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
it("omits the 'View in Gong' link when url is absent", () => {
|
|
669
|
+
render(<TimelineActivity events={[gongEvent({ url: null })]} />)
|
|
670
|
+
expect(screen.queryByRole("link", { name: /View in Gong/i })).toBeNull()
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
it("keeps absent Gong fields hygienic (no null/undefined/Invalid Date, no empty labels)", () => {
|
|
674
|
+
const { container } = render(
|
|
675
|
+
<TimelineActivity
|
|
676
|
+
events={[
|
|
677
|
+
gongEvent({
|
|
678
|
+
startTime: null,
|
|
679
|
+
durationSeconds: null,
|
|
680
|
+
direction: null,
|
|
681
|
+
outcome: null,
|
|
682
|
+
brief: null,
|
|
683
|
+
keyPoints: null,
|
|
684
|
+
nextSteps: null,
|
|
685
|
+
url: null,
|
|
686
|
+
}),
|
|
687
|
+
]}
|
|
688
|
+
/>,
|
|
689
|
+
)
|
|
690
|
+
const text = container.textContent ?? ""
|
|
691
|
+
expect(text).toContain("Discovery call with Acme")
|
|
692
|
+
expect(text).not.toContain("null")
|
|
693
|
+
expect(text).not.toContain("undefined")
|
|
694
|
+
expect(text).not.toContain("Invalid Date")
|
|
695
|
+
expect(text).not.toContain("NaN")
|
|
696
|
+
expect(text).not.toContain("Key points")
|
|
697
|
+
expect(text).not.toContain("Next steps")
|
|
698
|
+
expect(container.querySelector('[data-slot="timeline-gong-duration"]')).toBeNull()
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
it("collapses to a duration + snippet preview and expands the Gong card", () => {
|
|
702
|
+
const event = minimal({
|
|
703
|
+
isInteractive: true,
|
|
704
|
+
defaultExpanded: false,
|
|
705
|
+
gongCall: {
|
|
706
|
+
title: "Pricing sync",
|
|
707
|
+
durationSeconds: 2280,
|
|
708
|
+
brief: "Walked through the enterprise tier.",
|
|
709
|
+
},
|
|
710
|
+
})
|
|
711
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
712
|
+
|
|
713
|
+
// Collapsed: shows duration + snippet, no expanded body sections.
|
|
714
|
+
expect(container.textContent).toContain("38 min")
|
|
715
|
+
expect(container.textContent).toContain("Walked through the enterprise tier.")
|
|
716
|
+
expect(screen.getByRole("button", { name: /Expand/i })).toBeTruthy()
|
|
717
|
+
|
|
718
|
+
fireEvent.click(screen.getByRole("button", { name: /Expand/i }))
|
|
719
|
+
expect(container.querySelector('[data-slot="timeline-gong-body"]')).not.toBeNull()
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
it.each(["default", "case-panel"] as const)(
|
|
723
|
+
"collapses the expanded Gong %s card from the top affordance",
|
|
724
|
+
(variant) => {
|
|
725
|
+
const { container } = render(<TimelineActivity events={[gongEvent()]} variant={variant} />)
|
|
726
|
+
|
|
727
|
+
const topCollapse = container.querySelector('[data-slot="timeline-collapse-top"]')!
|
|
728
|
+
expect(topCollapse).not.toBeNull()
|
|
729
|
+
|
|
730
|
+
fireEvent.click(topCollapse)
|
|
731
|
+
|
|
732
|
+
expect(container.querySelector('[data-slot="timeline-collapse-top"]')).toBeNull()
|
|
733
|
+
expect(screen.getByRole("button", { name: /Expand/i })).toBeTruthy()
|
|
734
|
+
},
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
it("leaves email-only events unaffected by the Gong call path", () => {
|
|
738
|
+
const event = minimal({
|
|
739
|
+
isInteractive: true,
|
|
740
|
+
defaultExpanded: true,
|
|
741
|
+
email: { from: "Priya Raman", to: "Dana Okafor", subject: "Re: hi", body: "plain body" },
|
|
742
|
+
})
|
|
743
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
744
|
+
expect(container.querySelector('[data-slot="timeline-gong-card"]')).toBeNull()
|
|
745
|
+
expect(container.textContent).toContain("plain body")
|
|
746
|
+
})
|
|
747
|
+
|
|
419
748
|
})
|