@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.
@@ -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" | "link" | null | undefined;
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" | "link" | null | undefined;
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: "New reply", cls: "bg-status-active-bg text-status-active-fg border-status-active-border" },
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: "Awaiting", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
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-active-fg",
154
+ responded: "bg-status-warning-fg",
155
155
  draft: "bg-status-pending-fg",
156
- awaiting: "bg-status-pending-fg",
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: "text-muted-foreground inline-flex items-center gap-1 text-[11px]", children: [
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-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
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: "Follow-up actions stopped." }),
589
- " Your ",
601
+ /* @__PURE__ */ jsx("b", { children: "Playbook stopped." }),
602
+ " Follow-up actions for ",
590
603
  thread.paused.playbook,
591
- " next steps won\u2019t send automatically while this conversation is live. Continue it in ",
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("div", { "data-slot": "conv-thread", "data-open": open ? "true" : void 0, className: "border-border border-b last:border-b-0", children: [
698
- /* @__PURE__ */ jsxs(
699
- "button",
700
- {
701
- type: "button",
702
- onClick: onToggleOpen,
703
- "aria-expanded": open,
704
- className: "flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30",
705
- children: [
706
- /* @__PURE__ */ jsx("span", { className: cn("size-2 shrink-0 rounded-full", STATUS_DOT[status]), "aria-hidden": true }),
707
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
708
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
709
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: thread.subject }),
710
- /* @__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 })
711
- ] }),
712
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [
713
- /* @__PURE__ */ jsx("b", { className: "text-foreground/80", children: displayParticipant(thread.contact).name }),
714
- " \xB7 ",
715
- who,
716
- ": ",
717
- lastSnippet
718
- ] })
719
- ] }),
720
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: thread.lastWhen }),
721
- open ? /* @__PURE__ */ jsx(ChevronUp, { size: 15, className: "text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { size: 15, className: "text-muted-foreground shrink-0" })
722
- ]
723
- }
724
- ),
725
- open ? /* @__PURE__ */ jsx("div", { className: "px-3 pb-3", children: /* @__PURE__ */ jsx(
726
- ThreadBody,
727
- {
728
- thread,
729
- me,
730
- tenantName,
731
- onSendReply,
732
- onCreateGmailDraft,
733
- onPreviewReply,
734
- onOpenInGmail
735
- }
736
- ) }) : null
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
- const firstActionable = threads.find((t) => ["responded", "draft"].includes(t.status) && t.canReply !== false);
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-active-fg", ring: "bg-status-active-fg/30" } : draft > 0 ? { label: "Draft ready", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" } : awaiting > 0 ? { label: "Awaiting response", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" } : { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" };
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 on ${awaiting} ${awaiting === 1 ? "thread" : "threads"}` : `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`;
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
- className: cn("border-border bg-background overflow-hidden rounded-xl border", className),
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
- "button",
802
+ "div",
772
803
  {
773
- type: "button",
774
- onClick: () => setHubOpen((v) => !v),
775
- "aria-expanded": hubOpen,
776
- className: "flex w-full items-center gap-3 px-3 py-2.5 text-left",
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
- "span",
811
+ "button",
780
812
  {
781
- "data-slot": "conversation-badge",
782
- className: cn(
783
- "inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
784
- responded > 0 ? "bg-status-active-bg text-status-active-fg" : draft > 0 || awaiting > 0 ? "bg-status-pending-bg text-status-pending-fg" : "bg-muted text-muted-foreground"
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("span", { className: "relative inline-flex size-2", children: [
788
- /* @__PURE__ */ jsx("span", { className: cn("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", badge.ring) }),
789
- /* @__PURE__ */ jsx("span", { className: cn("relative inline-flex size-2 rounded-full", badge.dot) })
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
- badge.label
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__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
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
  ),