@handled-ai/design-system 0.20.11 → 0.20.12

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.
@@ -34,64 +34,84 @@ function CommentComposer({
34
34
  "data-slot": "comment-composer",
35
35
  "data-open": open ? "true" : void 0,
36
36
  className: cn(
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",
37
+ "flex items-start gap-4 rounded-xl transition-colors",
39
38
  className
40
39
  ),
41
40
  children: [
42
- /* @__PURE__ */ jsxs(Avatar, { size: "sm", className: "mt-px", children: [
41
+ /* @__PURE__ */ jsxs(Avatar, { size: "sm", className: "mt-1", children: [
43
42
  (author == null ? void 0 : author.avatarUrl) ? /* @__PURE__ */ jsx(AvatarImage, { src: author.avatarUrl, alt: (_a = author.name) != null ? _a : "You" }) : null,
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 }) })
43
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900", children: getInitials({ name: author == null ? void 0 : author.name, email: author == null ? void 0 : author.email }) })
45
44
  ] }),
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: [
45
+ /* @__PURE__ */ jsxs(
46
+ "div",
47
+ {
48
+ "data-slot": "comment-composer-shell",
49
+ className: cn(
50
+ "min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]",
51
+ open ? "overflow-hidden shadow-sm" : "shadow-none"
52
+ ),
53
+ children: [
75
54
  /* @__PURE__ */ jsx(
76
- Button,
55
+ Textarea,
77
56
  {
78
- type: "button",
79
- variant: "ghost",
80
- size: "sm",
81
- onClick: () => {
82
- setText("");
83
- setFocused(false);
84
- },
85
- children: "Cancel"
57
+ "data-slot": "comment-composer-input",
58
+ value: text,
59
+ onChange: (e) => setText(e.target.value),
60
+ onFocus: () => setFocused(true),
61
+ placeholder,
62
+ rows: open ? 4 : 1,
63
+ className: cn(
64
+ "resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
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
+ }
86
73
  }
87
74
  ),
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
- ] })
75
+ open ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4", children: [
76
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground", children: [
77
+ /* @__PURE__ */ jsx(Lock, { size: 16, strokeWidth: 1.75 }),
78
+ " ",
79
+ hint
80
+ ] }),
81
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3", children: [
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
+ )
95
115
  ]
96
116
  }
97
117
  );
