@dbx-tools/ui-email 0.6.67 → 0.6.69
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/README.md +10 -8
- package/package.json +6 -6
- package/src/react/email-approval-card.tsx +71 -20
package/README.md
CHANGED
|
@@ -49,9 +49,9 @@ const draft = email.emailMessageSchema.parse(toolCall.args);
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
`EmailApprovalCard` is the chat-facing component for the `send_email` tool. It
|
|
52
|
-
renders
|
|
53
|
-
|
|
54
|
-
transport decisions to the host app.
|
|
52
|
+
renders mail-client chrome (sender, recipients, subject, attachments) around the
|
|
53
|
+
branded React Email card, plus Approve/Deny actions while leaving tool-call
|
|
54
|
+
state and transport decisions to the host app.
|
|
55
55
|
|
|
56
56
|
Wire `onApprove` and `onDeny` to the chat framework's tool-result mechanism.
|
|
57
57
|
The component deliberately does not call the email API itself; the server-side
|
|
@@ -65,11 +65,13 @@ import { EmailPreview } from "@dbx-tools/ui-email/react";
|
|
|
65
65
|
<EmailPreview email={draft} />;
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Use `EmailPreview` when a page needs the same
|
|
69
|
-
buttons, such as a review queue, audit log, or test harness.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
Use `EmailPreview` when a page needs the same received-mail presentation without
|
|
69
|
+
action buttons, such as a review queue, audit log, or test harness. Envelope
|
|
70
|
+
fields live in the client chrome; the card itself matches delivered HTML. It
|
|
71
|
+
projects the nearest `BrandProvider` context into email-safe colors and
|
|
72
|
+
typography, so a live site brand picker updates the preview too. Pass the
|
|
73
|
+
optional `brand` prop when a specific message needs an independent campaign or
|
|
74
|
+
partner identity.
|
|
73
75
|
|
|
74
76
|
## Provide A Compose View
|
|
75
77
|
|
package/package.json
CHANGED
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"typescript": "^5.9.3"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@dbx-tools/shared-core": "0.6.
|
|
28
|
-
"@dbx-tools/shared-email": "0.6.
|
|
29
|
-
"@dbx-tools/shared-email-template": "0.6.
|
|
30
|
-
"@dbx-tools/ui-appkit": "0.6.
|
|
31
|
-
"@dbx-tools/ui-branding": "0.6.
|
|
27
|
+
"@dbx-tools/shared-core": "0.6.69",
|
|
28
|
+
"@dbx-tools/shared-email": "0.6.69",
|
|
29
|
+
"@dbx-tools/shared-email-template": "0.6.69",
|
|
30
|
+
"@dbx-tools/ui-appkit": "0.6.69",
|
|
31
|
+
"@dbx-tools/ui-branding": "0.6.69",
|
|
32
32
|
"lucide-react": "^0.554.0",
|
|
33
33
|
"react": "^19.2.4",
|
|
34
34
|
"react-dom": "^19.2.4"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
-
"version": "0.6.
|
|
40
|
+
"version": "0.6.69",
|
|
41
41
|
"type": "module",
|
|
42
42
|
"exports": {
|
|
43
43
|
"./react": "./src/react/index.ts",
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EmailCard,
|
|
3
3
|
emailBrandFromContext,
|
|
4
|
+
resolveEmailBrand,
|
|
4
5
|
type EmailBrand,
|
|
5
6
|
} from "@dbx-tools/shared-email-template";
|
|
6
7
|
import { Button } from "@dbx-tools/ui-appkit/react";
|
|
7
8
|
import { useBrand } from "@dbx-tools/ui-branding/react";
|
|
8
|
-
import { CheckIcon, MailIcon, XIcon } from "lucide-react";
|
|
9
|
+
import { CheckIcon, MailIcon, PaperclipIcon, XIcon } from "lucide-react";
|
|
9
10
|
import { attachmentNames, joinAddresses, type EmailDraft } from "./fields.ts";
|
|
10
11
|
|
|
11
12
|
// Presentational pieces for an outbound email awaiting a human Approve /
|
|
12
|
-
// Deny:
|
|
13
|
-
//
|
|
13
|
+
// Deny: a mail-client chrome (sender, recipients, subject, attachments)
|
|
14
|
+
// around the shared React Email card, plus an approval card wrapping it.
|
|
14
15
|
// State and the resolve transport belong to the caller; these components
|
|
15
16
|
// only render and report intent. The editable counterpart is
|
|
16
17
|
// `EmailComposeView` in `./email-compose`; both share `./fields` and
|
|
@@ -25,31 +26,81 @@ export interface EmailPreviewProps {
|
|
|
25
26
|
brand?: EmailBrand;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/** Initials for the sender avatar when no logo is available. */
|
|
30
|
+
function senderInitials(name: string): string {
|
|
31
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
32
|
+
if (parts.length === 0) return "?";
|
|
33
|
+
if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();
|
|
34
|
+
return `${parts[0]![0] ?? ""}${parts[1]![0] ?? ""}`.toUpperCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One muted "Label: value" row in the client chrome. */
|
|
38
|
+
const ChromeRow = ({ label, value }: { label: string; value: string }) => (
|
|
39
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
40
|
+
<span className="font-medium text-foreground/70">{label}</span> {value}
|
|
41
|
+
</p>
|
|
42
|
+
);
|
|
43
|
+
|
|
28
44
|
/**
|
|
29
|
-
* Render an email draft
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
45
|
+
* Render an email draft the way a mail client shows a received message: sender
|
|
46
|
+
* identity, recipients, subject, and attachment chips in chrome around the
|
|
47
|
+
* same branded React Email card used for delivery. Envelope fields stay out of
|
|
48
|
+
* the delivered HTML — they are transport metadata, not body content.
|
|
33
49
|
*/
|
|
34
50
|
export const EmailPreview = ({ email, brand }: EmailPreviewProps) => {
|
|
35
51
|
const { context } = useBrand();
|
|
52
|
+
const theme = resolveEmailBrand(brand ?? emailBrandFromContext(context));
|
|
36
53
|
const to = joinAddresses(email.to);
|
|
37
54
|
const cc = joinAddresses(email.cc);
|
|
38
55
|
const bcc = joinAddresses(email.bcc);
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (bcc) headers.push(["Bcc", bcc]);
|
|
44
|
-
if (attachments) headers.push(["Files", attachments]);
|
|
56
|
+
const subject = email.subject?.trim() || "Message";
|
|
57
|
+
const files = email.attachments?.map((att) => att.filename).filter(Boolean) ?? [];
|
|
58
|
+
const attachmentSummary = attachmentNames(email.attachments);
|
|
59
|
+
|
|
45
60
|
return (
|
|
46
|
-
<div className="overflow-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
<div className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
|
|
62
|
+
<div className="space-y-3 border-b border-border bg-background px-4 py-3">
|
|
63
|
+
<div className="flex items-start gap-3">
|
|
64
|
+
<div
|
|
65
|
+
className="flex size-10 shrink-0 items-center justify-center rounded-full text-xs font-semibold"
|
|
66
|
+
style={{ backgroundColor: theme.accent, color: theme.onAccent }}
|
|
67
|
+
aria-hidden
|
|
68
|
+
>
|
|
69
|
+
{senderInitials(theme.name)}
|
|
70
|
+
</div>
|
|
71
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
72
|
+
<div className="flex items-baseline justify-between gap-3">
|
|
73
|
+
<div className="min-w-0">
|
|
74
|
+
<p className="truncate text-sm font-semibold text-foreground">{theme.name}</p>
|
|
75
|
+
{theme.website ? (
|
|
76
|
+
<p className="truncate text-xs text-muted-foreground">{theme.website}</p>
|
|
77
|
+
) : null}
|
|
78
|
+
</div>
|
|
79
|
+
<time className="shrink-0 text-xs text-muted-foreground">Just now</time>
|
|
80
|
+
</div>
|
|
81
|
+
{to ? <ChromeRow label="To" value={to} /> : null}
|
|
82
|
+
{cc ? <ChromeRow label="Cc" value={cc} /> : null}
|
|
83
|
+
{bcc ? <ChromeRow label="Bcc" value={bcc} /> : null}
|
|
84
|
+
<p className="pt-1 text-sm font-medium leading-snug text-foreground">{subject}</p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
{files.length > 0 ? (
|
|
88
|
+
<ul className="flex flex-wrap gap-1.5" aria-label={`Attachments: ${attachmentSummary}`}>
|
|
89
|
+
{files.map((filename) => (
|
|
90
|
+
<li
|
|
91
|
+
key={filename}
|
|
92
|
+
className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-muted/40 px-2 py-1 text-xs text-foreground"
|
|
93
|
+
>
|
|
94
|
+
<PaperclipIcon className="size-3 shrink-0 text-muted-foreground" />
|
|
95
|
+
<span className="truncate">{filename}</span>
|
|
96
|
+
</li>
|
|
97
|
+
))}
|
|
98
|
+
</ul>
|
|
99
|
+
) : null}
|
|
100
|
+
</div>
|
|
101
|
+
<div className="bg-muted/30 p-3">
|
|
102
|
+
<EmailCard subject={subject} body={email.body || ""} brand={theme} />
|
|
103
|
+
</div>
|
|
53
104
|
</div>
|
|
54
105
|
);
|
|
55
106
|
};
|