@handled-ai/design-system 0.20.10 → 0.20.11
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/comment-composer.js +49 -69
- package/dist/components/comment-composer.js.map +1 -1
- package/dist/components/email-body.js +39 -1
- package/dist/components/email-body.js.map +1 -1
- package/dist/components/timeline-activity.d.ts +1 -0
- package/dist/components/timeline-activity.js +55 -24
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/prototype/prototype-inbox-view.js +62 -107
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/comment-composer.test.tsx +3 -7
- package/src/components/__tests__/email-body.test.tsx +32 -0
- package/src/components/__tests__/timeline-activity.test.tsx +36 -0
- package/src/components/comment-composer.tsx +14 -26
- package/src/components/email-body.tsx +46 -1
- package/src/components/timeline-activity.tsx +43 -10
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +3 -3
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +14 -17
- package/src/prototype/prototype-inbox-view.tsx +44 -72
|
@@ -34,84 +34,64 @@ function CommentComposer({
|
|
|
34
34
|
"data-slot": "comment-composer",
|
|
35
35
|
"data-open": open ? "true" : void 0,
|
|
36
36
|
className: cn(
|
|
37
|
-
"flex items-start gap-
|
|
37
|
+
"border-border bg-background flex items-start gap-2 rounded-lg border px-2 py-1.5 transition-colors",
|
|
38
|
+
open && "ring-ring/30 ring-2",
|
|
38
39
|
className
|
|
39
40
|
),
|
|
40
41
|
children: [
|
|
41
|
-
/* @__PURE__ */ jsxs(Avatar, { size: "sm", className: "mt-
|
|
42
|
+
/* @__PURE__ */ jsxs(Avatar, { size: "sm", className: "mt-px", children: [
|
|
42
43
|
(author == null ? void 0 : author.avatarUrl) ? /* @__PURE__ */ jsx(AvatarImage, { src: author.avatarUrl, alt: (_a = author.name) != null ? _a : "You" }) : null,
|
|
43
|
-
/* @__PURE__ */ jsx(AvatarFallback, { className: "bg-
|
|
44
|
+
/* @__PURE__ */ jsx(AvatarFallback, { className: "bg-muted text-muted-foreground text-[10px] font-medium uppercase", children: getInitials({ name: author == null ? void 0 : author.name, email: author == null ? void 0 : author.email }) })
|
|
44
45
|
] }),
|
|
45
|
-
/* @__PURE__ */ jsxs(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
Textarea,
|
|
49
|
+
{
|
|
50
|
+
"data-slot": "comment-composer-input",
|
|
51
|
+
value: text,
|
|
52
|
+
onChange: (e) => setText(e.target.value),
|
|
53
|
+
onFocus: () => setFocused(true),
|
|
54
|
+
placeholder,
|
|
55
|
+
rows: open ? 3 : 1,
|
|
56
|
+
className: cn(
|
|
57
|
+
"resize-none border-0 bg-transparent px-1 py-0.5 text-sm leading-snug shadow-none focus-visible:ring-0",
|
|
58
|
+
!open && "min-h-0"
|
|
59
|
+
),
|
|
60
|
+
onKeyDown: (e) => {
|
|
61
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
post();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
open ? /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center justify-between gap-2", children: [
|
|
69
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1 text-[11px]", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(Lock, { size: 12 }),
|
|
71
|
+
" ",
|
|
72
|
+
hint
|
|
73
|
+
] }),
|
|
74
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
54
75
|
/* @__PURE__ */ jsx(
|
|
55
|
-
|
|
76
|
+
Button,
|
|
56
77
|
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
open ? "min-h-32" : "min-h-14"
|
|
66
|
-
),
|
|
67
|
-
onKeyDown: (e) => {
|
|
68
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
post();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
78
|
+
type: "button",
|
|
79
|
+
variant: "ghost",
|
|
80
|
+
size: "sm",
|
|
81
|
+
onClick: () => {
|
|
82
|
+
setText("");
|
|
83
|
+
setFocused(false);
|
|
84
|
+
},
|
|
85
|
+
children: "Cancel"
|
|
73
86
|
}
|
|
74
87
|
),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/* @__PURE__ */ jsx(
|
|
83
|
-
Button,
|
|
84
|
-
{
|
|
85
|
-
type: "button",
|
|
86
|
-
variant: "ghost",
|
|
87
|
-
size: "sm",
|
|
88
|
-
className: "px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground",
|
|
89
|
-
onClick: () => {
|
|
90
|
-
setText("");
|
|
91
|
-
setFocused(false);
|
|
92
|
-
},
|
|
93
|
-
children: "Cancel"
|
|
94
|
-
}
|
|
95
|
-
),
|
|
96
|
-
/* @__PURE__ */ jsxs(
|
|
97
|
-
Button,
|
|
98
|
-
{
|
|
99
|
-
type: "button",
|
|
100
|
-
size: "sm",
|
|
101
|
-
disabled: !canPost,
|
|
102
|
-
onClick: post,
|
|
103
|
-
className: "rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90",
|
|
104
|
-
children: [
|
|
105
|
-
"Comment",
|
|
106
|
-
/* @__PURE__ */ jsx("kbd", { className: "ml-1 rounded px-1 text-[10px] text-background/70", children: "\u2318\u21B5" })
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
)
|
|
110
|
-
] })
|
|
111
|
-
] }) : null
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
)
|
|
88
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", disabled: !canPost, onClick: post, children: [
|
|
89
|
+
"Comment",
|
|
90
|
+
/* @__PURE__ */ jsx("kbd", { className: "bg-primary-foreground/15 ml-1 rounded px-1 text-[10px]", children: "\u2318\u21B5" })
|
|
91
|
+
] })
|
|
92
|
+
] })
|
|
93
|
+
] }) : null
|
|
94
|
+
] })
|
|
115
95
|
]
|
|
116
96
|
}
|
|
117
97
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/comment-composer.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * comment-composer.tsx — an internal-note composer for the case activity\n * timeline. Posting a comment prepends an `operatorNote` event to the log\n * (wired by the consumer). Collapses to a single line; expands on focus or\n * when it has text. ⌘↵ / Ctrl↵ posts.\n *\n * Presentational: `onPost` does the work (the consumer persists the note and\n * adds it to the timeline). Reuses Avatar / Button / Textarea primitives.\n */\n\nimport * as React from \"react\"\nimport { Lock } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Textarea } from \"./textarea\"\n\nexport interface CommentComposerProps {\n /** Called with the trimmed note text when the operator posts. */\n onPost: (text: string) => void\n /** Current operator (for the avatar). */\n author?: { name?: string; email?: string; avatarUrl?: string | null }\n placeholder?: string\n /** Hint shown in the footer; defaults to the internal-note reassurance. */\n hint?: string\n className?: string\n}\n\nfunction CommentComposer({\n onPost,\n author,\n placeholder = \"Add a comment or internal note…\",\n hint = \"Internal note: only your team sees this\",\n className,\n}: CommentComposerProps) {\n const [text, setText] = React.useState(\"\")\n const [focused, setFocused] = React.useState(false)\n const open = focused || text.length > 0\n const canPost = text.trim().length > 0\n\n const post = () => {\n const value = text.trim()\n if (!value) return\n onPost(value)\n setText(\"\")\n setFocused(false)\n }\n\n return (\n <div\n data-slot=\"comment-composer\"\n data-open={open ? \"true\" : undefined}\n className={cn(\n \"flex items-start gap-
|
|
1
|
+
{"version":3,"sources":["../../src/components/comment-composer.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * comment-composer.tsx — an internal-note composer for the case activity\n * timeline. Posting a comment prepends an `operatorNote` event to the log\n * (wired by the consumer). Collapses to a single line; expands on focus or\n * when it has text. ⌘↵ / Ctrl↵ posts.\n *\n * Presentational: `onPost` does the work (the consumer persists the note and\n * adds it to the timeline). Reuses Avatar / Button / Textarea primitives.\n */\n\nimport * as React from \"react\"\nimport { Lock } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Textarea } from \"./textarea\"\n\nexport interface CommentComposerProps {\n /** Called with the trimmed note text when the operator posts. */\n onPost: (text: string) => void\n /** Current operator (for the avatar). */\n author?: { name?: string; email?: string; avatarUrl?: string | null }\n placeholder?: string\n /** Hint shown in the footer; defaults to the internal-note reassurance. */\n hint?: string\n className?: string\n}\n\nfunction CommentComposer({\n onPost,\n author,\n placeholder = \"Add a comment or internal note…\",\n hint = \"Internal note: only your team sees this\",\n className,\n}: CommentComposerProps) {\n const [text, setText] = React.useState(\"\")\n const [focused, setFocused] = React.useState(false)\n const open = focused || text.length > 0\n const canPost = text.trim().length > 0\n\n const post = () => {\n const value = text.trim()\n if (!value) return\n onPost(value)\n setText(\"\")\n setFocused(false)\n }\n\n return (\n <div\n data-slot=\"comment-composer\"\n data-open={open ? \"true\" : undefined}\n className={cn(\n \"border-border bg-background flex items-start gap-2 rounded-lg border px-2 py-1.5 transition-colors\",\n open && \"ring-ring/30 ring-2\",\n className\n )}\n >\n <Avatar size=\"sm\" className=\"mt-px\">\n {author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? \"You\"} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: author?.name, email: author?.email })}\n </AvatarFallback>\n </Avatar>\n\n <div className=\"min-w-0 flex-1\">\n <Textarea\n data-slot=\"comment-composer-input\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n onFocus={() => setFocused(true)}\n placeholder={placeholder}\n rows={open ? 3 : 1}\n className={cn(\n \"resize-none border-0 bg-transparent px-1 py-0.5 text-sm leading-snug shadow-none focus-visible:ring-0\",\n !open && \"min-h-0\"\n )}\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n post()\n }\n }}\n />\n\n {open ? (\n <div className=\"mt-0.5 flex items-center justify-between gap-2\">\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n <Lock size={12} /> {hint}\n </span>\n <span className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n setText(\"\")\n setFocused(false)\n }}\n >\n Cancel\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={!canPost} onClick={post}>\n Comment\n <kbd className=\"bg-primary-foreground/15 ml-1 rounded px-1 text-[10px]\">⌘↵</kbd>\n </Button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport { CommentComposer }\n"],"mappings":";AA8DM,SACuB,KADvB;AAlDN,YAAY,WAAW;AACvB,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,gBAAgB,mBAAmB;AACpD,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAazB,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP;AACF,GAAyB;AAtCzB;AAuCE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,EAAE;AACzC,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,OAAO,WAAW,KAAK,SAAS;AACtC,QAAM,UAAU,KAAK,KAAK,EAAE,SAAS;AAErC,QAAM,OAAO,MAAM;AACjB,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,CAAC,MAAO;AACZ,WAAO,KAAK;AACZ,YAAQ,EAAE;AACV,eAAW,KAAK;AAAA,EAClB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,aAAW,OAAO,SAAS;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,UAAO,MAAK,MAAK,WAAU,SACzB;AAAA,4CAAQ,aAAY,oBAAC,eAAY,KAAK,OAAO,WAAW,MAAK,YAAO,SAAP,YAAe,OAAO,IAAK;AAAA,UACzF,oBAAC,kBAAe,WAAU,oEACvB,sBAAY,EAAE,MAAM,iCAAQ,MAAM,OAAO,iCAAQ,MAAM,CAAC,GAC3D;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,kBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,aAAU;AAAA,cACV,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,cACvC,SAAS,MAAM,WAAW,IAAI;AAAA,cAC9B;AAAA,cACA,MAAM,OAAO,IAAI;AAAA,cACjB,WAAW;AAAA,gBACT;AAAA,gBACA,CAAC,QAAQ;AAAA,cACX;AAAA,cACA,WAAW,CAAC,MAAM;AAChB,qBAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,SAAS;AACjD,oBAAE,eAAe;AACjB,uBAAK;AAAA,gBACP;AAAA,cACF;AAAA;AAAA,UACF;AAAA,UAEC,OACC,qBAAC,SAAI,WAAU,kDACb;AAAA,iCAAC,UAAK,WAAU,oEACd;AAAA,kCAAC,QAAK,MAAM,IAAI;AAAA,cAAE;AAAA,cAAE;AAAA,eACtB;AAAA,YACA,qBAAC,UAAK,WAAU,2BACd;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,SAAS,MAAM;AACb,4BAAQ,EAAE;AACV,+BAAW,KAAK;AAAA,kBAClB;AAAA,kBACD;AAAA;AAAA,cAED;AAAA,cACA,qBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,UAAU,CAAC,SAAS,SAAS,MAAM;AAAA;AAAA,gBAEjE,oBAAC,SAAI,WAAU,0DAAyD,0BAAE;AAAA,iBAC5E;AAAA,eACF;AAAA,aACF,IACE;AAAA,WACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -21,8 +21,46 @@ const PROSE = cn(
|
|
|
21
21
|
"[&_img]:max-w-full [&_img]:h-auto",
|
|
22
22
|
"[&_sup]:align-super [&_sup]:text-[0.75em] [&_sub]:align-sub [&_sub]:text-[0.75em]"
|
|
23
23
|
);
|
|
24
|
+
const PLAIN_TEXT_LINK_RE = /https?:\/\/[^\s<>"']+|www\.[^\s<>"']+|[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
25
|
+
const TRAILING_LINK_PUNCTUATION_RE = /[),.;:!?]+$/;
|
|
26
|
+
function plainTextHref(value) {
|
|
27
|
+
if (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
|
|
28
|
+
return `mailto:${value}`;
|
|
29
|
+
}
|
|
30
|
+
const href = value.toLowerCase().startsWith("www.") ? `https://${value}` : value;
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(href);
|
|
33
|
+
return url.protocol === "http:" || url.protocol === "https:" ? href : null;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function linkifyPlainText(text) {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
const nodes = [];
|
|
41
|
+
let cursor = 0;
|
|
42
|
+
for (const match of text.matchAll(PLAIN_TEXT_LINK_RE)) {
|
|
43
|
+
const value = match[0];
|
|
44
|
+
const index = (_a = match.index) != null ? _a : 0;
|
|
45
|
+
if (index > cursor) nodes.push(text.slice(cursor, index));
|
|
46
|
+
const trailing = (_c = (_b = value.match(TRAILING_LINK_PUNCTUATION_RE)) == null ? void 0 : _b[0]) != null ? _c : "";
|
|
47
|
+
const linkText = trailing ? value.slice(0, -trailing.length) : value;
|
|
48
|
+
const href = plainTextHref(linkText);
|
|
49
|
+
if (href) {
|
|
50
|
+
nodes.push(
|
|
51
|
+
/* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noreferrer noopener", children: linkText }, `${index}-${linkText}`)
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
nodes.push(linkText);
|
|
55
|
+
}
|
|
56
|
+
if (trailing) nodes.push(trailing);
|
|
57
|
+
cursor = index + value.length;
|
|
58
|
+
}
|
|
59
|
+
if (cursor < text.length) nodes.push(text.slice(cursor));
|
|
60
|
+
return nodes;
|
|
61
|
+
}
|
|
24
62
|
function PlainTextBlock({ text, className, slot }) {
|
|
25
|
-
return /* @__PURE__ */ jsx("div", { "data-slot": slot, className: cn(PROSE, "whitespace-pre-line", className), children: decodeEmailDisplayText(text) });
|
|
63
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": slot, className: cn(PROSE, "whitespace-pre-line", className), children: linkifyPlainText(decodeEmailDisplayText(text)) });
|
|
26
64
|
}
|
|
27
65
|
function HtmlBlock({ html, className, slot }) {
|
|
28
66
|
return /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/email-body.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { sanitizeHtml } from \"../internal/safe-html\"\nimport { cn } from \"../lib/utils\"\nimport {\n decodeEmailDisplayText,\n splitEmailHtmlForDisplay,\n splitEmailTextForDisplay,\n} from \"./email-display-helpers\"\n\nexport interface EmailBodyProps {\n html?: string | null\n text?: string | null\n detailsHtml?: string | null\n detailsText?: string | null\n collapseDetails?: boolean\n defaultDetailsOpen?: boolean\n variant?: \"history\" | \"preview\"\n className?: string\n}\n\nconst PROSE = cn(\n \"break-words leading-[1.62]\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground\",\n \"[&_table]:my-2 [&_table]:max-w-full [&_table]:border-collapse\",\n \"[&_td]:align-top [&_td]:pr-2 [&_th]:align-top [&_th]:pr-2\",\n \"[&_img]:max-w-full [&_img]:h-auto\",\n \"[&_sup]:align-super [&_sup]:text-[0.75em] [&_sub]:align-sub [&_sub]:text-[0.75em]\",\n)\n\nfunction PlainTextBlock({ text, className, slot }: { text: string; className?: string; slot: string }) {\n return (\n <div data-slot={slot} className={cn(PROSE, \"whitespace-pre-line\", className)}>\n {decodeEmailDisplayText(text)}\n </div>\n )\n}\n\nfunction HtmlBlock({ html, className, slot }: { html: string; className?: string; slot: string }) {\n return (\n <div\n data-slot={slot}\n className={cn(PROSE, className)}\n dangerouslySetInnerHTML={{ __html: sanitizeHtml(html) }}\n />\n )\n}\n\nexport function EmailBody({\n html,\n text,\n detailsHtml,\n detailsText,\n collapseDetails = false,\n defaultDetailsOpen = false,\n variant = \"history\",\n className,\n}: EmailBodyProps) {\n const [detailsOpen, setDetailsOpen] = React.useState(defaultDetailsOpen)\n\n const htmlParts = html ? splitEmailHtmlForDisplay(html) : null\n const textParts = html ? null : splitEmailTextForDisplay(text ?? \"\")\n\n const bodyHtml = htmlParts?.bodyHtml ?? \"\"\n const bodyText = textParts?.bodyText ?? \"\"\n const combinedDetailsHtml = [htmlParts?.detailsHtml, detailsHtml].filter(Boolean).join(\"\")\n const combinedDetailsText = [textParts?.detailsText, detailsText].filter(Boolean).join(\"\")\n const hasBody = Boolean(bodyHtml.trim() || bodyText.trim())\n const hasDetails = Boolean(combinedDetailsHtml.trim() || combinedDetailsText.trim())\n const shouldCollapseDetails = collapseDetails && hasDetails\n const showDetails = hasDetails && (!shouldCollapseDetails || detailsOpen)\n\n return (\n <div\n data-slot=\"email-body\"\n data-variant={variant}\n className={cn(\n \"text-sm text-foreground/90\",\n variant === \"preview\" && \"text-[13.5px]\",\n className,\n )}\n >\n {hasBody ? (\n htmlParts ? (\n <HtmlBlock slot=\"email-body-content\" html={bodyHtml} />\n ) : (\n <PlainTextBlock slot=\"email-body-content\" text={bodyText} />\n )\n ) : null}\n\n {shouldCollapseDetails ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((value) => !value)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted mt-2 rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n title={detailsOpen ? \"Hide historical details\" : \"Show historical details\"}\n >\n •••\n </button>\n ) : null}\n\n {showDetails ? (\n <div\n data-slot=\"email-body-details\"\n className={cn(\n \"mt-2 text-muted-foreground\",\n shouldCollapseDetails && \"border-border border-l-2 pl-3\",\n variant === \"preview\" && \"border-border/50 border-t pt-3\",\n )}\n >\n {combinedDetailsHtml ? (\n <HtmlBlock slot=\"email-body-details-content\" html={combinedDetailsHtml} />\n ) : (\n <PlainTextBlock slot=\"email-body-details-content\" text={combinedDetailsText} />\n )}\n </div>\n ) : null}\n </div>\n )\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/email-body.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { sanitizeHtml } from \"../internal/safe-html\"\nimport { cn } from \"../lib/utils\"\nimport {\n decodeEmailDisplayText,\n splitEmailHtmlForDisplay,\n splitEmailTextForDisplay,\n} from \"./email-display-helpers\"\n\nexport interface EmailBodyProps {\n html?: string | null\n text?: string | null\n detailsHtml?: string | null\n detailsText?: string | null\n collapseDetails?: boolean\n defaultDetailsOpen?: boolean\n variant?: \"history\" | \"preview\"\n className?: string\n}\n\nconst PROSE = cn(\n \"break-words leading-[1.62]\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground\",\n \"[&_table]:my-2 [&_table]:max-w-full [&_table]:border-collapse\",\n \"[&_td]:align-top [&_td]:pr-2 [&_th]:align-top [&_th]:pr-2\",\n \"[&_img]:max-w-full [&_img]:h-auto\",\n \"[&_sup]:align-super [&_sup]:text-[0.75em] [&_sub]:align-sub [&_sub]:text-[0.75em]\",\n)\n\nconst PLAIN_TEXT_LINK_RE = /https?:\\/\\/[^\\s<>\"']+|www\\.[^\\s<>\"']+|[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi\nconst TRAILING_LINK_PUNCTUATION_RE = /[),.;:!?]+$/\n\nfunction plainTextHref(value: string): string | null {\n if (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i.test(value)) {\n return `mailto:${value}`\n }\n const href = value.toLowerCase().startsWith(\"www.\") ? `https://${value}` : value\n try {\n const url = new URL(href)\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? href : null\n } catch {\n return null\n }\n}\n\nfunction linkifyPlainText(text: string): React.ReactNode[] {\n const nodes: React.ReactNode[] = []\n let cursor = 0\n\n for (const match of text.matchAll(PLAIN_TEXT_LINK_RE)) {\n const value = match[0]\n const index = match.index ?? 0\n if (index > cursor) nodes.push(text.slice(cursor, index))\n\n const trailing = value.match(TRAILING_LINK_PUNCTUATION_RE)?.[0] ?? \"\"\n const linkText = trailing ? value.slice(0, -trailing.length) : value\n const href = plainTextHref(linkText)\n if (href) {\n nodes.push(\n <a key={`${index}-${linkText}`} href={href} target=\"_blank\" rel=\"noreferrer noopener\">\n {linkText}\n </a>,\n )\n } else {\n nodes.push(linkText)\n }\n if (trailing) nodes.push(trailing)\n cursor = index + value.length\n }\n\n if (cursor < text.length) nodes.push(text.slice(cursor))\n return nodes\n}\n\nfunction PlainTextBlock({ text, className, slot }: { text: string; className?: string; slot: string }) {\n return (\n <div data-slot={slot} className={cn(PROSE, \"whitespace-pre-line\", className)}>\n {linkifyPlainText(decodeEmailDisplayText(text))}\n </div>\n )\n}\n\nfunction HtmlBlock({ html, className, slot }: { html: string; className?: string; slot: string }) {\n return (\n <div\n data-slot={slot}\n className={cn(PROSE, className)}\n dangerouslySetInnerHTML={{ __html: sanitizeHtml(html) }}\n />\n )\n}\n\nexport function EmailBody({\n html,\n text,\n detailsHtml,\n detailsText,\n collapseDetails = false,\n defaultDetailsOpen = false,\n variant = \"history\",\n className,\n}: EmailBodyProps) {\n const [detailsOpen, setDetailsOpen] = React.useState(defaultDetailsOpen)\n\n const htmlParts = html ? splitEmailHtmlForDisplay(html) : null\n const textParts = html ? null : splitEmailTextForDisplay(text ?? \"\")\n\n const bodyHtml = htmlParts?.bodyHtml ?? \"\"\n const bodyText = textParts?.bodyText ?? \"\"\n const combinedDetailsHtml = [htmlParts?.detailsHtml, detailsHtml].filter(Boolean).join(\"\")\n const combinedDetailsText = [textParts?.detailsText, detailsText].filter(Boolean).join(\"\")\n const hasBody = Boolean(bodyHtml.trim() || bodyText.trim())\n const hasDetails = Boolean(combinedDetailsHtml.trim() || combinedDetailsText.trim())\n const shouldCollapseDetails = collapseDetails && hasDetails\n const showDetails = hasDetails && (!shouldCollapseDetails || detailsOpen)\n\n return (\n <div\n data-slot=\"email-body\"\n data-variant={variant}\n className={cn(\n \"text-sm text-foreground/90\",\n variant === \"preview\" && \"text-[13.5px]\",\n className,\n )}\n >\n {hasBody ? (\n htmlParts ? (\n <HtmlBlock slot=\"email-body-content\" html={bodyHtml} />\n ) : (\n <PlainTextBlock slot=\"email-body-content\" text={bodyText} />\n )\n ) : null}\n\n {shouldCollapseDetails ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((value) => !value)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted mt-2 rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n title={detailsOpen ? \"Hide historical details\" : \"Show historical details\"}\n >\n •••\n </button>\n ) : null}\n\n {showDetails ? (\n <div\n data-slot=\"email-body-details\"\n className={cn(\n \"mt-2 text-muted-foreground\",\n shouldCollapseDetails && \"border-border border-l-2 pl-3\",\n variant === \"preview\" && \"border-border/50 border-t pt-3\",\n )}\n >\n {combinedDetailsHtml ? (\n <HtmlBlock slot=\"email-body-details-content\" html={combinedDetailsHtml} />\n ) : (\n <PlainTextBlock slot=\"email-body-details-content\" text={combinedDetailsText} />\n )}\n </div>\n ) : null}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -297,6 +297,8 @@ function SourceAction({
|
|
|
297
297
|
onSourceClick,
|
|
298
298
|
className
|
|
299
299
|
}) {
|
|
300
|
+
var _a;
|
|
301
|
+
const actionLabel = (_a = source.actionLabel) != null ? _a : `Open in ${source.label}`;
|
|
300
302
|
if (onSourceClick) {
|
|
301
303
|
return /* @__PURE__ */ jsxs(
|
|
302
304
|
"button",
|
|
@@ -308,8 +310,7 @@ function SourceAction({
|
|
|
308
310
|
},
|
|
309
311
|
className,
|
|
310
312
|
children: [
|
|
311
|
-
|
|
312
|
-
source.label,
|
|
313
|
+
actionLabel,
|
|
313
314
|
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
|
|
314
315
|
]
|
|
315
316
|
}
|
|
@@ -323,8 +324,7 @@ function SourceAction({
|
|
|
323
324
|
rel: "noreferrer noopener",
|
|
324
325
|
className,
|
|
325
326
|
children: [
|
|
326
|
-
|
|
327
|
-
source.label,
|
|
327
|
+
actionLabel,
|
|
328
328
|
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
|
|
329
329
|
]
|
|
330
330
|
}
|
|
@@ -358,16 +358,27 @@ function EmailCard({
|
|
|
358
358
|
}
|
|
359
359
|
) }),
|
|
360
360
|
/* @__PURE__ */ jsx(TimelineEmailBody, { email: event.email }),
|
|
361
|
-
/* @__PURE__ */ jsx(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
361
|
+
event.content ? /* @__PURE__ */ jsx("div", { className: "rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground", children: event.content }) : null,
|
|
362
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-3", children: [
|
|
363
|
+
event.source ? /* @__PURE__ */ jsx(
|
|
364
|
+
SourceAction,
|
|
365
|
+
{
|
|
366
|
+
source: event.source,
|
|
367
|
+
onSourceClick: event.onSourceClick,
|
|
368
|
+
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"
|
|
369
|
+
}
|
|
370
|
+
) : null,
|
|
371
|
+
/* @__PURE__ */ jsx(
|
|
372
|
+
ShowLessButton,
|
|
373
|
+
{
|
|
374
|
+
onClick: (e) => {
|
|
375
|
+
e.stopPropagation();
|
|
376
|
+
setExpanded(false);
|
|
377
|
+
},
|
|
378
|
+
className: "flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
] })
|
|
371
382
|
] }) : /* @__PURE__ */ jsx(
|
|
372
383
|
CollapsedEmailPreview,
|
|
373
384
|
{
|
|
@@ -389,18 +400,38 @@ function EmailCard({
|
|
|
389
400
|
setShowAllRecipients
|
|
390
401
|
}
|
|
391
402
|
) }),
|
|
392
|
-
/* @__PURE__ */
|
|
393
|
-
|
|
394
|
-
|
|
403
|
+
/* @__PURE__ */ jsxs("div", { className: classes.cardBody, "data-slot": "timeline-card-body", children: [
|
|
404
|
+
/* @__PURE__ */ jsx(TimelineEmailBody, { email: event.email }),
|
|
405
|
+
event.content ? /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground", children: event.content }) : null
|
|
406
|
+
] }),
|
|
407
|
+
/* @__PURE__ */ jsxs(
|
|
408
|
+
"div",
|
|
395
409
|
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
410
|
+
className: cn(classes.cardFooter, classes.actionLinkRow, event.source ? "justify-between" : "justify-end"),
|
|
411
|
+
"data-slot": "timeline-card-footer",
|
|
412
|
+
children: [
|
|
413
|
+
event.source ? /* @__PURE__ */ jsx(
|
|
414
|
+
SourceAction,
|
|
415
|
+
{
|
|
416
|
+
source: event.source,
|
|
417
|
+
onSourceClick: event.onSourceClick,
|
|
418
|
+
className: classes.actionLink
|
|
419
|
+
}
|
|
420
|
+
) : null,
|
|
421
|
+
/* @__PURE__ */ jsx(
|
|
422
|
+
ShowLessButton,
|
|
423
|
+
{
|
|
424
|
+
type: "button",
|
|
425
|
+
onClick: (e) => {
|
|
426
|
+
e.stopPropagation();
|
|
427
|
+
setExpanded(false);
|
|
428
|
+
},
|
|
429
|
+
className: classes.actionLink
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
]
|
|
402
433
|
}
|
|
403
|
-
)
|
|
434
|
+
)
|
|
404
435
|
] }) : /* @__PURE__ */ jsx(
|
|
405
436
|
CollapsedEmailPreview,
|
|
406
437
|
{
|
|
@@ -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 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 if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n Open in {source.label}\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 Open in {source.label}\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 <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-2 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 ) : (\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 </div>\n\n <div className={cn(classes.cardFooter, classes.actionLinkRow)} data-slot=\"timeline-card-footer\">\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 } 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":[]}
|