@@ -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 \"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":[]}
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-4 rounded-xl transition-colors\",\n className\n )}\n >\n <Avatar size=\"sm\" className=\"mt-1\">\n {author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? \"You\"} /> : null}\n <AvatarFallback className=\"bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900\">\n {getInitials({ name: author?.name, email: author?.email })}\n </AvatarFallback>\n </Avatar>\n\n <div\n data-slot=\"comment-composer-shell\"\n className={cn(\n \"min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]\",\n open ? \"overflow-hidden shadow-sm\" : \"shadow-none\"\n )}\n >\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 ? 4 : 1}\n className={cn(\n \"resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0\",\n open ? \"min-h-32\" : \"min-h-14\"\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=\"flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4\">\n <span className=\"inline-flex items-center gap-2 text-sm text-muted-foreground\">\n <Lock size={16} strokeWidth={1.75} /> {hint}\n </span>\n <span className=\"flex items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => {\n setText(\"\")\n setFocused(false)\n }}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={!canPost}\n onClick={post}\n className=\"rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90\"\n >\n Comment\n <kbd className=\"ml-1 rounded px-1 text-[10px] text-background/70\">⌘↵</kbd>\n </Button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport { CommentComposer }\n"],"mappings":";AA6DM,SACuB,KADvB;AAjDN,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;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,UAAO,MAAK,MAAK,WAAU,QACzB;AAAA,4CAAQ,aAAY,oBAAC,eAAY,KAAK,OAAO,WAAW,MAAK,YAAO,SAAP,YAAe,OAAO,IAAK;AAAA,UACzF,oBAAC,kBAAe,WAAU,qGACvB,sBAAY,EAAE,MAAM,iCAAQ,MAAM,OAAO,iCAAQ,MAAM,CAAC,GAC3D;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,OAAO,8BAA8B;AAAA,YACvC;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,OAAO;AAAA,kBACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,kBACvC,SAAS,MAAM,WAAW,IAAI;AAAA,kBAC9B;AAAA,kBACA,MAAM,OAAO,IAAI;AAAA,kBACjB,WAAW;AAAA,oBACT;AAAA,oBACA,OAAO,aAAa;AAAA,kBACtB;AAAA,kBACA,WAAW,CAAC,MAAM;AAChB,yBAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,SAAS;AACjD,wBAAE,eAAe;AACjB,2BAAK;AAAA,oBACP;AAAA,kBACF;AAAA;AAAA,cACF;AAAA,cAEC,OACC,qBAAC,SAAI,WAAU,wFACb;AAAA,qCAAC,UAAK,WAAU,gEACd;AAAA,sCAAC,QAAK,MAAM,IAAI,aAAa,MAAM;AAAA,kBAAE;AAAA,kBAAE;AAAA,mBACzC;AAAA,gBACA,qBAAC,UAAK,WAAU,2BACd;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,SAAS,MAAM;AACb,gCAAQ,EAAE;AACV,mCAAW,KAAK;AAAA,sBAClB;AAAA,sBACD;AAAA;AAAA,kBAED;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,MAAK;AAAA,sBACL,UAAU,CAAC;AAAA,sBACX,SAAS;AAAA,sBACT,WAAU;AAAA,sBACX;AAAA;AAAA,wBAEC,oBAAC,SAAI,WAAU,oDAAmD,0BAAE;AAAA;AAAA;AAAA,kBACtE;AAAA,mBACF;AAAA,iBACF,IACE;AAAA;AAAA;AAAA,QACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -21,46 +21,8 @@ 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
- }
62
24
  function PlainTextBlock({ text, className, slot }) {
63
- return /* @__PURE__ */ jsx("div", { "data-slot": slot, className: cn(PROSE, "whitespace-pre-line", className), children: linkifyPlainText(decodeEmailDisplayText(text)) });
25
+ return /* @__PURE__ */ jsx("div", { "data-slot": slot, className: cn(PROSE, "whitespace-pre-line", className), children: decodeEmailDisplayText(text) });
64
26
  }
