@handled-ai/design-system 0.20.12 → 0.20.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/comment-composer.js +49 -69
- package/dist/components/comment-composer.js.map +1 -1
- package/dist/components/conversation-panel.js +127 -84
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/tabs.d.ts +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__/conversation-panel.test.tsx +75 -11
- package/src/components/comment-composer.tsx +14 -26
- package/src/components/conversation-panel.tsx +83 -29
- 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
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const badgeVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" |
|
|
6
|
+
variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
7
7
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
8
8
|
declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
|
|
9
9
|
asChild?: boolean;
|
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" |
|
|
6
|
+
variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
7
7
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
@@ -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":[]}
|
|
@@ -145,17 +145,30 @@ function PersonAvatar({ person, size = "sm" }) {
|
|
|
145
145
|
] });
|
|
146
146
|
}
|
|
147
147
|
const STATUS_PILL = {
|
|
148
|
-
responded: { label: "
|
|
148
|
+
responded: { label: "NEW REPLY", cls: "bg-status-warning-bg text-status-warning-fg border-status-warning-border" },
|
|
149
149
|
draft: { label: "Draft", cls: "bg-background text-foreground/80 border-border" },
|
|
150
|
-
awaiting: { label: "
|
|
150
|
+
awaiting: { label: "SENT", cls: "bg-status-info-bg text-status-info-fg border-status-info-border" },
|
|
151
151
|
viewing: { label: "Viewing", cls: "bg-muted text-muted-foreground border-border" }
|
|
152
152
|
};
|
|
153
153
|
const STATUS_DOT = {
|
|
154
|
-
responded: "bg-status-
|
|
154
|
+
responded: "bg-status-warning-fg",
|
|
155
155
|
draft: "bg-status-pending-fg",
|
|
156
|
-
awaiting: "bg-status-
|
|
156
|
+
awaiting: "bg-status-info-fg",
|
|
157
157
|
viewing: "bg-muted-foreground/50"
|
|
158
158
|
};
|
|
159
|
+
const THREAD_ROW_ACCENT = {
|
|
160
|
+
responded: "border-l-4 border-l-status-warning-border bg-status-warning-bg/25",
|
|
161
|
+
awaiting: "border-l-4 border-l-status-info-border bg-status-info-bg/25",
|
|
162
|
+
draft: "border-l-4 border-l-status-pending-border bg-status-pending-bg/20",
|
|
163
|
+
viewing: ""
|
|
164
|
+
};
|
|
165
|
+
const RECEIPT_CHIP = {
|
|
166
|
+
new: "border-status-warning-border bg-status-warning-bg text-status-warning-fg",
|
|
167
|
+
read: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
168
|
+
opened: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
169
|
+
sent: "border-status-info-border bg-status-info-bg text-status-info-fg",
|
|
170
|
+
draft: "border-status-pending-border bg-status-pending-bg text-status-pending-fg"
|
|
171
|
+
};
|
|
159
172
|
function effectiveStatus(t) {
|
|
160
173
|
return t.canReply === false ? "viewing" : t.status;
|
|
161
174
|
}
|
|
@@ -258,8 +271,8 @@ function MessageView({
|
|
|
258
271
|
] })
|
|
259
272
|
] }),
|
|
260
273
|
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
|
|
261
|
-
message.receipt ? /* @__PURE__ */ jsxs("span", { className: "
|
|
262
|
-
message.receipt.kind === "new" ? /* @__PURE__ */ jsx(CornerUpLeft, { size: 11 }) : message.receipt.kind === "read" ? /* @__PURE__ */ jsx(CheckCheck, { size: 11 }) : message.receipt.kind === "draft" ? /* @__PURE__ */ jsx(FilePenLine, { size: 11 }) : /* @__PURE__ */ jsx(MailOpen, { size: 11 }),
|
|
274
|
+
message.receipt ? /* @__PURE__ */ jsxs("span", { className: cn("inline-flex items-center gap-1 rounded-md border px-1.5 py-px text-[10px] font-semibold leading-4", RECEIPT_CHIP[message.receipt.kind]), children: [
|
|
275
|
+
message.receipt.kind === "new" ? /* @__PURE__ */ jsx(CornerUpLeft, { size: 11 }) : message.receipt.kind === "read" || message.receipt.kind === "sent" ? /* @__PURE__ */ jsx(CheckCheck, { size: 11 }) : message.receipt.kind === "draft" ? /* @__PURE__ */ jsx(FilePenLine, { size: 11 }) : /* @__PURE__ */ jsx(MailOpen, { size: 11 }),
|
|
263
276
|
message.receipt.label
|
|
264
277
|
] }) : null,
|
|
265
278
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 text-xs", children: message.date }),
|
|
@@ -582,13 +595,13 @@ function ThreadBody({
|
|
|
582
595
|
}, [sortedMessages]);
|
|
583
596
|
const toggle = (id) => setExpanded((e) => __spreadProps(__spreadValues({}, e), { [id]: !e[id] }));
|
|
584
597
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread-body", className: "space-y-2", children: [
|
|
585
|
-
canReply && thread.paused ? /* @__PURE__ */ jsxs("div", { className: "border-status-
|
|
598
|
+
canReply && thread.paused ? /* @__PURE__ */ jsxs("div", { className: "border-status-warning-border bg-status-warning-bg text-status-warning-fg flex items-start gap-2 rounded-md border border-l-4 p-2.5 text-[12px]", children: [
|
|
586
599
|
/* @__PURE__ */ jsx(Pause, { size: 13, className: "mt-0.5 shrink-0" }),
|
|
587
600
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
588
|
-
/* @__PURE__ */ jsx("b", { children: "
|
|
589
|
-
"
|
|
601
|
+
/* @__PURE__ */ jsx("b", { children: "Playbook stopped." }),
|
|
602
|
+
" Follow-up actions for ",
|
|
590
603
|
thread.paused.playbook,
|
|
591
|
-
"
|
|
604
|
+
" won\u2019t send automatically while this conversation is live. Continue it in ",
|
|
592
605
|
tenantName != null ? tenantName : "the app",
|
|
593
606
|
" or Gmail."
|
|
594
607
|
] })
|
|
@@ -694,47 +707,56 @@ function ThreadRow({
|
|
|
694
707
|
const who = (last == null ? void 0 : last.direction) === "outbound" && sameEmail(lastSender == null ? void 0 : lastSender.email, meDisplay == null ? void 0 : meDisplay.email) ? "You" : firstName((_a = lastSender == null ? void 0 : lastSender.name) != null ? _a : "");
|
|
695
708
|
const lastSnippet = last ? messageBodySnippet(last, 120) : "";
|
|
696
709
|
const pill = STATUS_PILL[status];
|
|
697
|
-
return /* @__PURE__ */ jsxs(
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
/* @__PURE__ */ jsx("
|
|
714
|
-
"
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
710
|
+
return /* @__PURE__ */ jsxs(
|
|
711
|
+
"div",
|
|
712
|
+
{
|
|
713
|
+
"data-slot": "conv-thread",
|
|
714
|
+
"data-status": status,
|
|
715
|
+
"data-open": open ? "true" : void 0,
|
|
716
|
+
className: cn("border-border border-b last:border-b-0", THREAD_ROW_ACCENT[status]),
|
|
717
|
+
children: [
|
|
718
|
+
/* @__PURE__ */ jsxs(
|
|
719
|
+
"button",
|
|
720
|
+
{
|
|
721
|
+
type: "button",
|
|
722
|
+
onClick: onToggleOpen,
|
|
723
|
+
"aria-expanded": open,
|
|
724
|
+
className: "flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30",
|
|
725
|
+
children: [
|
|
726
|
+
/* @__PURE__ */ jsx("span", { className: cn("size-2 shrink-0 rounded-full", STATUS_DOT[status]), "aria-hidden": true }),
|
|
727
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
728
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
729
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: thread.subject }),
|
|
730
|
+
/* @__PURE__ */ jsx("span", { className: cn("shrink-0 rounded-md border px-1.5 py-px text-[10px] font-medium leading-4", pill.cls), children: pill.label })
|
|
731
|
+
] }),
|
|
732
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [
|
|
733
|
+
/* @__PURE__ */ jsx("b", { className: "text-foreground/80", children: displayParticipant(thread.contact).name }),
|
|
734
|
+
" \xB7 ",
|
|
735
|
+
who,
|
|
736
|
+
": ",
|
|
737
|
+
lastSnippet
|
|
738
|
+
] })
|
|
739
|
+
] }),
|
|
740
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: thread.lastWhen }),
|
|
741
|
+
open ? /* @__PURE__ */ jsx(ChevronUp, { size: 15, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 15, className: "text-muted-foreground shrink-0" })
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
open ? /* @__PURE__ */ jsx("div", { className: "px-3 pb-3", children: /* @__PURE__ */ jsx(
|
|
746
|
+
ThreadBody,
|
|
747
|
+
{
|
|
748
|
+
thread,
|
|
749
|
+
me,
|
|
750
|
+
tenantName,
|
|
751
|
+
onSendReply,
|
|
752
|
+
onCreateGmailDraft,
|
|
753
|
+
onPreviewReply,
|
|
754
|
+
onOpenInGmail
|
|
755
|
+
}
|
|
756
|
+
) }) : null
|
|
757
|
+
]
|
|
758
|
+
}
|
|
759
|
+
);
|
|
738
760
|
}
|
|
739
761
|
function ConversationPanel({
|
|
740
762
|
threads,
|
|
@@ -751,61 +773,82 @@ function ConversationPanel({
|
|
|
751
773
|
const draft = threads.filter((t) => effectiveStatus(t) === "draft").length;
|
|
752
774
|
const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length;
|
|
753
775
|
const anyPaused = threads.some((t) => t.paused);
|
|
776
|
+
const hubGmailThread = threads.find((t) => canOpenInGmail(t, onOpenInGmail));
|
|
777
|
+
const firstAwaiting = threads.find((t) => effectiveStatus(t) === "awaiting");
|
|
754
778
|
const [hubOpen, setHubOpen] = React.useState(true);
|
|
755
779
|
const [openId, setOpenId] = React.useState(() => {
|
|
756
780
|
if (defaultOpenThreadId) return defaultOpenThreadId;
|
|
757
|
-
const firstActionable = threads.find((t) => ["responded", "draft"].includes(t.status) && t.canReply !== false);
|
|
781
|
+
const firstActionable = threads.find((t) => ["responded", "draft", "awaiting"].includes(t.status) && t.canReply !== false);
|
|
758
782
|
return firstActionable ? firstActionable.threadId : null;
|
|
759
783
|
});
|
|
760
784
|
if (!threads.length) return null;
|
|
761
|
-
const badge = responded > 0 ? { label: "Email response detected", dot: "bg-status-
|
|
762
|
-
const headTitle = responded > 0 ? `${responded} ${responded === 1 ? "reply needs" : "replies need"} your response` : draft > 0 ? `Draft ready on ${draft} ${draft === 1 ? "thread" : "threads"}` : awaiting > 0 ? `Awaiting response
|
|
785
|
+
const badge = responded > 0 ? { label: "Email response detected", dot: "bg-status-warning-fg", ring: "bg-status-warning-fg/30" } : draft > 0 ? { label: "Draft ready", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" } : awaiting > 0 ? { label: "Email sent \xB7 awaiting reply", dot: "bg-status-info-fg", ring: "bg-status-info-fg/30" } : { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" };
|
|
786
|
+
const headTitle = responded > 0 ? `${responded} ${responded === 1 ? "reply needs" : "replies need"} your response` : draft > 0 ? `Draft ready on ${draft} ${draft === 1 ? "thread" : "threads"}` : awaiting > 0 ? awaiting === 1 && firstAwaiting ? `Awaiting a response from ${firstName(displayParticipant(firstAwaiting.contact).name)}` : `Awaiting responses on ${awaiting} threads` : `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`;
|
|
787
|
+
const panelState = responded > 0 ? "responded" : draft > 0 ? "draft" : awaiting > 0 ? "awaiting" : "viewing";
|
|
763
788
|
return /* @__PURE__ */ jsxs(
|
|
764
789
|
"section",
|
|
765
790
|
{
|
|
766
791
|
"data-slot": "conversation-panel",
|
|
767
792
|
"data-responded": responded > 0 ? "true" : void 0,
|
|
768
|
-
|
|
793
|
+
"data-state": panelState,
|
|
794
|
+
className: cn(
|
|
795
|
+
"bg-background overflow-hidden rounded-xl border",
|
|
796
|
+
panelState === "responded" ? "border-status-warning-border" : panelState === "awaiting" ? "border-status-info-border" : "border-border",
|
|
797
|
+
className
|
|
798
|
+
),
|
|
769
799
|
children: [
|
|
770
800
|
/* @__PURE__ */ jsxs(
|
|
771
|
-
"
|
|
801
|
+
"div",
|
|
772
802
|
{
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
803
|
+
"data-slot": "conversation-panel-header",
|
|
804
|
+
className: cn(
|
|
805
|
+
"flex w-full items-center gap-2 px-3 py-2.5",
|
|
806
|
+
panelState === "responded" ? "bg-status-warning-bg/45" : panelState === "awaiting" ? "bg-status-info-bg/55" : "bg-background"
|
|
807
|
+
),
|
|
777
808
|
children: [
|
|
778
809
|
/* @__PURE__ */ jsxs(
|
|
779
|
-
"
|
|
810
|
+
"button",
|
|
780
811
|
{
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
),
|
|
812
|
+
type: "button",
|
|
813
|
+
onClick: () => setHubOpen((v) => !v),
|
|
814
|
+
"aria-expanded": hubOpen,
|
|
815
|
+
className: "flex min-w-0 flex-1 items-center gap-3 text-left",
|
|
786
816
|
children: [
|
|
787
|
-
/* @__PURE__ */ jsxs(
|
|
788
|
-
|
|
789
|
-
|
|
817
|
+
/* @__PURE__ */ jsxs(
|
|
818
|
+
"span",
|
|
819
|
+
{
|
|
820
|
+
"data-slot": "conversation-badge",
|
|
821
|
+
className: cn(
|
|
822
|
+
"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
|
|
823
|
+
responded > 0 ? "bg-status-warning-bg text-status-warning-fg" : awaiting > 0 ? "bg-status-info-bg text-status-info-fg" : draft > 0 ? "bg-status-pending-bg text-status-pending-fg" : "bg-muted text-muted-foreground"
|
|
824
|
+
),
|
|
825
|
+
children: [
|
|
826
|
+
/* @__PURE__ */ jsxs("span", { className: "relative inline-flex size-2", children: [
|
|
827
|
+
/* @__PURE__ */ jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", badge.ring) }),
|
|
828
|
+
/* @__PURE__ */ jsx("span", { className: cn("relative inline-flex size-2 rounded-full", badge.dot) })
|
|
829
|
+
] }),
|
|
830
|
+
badge.label
|
|
831
|
+
]
|
|
832
|
+
}
|
|
833
|
+
),
|
|
834
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
835
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold leading-tight", children: headTitle }),
|
|
836
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mt-0.5 block truncate text-xs", children: [
|
|
837
|
+
threads.length,
|
|
838
|
+
" ",
|
|
839
|
+
threads.length === 1 ? "thread" : "threads",
|
|
840
|
+
" on this action",
|
|
841
|
+
anyPaused ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
842
|
+
" \xB7 ",
|
|
843
|
+
/* @__PURE__ */ jsx("b", { children: "playbook stopped" })
|
|
844
|
+
] }) : null
|
|
845
|
+
] })
|
|
790
846
|
] }),
|
|
791
|
-
|
|
847
|
+
hubOpen ? /* @__PURE__ */ jsx(ChevronUp, { size: 16, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 16, className: "text-muted-foreground shrink-0" })
|
|
792
848
|
]
|
|
793
849
|
}
|
|
794
850
|
),
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold leading-tight", children: headTitle }),
|
|
797
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mt-0.5 block truncate text-xs", children: [
|
|
798
|
-
threads.length,
|
|
799
|
-
" ",
|
|
800
|
-
threads.length === 1 ? "thread" : "threads",
|
|
801
|
-
" on this action",
|
|
802
|
-
anyPaused ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
803
|
-
" \xB7 ",
|
|
804
|
-
/* @__PURE__ */ jsx("b", { children: "playbook stopped" })
|
|
805
|
-
] }) : null
|
|
806
|
-
] })
|
|
807
|
-
] }),
|
|
808
|
-
hubOpen ? /* @__PURE__ */ jsx(ChevronUp, { size: 16, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 16, className: "text-muted-foreground shrink-0" })
|
|
851
|
+
hubGmailThread ? /* @__PURE__ */ jsx("div", { className: "shrink-0", onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(OpenInGmailButton, { thread: hubGmailThread, onOpenInGmail }) }) : null
|
|
809
852
|
]
|
|
810
853
|
}
|
|
811
854
|
),
|