@handled-ai/design-system 0.20.12 → 0.20.14
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/conversation-panel.js +129 -85
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/email-body.js +39 -1
- package/dist/components/email-body.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +193 -11
- package/src/components/__tests__/email-body.test.tsx +32 -0
- package/src/components/conversation-panel.tsx +90 -29
- package/src/components/email-body.tsx +46 -1
|
@@ -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> & {
|
|
@@ -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,
|
|
@@ -747,65 +769,87 @@ function ConversationPanel({
|
|
|
747
769
|
defaultOpenThreadId,
|
|
748
770
|
className
|
|
749
771
|
}) {
|
|
772
|
+
var _a, _b, _c, _d, _e;
|
|
750
773
|
const responded = threads.filter((t) => t.status === "responded" && t.canReply !== false).length;
|
|
751
774
|
const draft = threads.filter((t) => effectiveStatus(t) === "draft").length;
|
|
752
775
|
const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length;
|
|
753
776
|
const anyPaused = threads.some((t) => t.paused);
|
|
777
|
+
const prioritizedThread = (_b = (_a = threads.find((t) => t.status === "responded" && t.canReply !== false)) != null ? _a : threads.find((t) => effectiveStatus(t) === "draft")) != null ? _b : threads.find((t) => effectiveStatus(t) === "awaiting");
|
|
778
|
+
const hubGmailThread = (_e = (_d = (_c = threads.find((t) => t.status === "responded" && t.canReply !== false && canOpenInGmail(t, onOpenInGmail))) != null ? _c : threads.find((t) => effectiveStatus(t) === "draft" && canOpenInGmail(t, onOpenInGmail))) != null ? _d : threads.find((t) => effectiveStatus(t) === "awaiting" && canOpenInGmail(t, onOpenInGmail))) != null ? _e : threads.find((t) => canOpenInGmail(t, onOpenInGmail));
|
|
779
|
+
const firstAwaiting = threads.find((t) => effectiveStatus(t) === "awaiting");
|
|
754
780
|
const [hubOpen, setHubOpen] = React.useState(true);
|
|
755
781
|
const [openId, setOpenId] = React.useState(() => {
|
|
756
782
|
if (defaultOpenThreadId) return defaultOpenThreadId;
|
|
757
|
-
|
|
758
|
-
return firstActionable ? firstActionable.threadId : null;
|
|
783
|
+
return prioritizedThread ? prioritizedThread.threadId : null;
|
|
759
784
|
});
|
|
760
785
|
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
|
|
786
|
+
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" };
|
|
787
|
+
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"}`;
|
|
788
|
+
const panelState = responded > 0 ? "responded" : draft > 0 ? "draft" : awaiting > 0 ? "awaiting" : "viewing";
|
|
763
789
|
return /* @__PURE__ */ jsxs(
|
|
764
790
|
"section",
|
|
765
791
|
{
|
|
766
792
|
"data-slot": "conversation-panel",
|
|
767
793
|
"data-responded": responded > 0 ? "true" : void 0,
|
|
768
|
-
|
|
794
|
+
"data-state": panelState,
|
|
795
|
+
className: cn(
|
|
796
|
+
"bg-background overflow-hidden rounded-xl border",
|
|
797
|
+
panelState === "responded" ? "border-status-warning-border" : panelState === "awaiting" ? "border-status-info-border" : "border-border",
|
|
798
|
+
className
|
|
799
|
+
),
|
|
769
800
|
children: [
|
|
770
801
|
/* @__PURE__ */ jsxs(
|
|
771
|
-
"
|
|
802
|
+
"div",
|
|
772
803
|
{
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
804
|
+
"data-slot": "conversation-panel-header",
|
|
805
|
+
className: cn(
|
|
806
|
+
"flex w-full items-center gap-2 px-3 py-2.5",
|
|
807
|
+
panelState === "responded" ? "bg-status-warning-bg/45" : panelState === "awaiting" ? "bg-status-info-bg/55" : "bg-background"
|
|
808
|
+
),
|
|
777
809
|
children: [
|
|
778
810
|
/* @__PURE__ */ jsxs(
|
|
779
|
-
"
|
|
811
|
+
"button",
|
|
780
812
|
{
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
),
|
|
813
|
+
type: "button",
|
|
814
|
+
onClick: () => setHubOpen((v) => !v),
|
|
815
|
+
"aria-expanded": hubOpen,
|
|
816
|
+
className: "flex min-w-0 flex-1 items-center gap-3 text-left",
|
|
786
817
|
children: [
|
|
787
|
-
/* @__PURE__ */ jsxs(
|
|
788
|
-
|
|
789
|
-
|
|
818
|
+
/* @__PURE__ */ jsxs(
|
|
819
|
+
"span",
|
|
820
|
+
{
|
|
821
|
+
"data-slot": "conversation-badge",
|
|
822
|
+
className: cn(
|
|
823
|
+
"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
|
|
824
|
+
responded > 0 ? "bg-status-warning-bg text-status-warning-fg" : draft > 0 ? "bg-status-pending-bg text-status-pending-fg" : awaiting > 0 ? "bg-status-info-bg text-status-info-fg" : "bg-muted text-muted-foreground"
|
|
825
|
+
),
|
|
826
|
+
children: [
|
|
827
|
+
/* @__PURE__ */ jsxs("span", { className: "relative inline-flex size-2", children: [
|
|
828
|
+
/* @__PURE__ */ jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", badge.ring) }),
|
|
829
|
+
/* @__PURE__ */ jsx("span", { className: cn("relative inline-flex size-2 rounded-full", badge.dot) })
|
|
830
|
+
] }),
|
|
831
|
+
badge.label
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
),
|
|
835
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
836
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold leading-tight", children: headTitle }),
|
|
837
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mt-0.5 block truncate text-xs", children: [
|
|
838
|
+
threads.length,
|
|
839
|
+
" ",
|
|
840
|
+
threads.length === 1 ? "thread" : "threads",
|
|
841
|
+
" on this action",
|
|
842
|
+
anyPaused ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
843
|
+
" \xB7 ",
|
|
844
|
+
/* @__PURE__ */ jsx("b", { children: "playbook stopped" })
|
|
845
|
+
] }) : null
|
|
846
|
+
] })
|
|
790
847
|
] }),
|
|
791
|
-
|
|
848
|
+
hubOpen ? /* @__PURE__ */ jsx(ChevronUp, { size: 16, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 16, className: "text-muted-foreground shrink-0" })
|
|
792
849
|
]
|
|
793
850
|
}
|
|
794
851
|
),
|
|
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" })
|
|
852
|
+
hubGmailThread ? /* @__PURE__ */ jsx("div", { className: "shrink-0", onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsx(OpenInGmailButton, { thread: hubGmailThread, onOpenInGmail }) }) : null
|
|
809
853
|
]
|
|
810
854
|
}
|
|
811
855
|
),
|