65
27
  function HtmlBlock({ html, className, slot }) {
66
28
  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\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":[]}
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":";AAqCI,cAyCA,YAzCA;AAnCJ,YAAY,WAAW;AAEvB,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAaP,MAAM,QAAQ;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,eAAe,EAAE,MAAM,WAAW,KAAK,GAAuD;AACrG,SACE,oBAAC,SAAI,aAAW,MAAM,WAAW,GAAG,OAAO,uBAAuB,SAAS,GACxE,iCAAuB,IAAI,GAC9B;AAEJ;AAEA,SAAS,UAAU,EAAE,MAAM,WAAW,KAAK,GAAuD;AAChG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAW;AAAA,MACX,WAAW,GAAG,OAAO,SAAS;AAAA,MAC9B,yBAAyB,EAAE,QAAQ,aAAa,IAAI,EAAE;AAAA;AAAA,EACxD;AAEJ;AAEO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,UAAU;AAAA,EACV;AACF,GAAmB;AA9DnB;AA+DE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,kBAAkB;AAEvE,QAAM,YAAY,OAAO,yBAAyB,IAAI,IAAI;AAC1D,QAAM,YAAY,OAAO,OAAO,yBAAyB,sBAAQ,EAAE;AAEnE,QAAM,YAAW,4CAAW,aAAX,YAAuB;AACxC,QAAM,YAAW,4CAAW,aAAX,YAAuB;AACxC,QAAM,sBAAsB,CAAC,uCAAW,aAAa,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK,EAAE;AACzF,QAAM,sBAAsB,CAAC,uCAAW,aAAa,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK,EAAE;AACzF,QAAM,UAAU,QAAQ,SAAS,KAAK,KAAK,SAAS,KAAK,CAAC;AAC1D,QAAM,aAAa,QAAQ,oBAAoB,KAAK,KAAK,oBAAoB,KAAK,CAAC;AACnF,QAAM,wBAAwB,mBAAmB;AACjD,QAAM,cAAc,eAAe,CAAC,yBAAyB;AAE7D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA,YAAY,aAAa;AAAA,QACzB;AAAA,MACF;AAAA,MAEC;AAAA,kBACC,YACE,oBAAC,aAAU,MAAK,sBAAqB,MAAM,UAAU,IAErD,oBAAC,kBAAe,MAAK,sBAAqB,MAAM,UAAU,IAE1D;AAAA,QAEH,wBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,eAAe,CAAC,UAAU,CAAC,KAAK;AAAA,YAC/C,WAAU;AAAA,YACV,iBAAe;AAAA,YACf,OAAO,cAAc,4BAA4B;AAAA,YAClD;AAAA;AAAA,QAED,IACE;AAAA,QAEH,cACC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,yBAAyB;AAAA,cACzB,YAAY,aAAa;AAAA,YAC3B;AAAA,YAEC,gCACC,oBAAC,aAAU,MAAK,8BAA6B,MAAM,qBAAqB,IAExE,oBAAC,kBAAe,MAAK,8BAA6B,MAAM,qBAAqB;AAAA;AAAA,QAEjF,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -113,9 +113,10 @@ function TimelineSection({
113
113
  setShowSystemEvents,
114
114
  attentionCount,
115
115
  sysEvtConfig,
116
- lastActivityTime
116
+ lastActivityTime,
117
+ isCasePanel = false
117
118
  }) {
118
- var _a;
119
+ var _a, _b, _c, _d;
119
120
  const visibleEvents = [];
120
121
  let hiddenCount = 0;
121
122
  for (const e of timelineEvents) {
@@ -124,74 +125,117 @@ function TimelineSection({
124
125
  }
125
126
  const hasSystemNoise = hiddenCount > 0;
126
127
  const toggleLabel = (_a = sysEvtConfig == null ? void 0 : sysEvtConfig.toggleLabel) != null ? _a : "System events";
128
+ const toggleHelp = showSystemEvents ? (_c = (_b = sysEvtConfig == null ? void 0 : sysEvtConfig.visibleHint) == null ? void 0 : _b.replace("{count}", String(hiddenCount))) != null ? _c : "Hide system events" : (_d = sysEvtConfig == null ? void 0 : sysEvtConfig.hiddenHint) != null ? _d : "Show system events";
127
129
  const firstVisibleTime = (!hasSystemNoise || showSystemEvents) && lastActivityTime ? lastActivityTime : visibleEvents.length > 0 ? visibleEvents[0].time : "";
128
130
  const visibleCount = visibleEvents.length;
129
131
  const eventCountLabel = `${visibleCount} ${visibleCount === 1 ? "event" : "events"}`;
130
- return /* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
131
- /* @__PURE__ */ jsxs(
132
- "div",
133
- {
134
- className: "group/timeline flex w-full items-center justify-between gap-2 py-2 rounded-md transition-colors hover:bg-muted/40 -mx-2 px-2",
135
- "data-testid": "timeline-header",
136
- children: [
137
- /* @__PURE__ */ jsxs(
138
- "button",
139
- {
140
- type: "button",
141
- onClick: () => setShowTimeline((prev) => !prev),
142
- className: "flex items-center gap-2 cursor-pointer bg-transparent border-0 p-0",
143
- "data-testid": "timeline-collapse-btn",
144
- children: [
145
- /* @__PURE__ */ jsx("h3", { className: "text-xs font-bold text-muted-foreground uppercase tracking-wider group-hover/timeline:text-foreground transition-colors", children: "Activity timeline" }),
146
- !showTimeline && attentionCount != null && attentionCount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 px-1.5 py-0.5 text-[10px] font-semibold text-destructive border border-destructive/20", children: [
147
- attentionCount,
148
- " new"
149
- ] }),
150
- !showTimeline && firstVisibleTime && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground/60", "data-testid": "last-activity-hint", children: [
151
- "\xB7 Last activity ",
152
- firstVisibleTime
153
- ] }),
154
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
155
- /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground", "data-testid": "event-count", children: eventCountLabel }),
156
- /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}` })
157
- ] })
158
- ]
159
- }
160
- ),
161
- hasSystemNoise && /* @__PURE__ */ jsxs(
162
- "button",
163
- {
164
- type: "button",
165
- onClick: () => setShowSystemEvents((prev) => !prev),
166
- className: cn(
167
- "flex shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors hover:text-foreground",
168
- showSystemEvents ? "border-primary/40 bg-primary/10 text-primary shadow-sm hover:bg-primary/15" : "border-border bg-background text-muted-foreground hover:bg-muted/40"
132
+ return /* @__PURE__ */ jsxs(
133
+ "div",
134
+ {
135
+ className: cn(
136
+ isCasePanel ? "mt-8 border-t border-border pt-8 pb-8" : "mb-8"
137
+ ),
138
+ children: [
139
+ /* @__PURE__ */ jsxs(
140
+ "div",
141
+ {
142
+ className: cn(
143
+ "flex w-full items-center justify-between",
144
+ isCasePanel ? "gap-4 border-b border-border pb-5" : "group/timeline gap-2 rounded-md py-2 transition-colors hover:bg-muted/40 -mx-2 px-2"
145
+ ),
146
+ "data-testid": "timeline-header",
147
+ children: [
148
+ /* @__PURE__ */ jsxs(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ onClick: () => setShowTimeline((prev) => !prev),
153
+ className: "flex min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left",
154
+ "data-testid": "timeline-collapse-btn",
155
+ children: [
156
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-bold uppercase tracking-[0.16em] text-muted-foreground transition-colors group-hover/timeline:text-foreground", children: "ACTIVITY TIMELINE" }),
157
+ !showTimeline && attentionCount != null && attentionCount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 px-1.5 py-0.5 text-[10px] font-semibold text-destructive border border-destructive/20", children: [
158
+ attentionCount,
159
+ " new"
160
+ ] }),
161
+ !isCasePanel && !showTimeline && firstVisibleTime && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground/60", "data-testid": "last-activity-hint", children: [
162
+ "\xB7 Last activity ",
163
+ firstVisibleTime
164
+ ] })
165
+ ]
166
+ }
169
167
  ),
170
- "aria-pressed": showSystemEvents,
171
- "data-testid": "system-events-toggle",
172
- children: [
173
- toggleLabel,
174
- /* @__PURE__ */ jsx(
175
- "span",
168
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-4", children: [
169
+ hasSystemNoise && /* @__PURE__ */ jsxs(
170
+ "button",
176
171
  {
172
+ type: "button",
173
+ onClick: () => setShowSystemEvents((prev) => !prev),
177
174
  className: cn(
178
- "inline-flex min-w-[18px] items-center justify-center rounded-full px-1.5 text-[10px] font-semibold tabular-nums",
179
- showSystemEvents ? "bg-primary/15 text-primary ring-1 ring-primary/30" : "bg-muted text-muted-foreground ring-1 ring-border/70"
175
+ "inline-flex shrink-0 cursor-pointer items-center gap-3 rounded-full border px-3.5 py-2 text-sm font-semibold transition-colors",
176
+ showSystemEvents ? "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90" : "border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground"
180
177
  ),
181
- "data-testid": "hidden-count-badge",
182
- children: hiddenCount
178
+ "aria-pressed": showSystemEvents,
179
+ "aria-label": toggleHelp,
180
+ title: toggleHelp,
181
+ "data-testid": "system-events-toggle",
182
+ children: [
183
+ /* @__PURE__ */ jsx(
184
+ "span",
185
+ {
186
+ className: cn(
187
+ "relative inline-flex h-4 w-8 shrink-0 items-center rounded-full p-0.5 transition-colors",
188
+ showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/30"
189
+ ),
190
+ "aria-hidden": "true",
191
+ "data-testid": "system-events-indicator",
192
+ children: /* @__PURE__ */ jsx(
193
+ "span",
194
+ {
195
+ className: cn(
196
+ "block h-3 w-3 rounded-full bg-white shadow-sm transition-transform",
197
+ showSystemEvents ? "translate-x-4" : "translate-x-0"
198
+ )
199
+ }
200
+ )
201
+ }
202
+ ),
203
+ /* @__PURE__ */ jsx("span", { children: toggleLabel }),
204
+ !showSystemEvents ? /* @__PURE__ */ jsx(
205
+ "span",
206
+ {
207
+ className: "inline-flex min-w-[22px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold tabular-nums text-muted-foreground",
208
+ "data-testid": "hidden-count-badge",
209
+ children: hiddenCount
210
+ }
211
+ ) : null
212
+ ]
213
+ }
214
+ ),
215
+ /* @__PURE__ */ jsxs(
216
+ "button",
217
+ {
218
+ type: "button",
219
+ onClick: () => setShowTimeline((prev) => !prev),
220
+ className: cn(
221
+ "inline-flex shrink-0 cursor-pointer items-center border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground",
222
+ isCasePanel ? "gap-3 text-sm" : "gap-1.5 text-[11px]"
223
+ ),
224
+ "aria-label": showTimeline ? "Collapse activity timeline" : "Expand activity timeline",
225
+ children: [
226
+ /* @__PURE__ */ jsx("span", { className: "font-medium", "data-testid": "event-count", children: eventCountLabel }),
227
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}` })
228
+ ]
183
229
  }
184
230
  )
185
- ]
186
- }
187
- )
188
- ]
189
- }
190
- ),
191
- showTimeline && visibleEvents.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(TimelineActivity, { events: visibleEvents, variant: "case-panel" }) }),
192
- showTimeline && !showSystemEvents && (sysEvtConfig == null ? void 0 : sysEvtConfig.hiddenHint) && hasSystemNoise && /* @__PURE__ */ jsx("p", { className: "mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2", "data-testid": "timeline-footer-hint", children: sysEvtConfig.hiddenHint }),
193
- showTimeline && showSystemEvents && (sysEvtConfig == null ? void 0 : sysEvtConfig.visibleHint) && hasSystemNoise && /* @__PURE__ */ jsx("p", { className: "mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2", "data-testid": "timeline-footer-hint", children: sysEvtConfig.visibleHint.replace("{count}", String(hiddenCount)) })
194
- ] });
231
+ ] })
232
+ ]
233
+ }
234
+ ),
235
+ showTimeline && visibleEvents.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(TimelineActivity, { events: visibleEvents, variant: "case-panel" }) })
236
+ ]
237
+ }
238
+ );
195
239
  }
196
240
  function DetailView({
197
241
  item,
@@ -387,7 +431,8 @@ function DetailView({
387
431
  setShowSystemEvents,
388
432
  attentionCount,
389
433
  sysEvtConfig,
390
- lastActivityTime
434
+ lastActivityTime,
435
+ isCasePanel: isCasePanelV2
391
436
  }
392
437
  ) : null;
393
438
  const suggestedActionsSection = sections.suggestedActions ? /* @__PURE__ */ jsx(SignalApproval.Gate, { children: /* @__PURE__ */ jsx(