@dbx-tools/shared-email-template 0.6.48
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 +82 -0
- package/index.ts +7 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +6 -0
- package/lib/src/template.d.ts +53 -0
- package/lib/src/template.js +194 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +66 -0
- package/src/template.tsx +351 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @dbx-tools/shared-email-template
|
|
2
|
+
|
|
3
|
+
Universal React Email presentation shared by dbx-tools server and browser email
|
|
4
|
+
surfaces.
|
|
5
|
+
|
|
6
|
+
Key features:
|
|
7
|
+
|
|
8
|
+
- Responsive React Email document with email-safe inline styling.
|
|
9
|
+
- Reusable message-body component for approval and compose previews.
|
|
10
|
+
- Repository branding applied by default through `BrandContext`.
|
|
11
|
+
- Consumer brand overrides without coupling templates to AppKit or SMTP.
|
|
12
|
+
- Rich content rendered consistently in the inbox and browser.
|
|
13
|
+
|
|
14
|
+
## Why A Separate Package
|
|
15
|
+
|
|
16
|
+
The presentation layer has two consumers with incompatible dependency needs, so
|
|
17
|
+
it lives on its own:
|
|
18
|
+
|
|
19
|
+
- `@dbx-tools/email` is a Node/AppKit package: it depends on
|
|
20
|
+
`@databricks/appkit` and `nodemailer`. Templates that lived there could not be
|
|
21
|
+
imported by a browser bundle.
|
|
22
|
+
- `@dbx-tools/shared-email` is a pure zod contract package consumed by
|
|
23
|
+
non-AppKit callers such as `@dbx-tools/cli-tunnel`. Templates that lived there
|
|
24
|
+
would push React and `@react-email/components` onto every schema consumer.
|
|
25
|
+
- `@dbx-tools/ui-email` is a React/DOM package. Templates that lived there would
|
|
26
|
+
make the Node renderer depend on a DOM-typed package.
|
|
27
|
+
|
|
28
|
+
This package depends on neither AppKit nor SMTP nor the DOM, so any runtime that
|
|
29
|
+
can render React can produce the same branded email — including callers outside
|
|
30
|
+
AppKit entirely.
|
|
31
|
+
|
|
32
|
+
## Render The Shared Template
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { EmailDocument } from "@dbx-tools/shared-email-template";
|
|
36
|
+
|
|
37
|
+
<EmailDocument subject="Incident update" body="## Resolved\nAll systems are healthy." />;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Node runtimes should pass this component to `@react-email/render`.
|
|
41
|
+
`@dbx-tools/email` does that automatically for SMTP and local outbox delivery.
|
|
42
|
+
|
|
43
|
+
## Reuse The Body Preview
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { EmailBody } from "@dbx-tools/shared-email-template";
|
|
47
|
+
|
|
48
|
+
<EmailBody body={draft.body} />;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The body preview and complete document share typography and content styling. The
|
|
52
|
+
full document adds the branded header, subject, envelope metadata, and footer.
|
|
53
|
+
|
|
54
|
+
Use `EmailCard` when a browser surface should show that complete presentation
|
|
55
|
+
without rendering an outer `<html>` or `<body>` element:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { EmailCard } from "@dbx-tools/shared-email-template";
|
|
59
|
+
|
|
60
|
+
<EmailCard subject={draft.subject} body={draft.body} headers={[["To", draft.to.join(", ")]]} />;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Apply A Brand
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { brand } from "@dbx-tools/shared-core";
|
|
67
|
+
import { emailBrandFromContext } from "@dbx-tools/shared-email-template";
|
|
68
|
+
|
|
69
|
+
const emailBrand = emailBrandFromContext(brand.defaultBrandContext);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Omitting `brand` uses the dbx-tools brand. A consumer may supply its own colors,
|
|
73
|
+
font, name, tagline, website, and fetchable logo URL.
|
|
74
|
+
|
|
75
|
+
## Modules
|
|
76
|
+
|
|
77
|
+
- `.` - `EmailDocument`, `EmailCard`, `EmailBody`, brand types/defaults, and
|
|
78
|
+
content normalization.
|
|
79
|
+
|
|
80
|
+
Pair this package with [`@dbx-tools/shared-email`](../email) for wire schemas,
|
|
81
|
+
[`@dbx-tools/email`](../../node/email) for delivery, and
|
|
82
|
+
[`@dbx-tools/ui-email`](../../ui/email) for approval and compose UI.
|
package/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
|
|
5
|
+
export * as template from "./src/template.tsx";
|
|
6
|
+
export { emailBrandFromContext, defaultEmailBrand, resolveEmailBrand, normalizeEmailMarkdown, EmailBody, EmailCard, EmailDocument } from "./src/template.tsx";
|
|
7
|
+
export type { EmailBrand, ResolvedEmailBrand, EmailBodyProps, EmailDocumentProps } from "./src/template.tsx";
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * as template from "./src/template.tsx";
|
|
2
|
+
export { emailBrandFromContext, defaultEmailBrand, resolveEmailBrand, normalizeEmailMarkdown, EmailBody, EmailCard, EmailDocument } from "./src/template.tsx";
|
|
3
|
+
export type { EmailBrand, ResolvedEmailBrand, EmailBodyProps, EmailDocumentProps } from "./src/template.tsx";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export * as template from "./src/template.js";
|
|
5
|
+
export { emailBrandFromContext, defaultEmailBrand, resolveEmailBrand, normalizeEmailMarkdown, EmailBody, EmailCard, EmailDocument } from "./src/template.js";
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssUUFBUSxNQUFNLG1CQUFvQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxpQkFBaUIsRUFBRSxpQkFBaUIsRUFBRSxzQkFBc0IsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFvQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0ICogYXMgdGVtcGxhdGUgZnJvbSBcIi4vc3JjL3RlbXBsYXRlLnRzeFwiO1xuZXhwb3J0IHsgZW1haWxCcmFuZEZyb21Db250ZXh0LCBkZWZhdWx0RW1haWxCcmFuZCwgcmVzb2x2ZUVtYWlsQnJhbmQsIG5vcm1hbGl6ZUVtYWlsTWFya2Rvd24sIEVtYWlsQm9keSwgRW1haWxDYXJkLCBFbWFpbERvY3VtZW50IH0gZnJvbSBcIi4vc3JjL3RlbXBsYXRlLnRzeFwiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbEJyYW5kLCBSZXNvbHZlZEVtYWlsQnJhbmQsIEVtYWlsQm9keVByb3BzLCBFbWFpbERvY3VtZW50UHJvcHMgfSBmcm9tIFwiLi9zcmMvdGVtcGxhdGUudHN4XCI7XG4iXX0=
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal React Email presentation for dbx-tools email flows.
|
|
3
|
+
*
|
|
4
|
+
* The document component renders on the server through `@react-email/render`;
|
|
5
|
+
* the body component is reused directly by browser approval and compose views.
|
|
6
|
+
* Both default to the repository brand while accepting a consumer override.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { type BrandContext } from "@dbx-tools/shared-core";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
/** Email-safe brand values used by the React Email template. */
|
|
13
|
+
export interface EmailBrand {
|
|
14
|
+
accent: string;
|
|
15
|
+
onAccent?: string;
|
|
16
|
+
fontFamily: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
logoUrl?: string;
|
|
19
|
+
background?: string;
|
|
20
|
+
surface?: string;
|
|
21
|
+
foreground?: string;
|
|
22
|
+
muted?: string;
|
|
23
|
+
border?: string;
|
|
24
|
+
tagline?: string;
|
|
25
|
+
website?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Complete values after applying the dbx-tools default brand. */
|
|
28
|
+
export type ResolvedEmailBrand = Required<Pick<EmailBrand, "accent" | "onAccent" | "fontFamily" | "name" | "background" | "surface" | "foreground" | "muted" | "border" | "tagline">> & Pick<EmailBrand, "logoUrl" | "website">;
|
|
29
|
+
/** Project a portable brand context into email-safe values. */
|
|
30
|
+
export declare const emailBrandFromContext: (context: BrandContext) => EmailBrand;
|
|
31
|
+
/** The repository brand used by every email unless explicitly overridden. */
|
|
32
|
+
export declare const defaultEmailBrand: EmailBrand;
|
|
33
|
+
/** Merge a partial consumer brand over the repository default. */
|
|
34
|
+
export declare const resolveEmailBrand: (input?: EmailBrand) => ResolvedEmailBrand;
|
|
35
|
+
/** Normalize indentation in authored content without constraining its structure. */
|
|
36
|
+
export declare const normalizeEmailMarkdown: (value: string) => string;
|
|
37
|
+
/** Props for the reusable message-body component. */
|
|
38
|
+
export interface EmailBodyProps {
|
|
39
|
+
body: string;
|
|
40
|
+
brand?: EmailBrand;
|
|
41
|
+
}
|
|
42
|
+
/** Render rich message content with email-client-safe inline styles. */
|
|
43
|
+
export declare const EmailBody: ({ body, brand: brandInput }: EmailBodyProps) => React.JSX.Element;
|
|
44
|
+
/** Props for the complete React Email document. */
|
|
45
|
+
export interface EmailDocumentProps extends EmailBodyProps {
|
|
46
|
+
subject?: string;
|
|
47
|
+
headers?: ReadonlyArray<readonly [string, string]>;
|
|
48
|
+
footer?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Branded message card shared by the full document and browser previews. */
|
|
51
|
+
export declare const EmailCard: ({ body, subject, headers, footer, brand: brandInput, }: EmailDocumentProps) => React.JSX.Element;
|
|
52
|
+
/** Complete responsive, branded React Email document. */
|
|
53
|
+
export declare const EmailDocument: (props: EmailDocumentProps) => React.JSX.Element;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Universal React Email presentation for dbx-tools email flows.
|
|
4
|
+
*
|
|
5
|
+
* The document component renders on the server through `@react-email/render`;
|
|
6
|
+
* the body component is reused directly by browser approval and compose views.
|
|
7
|
+
* Both default to the repository brand while accepting a consumer override.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { brand } from "@dbx-tools/shared-core";
|
|
12
|
+
import { Body, Column, Container, Head, Heading, Hr, Html, Img, Link, Markdown, Preview, Row, Section, Text, } from "@react-email/components";
|
|
13
|
+
function isRenderableImageUrl(value) {
|
|
14
|
+
return typeof value === "string" && /^(?:https?:|data:|cid:)/i.test(value);
|
|
15
|
+
}
|
|
16
|
+
/** Project a portable brand context into email-safe values. */
|
|
17
|
+
export const emailBrandFromContext = (context) => {
|
|
18
|
+
const logo = context.assets.logo.dark ?? context.assets.logo.light;
|
|
19
|
+
return {
|
|
20
|
+
accent: context.colors.primary,
|
|
21
|
+
onAccent: "#ffffff",
|
|
22
|
+
fontFamily: context.typography.sans,
|
|
23
|
+
name: context.name,
|
|
24
|
+
background: context.colors.surface,
|
|
25
|
+
surface: context.colors.background,
|
|
26
|
+
foreground: context.colors.foreground,
|
|
27
|
+
muted: context.colors.muted,
|
|
28
|
+
border: context.colors.border,
|
|
29
|
+
tagline: context.tagline,
|
|
30
|
+
website: context.links.website,
|
|
31
|
+
...(isRenderableImageUrl(logo) ? { logoUrl: logo } : {}),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** The repository brand used by every email unless explicitly overridden. */
|
|
35
|
+
export const defaultEmailBrand = emailBrandFromContext(brand.defaultBrandContext);
|
|
36
|
+
/** Merge a partial consumer brand over the repository default. */
|
|
37
|
+
export const resolveEmailBrand = (input) => {
|
|
38
|
+
const merged = { ...defaultEmailBrand, ...input };
|
|
39
|
+
return {
|
|
40
|
+
accent: merged.accent,
|
|
41
|
+
onAccent: merged.onAccent ?? "#ffffff",
|
|
42
|
+
fontFamily: merged.fontFamily,
|
|
43
|
+
name: merged.name ?? brand.defaultBrandContext.name,
|
|
44
|
+
background: merged.background ?? brand.defaultBrandContext.colors.surface,
|
|
45
|
+
surface: merged.surface ?? brand.defaultBrandContext.colors.background,
|
|
46
|
+
foreground: merged.foreground ?? brand.defaultBrandContext.colors.foreground,
|
|
47
|
+
muted: merged.muted ?? brand.defaultBrandContext.colors.muted,
|
|
48
|
+
border: merged.border ?? brand.defaultBrandContext.colors.border,
|
|
49
|
+
tagline: merged.tagline ?? brand.defaultBrandContext.tagline,
|
|
50
|
+
...(merged.logoUrl ? { logoUrl: merged.logoUrl } : {}),
|
|
51
|
+
...(merged.website ? { website: merged.website } : {}),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** Normalize indentation in authored content without constraining its structure. */
|
|
55
|
+
export const normalizeEmailMarkdown = (value) => {
|
|
56
|
+
const lines = value.replace(/\r\n?/g, "\n").split("\n");
|
|
57
|
+
while (lines[0]?.trim() === "")
|
|
58
|
+
lines.shift();
|
|
59
|
+
while (lines.at(-1)?.trim() === "")
|
|
60
|
+
lines.pop();
|
|
61
|
+
const nonBlank = lines.filter((line) => line.trim().length > 0);
|
|
62
|
+
const indent = nonBlank.reduce((minimum, line) => {
|
|
63
|
+
const width = line.match(/^\s*/)?.[0].length ?? 0;
|
|
64
|
+
return Math.min(minimum, width);
|
|
65
|
+
}, Number.POSITIVE_INFINITY);
|
|
66
|
+
if (!Number.isFinite(indent) || indent === 0)
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
return lines.map((line) => line.slice(Math.min(indent, line.length))).join("\n");
|
|
69
|
+
};
|
|
70
|
+
/** Render rich message content with email-client-safe inline styles. */
|
|
71
|
+
export const EmailBody = ({ body, brand: brandInput }) => {
|
|
72
|
+
const theme = resolveEmailBrand(brandInput);
|
|
73
|
+
return (_jsx(Markdown, { markdownContainerStyles: {
|
|
74
|
+
color: theme.foreground,
|
|
75
|
+
fontFamily: theme.fontFamily,
|
|
76
|
+
fontSize: "15px",
|
|
77
|
+
lineHeight: "1.65",
|
|
78
|
+
}, markdownCustomStyles: {
|
|
79
|
+
h1: { color: theme.foreground, fontSize: "30px", lineHeight: "1.2", margin: "0 0 20px" },
|
|
80
|
+
h2: { color: theme.foreground, fontSize: "23px", lineHeight: "1.3", margin: "28px 0 12px" },
|
|
81
|
+
h3: { color: theme.foreground, fontSize: "18px", lineHeight: "1.4", margin: "24px 0 10px" },
|
|
82
|
+
p: { margin: "0 0 16px" },
|
|
83
|
+
link: { color: theme.accent, fontWeight: 600, textDecoration: "underline" },
|
|
84
|
+
blockQuote: {
|
|
85
|
+
backgroundColor: theme.background,
|
|
86
|
+
borderLeft: `4px solid ${theme.accent}`,
|
|
87
|
+
borderRadius: "0 10px 10px 0",
|
|
88
|
+
color: theme.foreground,
|
|
89
|
+
margin: "20px 0",
|
|
90
|
+
padding: "14px 18px",
|
|
91
|
+
},
|
|
92
|
+
codeInline: {
|
|
93
|
+
backgroundColor: theme.background,
|
|
94
|
+
borderRadius: "5px",
|
|
95
|
+
color: theme.foreground,
|
|
96
|
+
fontFamily: brand.defaultBrandContext.typography.mono,
|
|
97
|
+
fontSize: "13px",
|
|
98
|
+
padding: "2px 5px",
|
|
99
|
+
},
|
|
100
|
+
codeBlock: {
|
|
101
|
+
backgroundColor: theme.foreground,
|
|
102
|
+
borderRadius: "10px",
|
|
103
|
+
color: theme.surface,
|
|
104
|
+
fontFamily: brand.defaultBrandContext.typography.mono,
|
|
105
|
+
fontSize: "13px",
|
|
106
|
+
lineHeight: "1.55",
|
|
107
|
+
margin: "20px 0",
|
|
108
|
+
padding: "16px",
|
|
109
|
+
},
|
|
110
|
+
hr: { borderColor: theme.border, margin: "28px 0" },
|
|
111
|
+
image: { borderRadius: "10px", height: "auto", maxWidth: "100%" },
|
|
112
|
+
li: { margin: "6px 0" },
|
|
113
|
+
table: { borderCollapse: "collapse", margin: "20px 0", width: "100%" },
|
|
114
|
+
th: {
|
|
115
|
+
backgroundColor: theme.background,
|
|
116
|
+
border: `1px solid ${theme.border}`,
|
|
117
|
+
padding: "9px 12px",
|
|
118
|
+
textAlign: "left",
|
|
119
|
+
},
|
|
120
|
+
td: {
|
|
121
|
+
border: `1px solid ${theme.border}`,
|
|
122
|
+
padding: "9px 12px",
|
|
123
|
+
textAlign: "left",
|
|
124
|
+
},
|
|
125
|
+
}, children: normalizeEmailMarkdown(body) }));
|
|
126
|
+
};
|
|
127
|
+
function BrandMark({ theme }) {
|
|
128
|
+
if (theme.logoUrl) {
|
|
129
|
+
return (_jsx(Img, { src: theme.logoUrl, alt: theme.name, height: "34", style: { display: "block", height: "34px", width: "auto" } }));
|
|
130
|
+
}
|
|
131
|
+
return (_jsx(Text, { style: {
|
|
132
|
+
color: theme.onAccent,
|
|
133
|
+
fontSize: "20px",
|
|
134
|
+
fontWeight: 800,
|
|
135
|
+
letterSpacing: "-0.3px",
|
|
136
|
+
margin: 0,
|
|
137
|
+
}, children: theme.name }));
|
|
138
|
+
}
|
|
139
|
+
function EnvelopeHeaders({ headers, theme, }) {
|
|
140
|
+
if (!headers?.length)
|
|
141
|
+
return null;
|
|
142
|
+
return (_jsx(Section, { style: {
|
|
143
|
+
backgroundColor: theme.background,
|
|
144
|
+
borderRadius: "10px",
|
|
145
|
+
margin: "0 0 24px",
|
|
146
|
+
padding: "14px 16px",
|
|
147
|
+
}, children: headers.map(([label, value]) => (_jsxs(Row, { children: [_jsx(Column, { style: {
|
|
148
|
+
color: theme.muted,
|
|
149
|
+
fontSize: "12px",
|
|
150
|
+
fontWeight: 700,
|
|
151
|
+
padding: "3px 12px 3px 0",
|
|
152
|
+
width: "70px",
|
|
153
|
+
}, children: label }), _jsx(Column, { style: { color: theme.foreground, fontSize: "13px", padding: "3px 0" }, children: value })] }, `${label}:${value}`))) }));
|
|
154
|
+
}
|
|
155
|
+
/** Branded message card shared by the full document and browser previews. */
|
|
156
|
+
export const EmailCard = ({ body, subject = "Message", headers, footer, brand: brandInput, }) => {
|
|
157
|
+
const theme = resolveEmailBrand(brandInput);
|
|
158
|
+
return (_jsxs(Container, { style: {
|
|
159
|
+
backgroundColor: theme.surface,
|
|
160
|
+
border: `1px solid ${theme.border}`,
|
|
161
|
+
borderRadius: "16px",
|
|
162
|
+
boxShadow: "0 8px 30px rgba(27,49,57,0.08)",
|
|
163
|
+
fontFamily: theme.fontFamily,
|
|
164
|
+
margin: "0 auto",
|
|
165
|
+
maxWidth: "620px",
|
|
166
|
+
overflow: "hidden",
|
|
167
|
+
}, children: [_jsx(Section, { style: { backgroundColor: theme.accent, padding: "22px 30px" }, children: _jsx(BrandMark, { theme: theme }) }), _jsxs(Section, { style: { padding: "34px 34px 24px" }, children: [_jsx(Heading, { style: {
|
|
168
|
+
color: theme.foreground,
|
|
169
|
+
fontSize: "28px",
|
|
170
|
+
letterSpacing: "-0.5px",
|
|
171
|
+
lineHeight: "1.2",
|
|
172
|
+
margin: "0 0 24px",
|
|
173
|
+
}, children: subject }), _jsx(EnvelopeHeaders, { headers: headers, theme: theme }), _jsx(EmailBody, { body: body, brand: theme })] }), _jsx(Hr, { style: { borderColor: theme.border, margin: 0 } }), _jsx(Section, { style: { padding: "20px 34px 26px" }, children: _jsxs(Text, { style: {
|
|
174
|
+
color: theme.muted,
|
|
175
|
+
fontSize: "12px",
|
|
176
|
+
lineHeight: "1.55",
|
|
177
|
+
margin: 0,
|
|
178
|
+
}, children: [footer ?? theme.tagline, " ", theme.website ? (_jsx(Link, { href: theme.website, style: {
|
|
179
|
+
color: theme.accent,
|
|
180
|
+
textDecoration: "none",
|
|
181
|
+
}, children: theme.name })) : null] }) })] }));
|
|
182
|
+
};
|
|
183
|
+
/** Complete responsive, branded React Email document. */
|
|
184
|
+
export const EmailDocument = (props) => {
|
|
185
|
+
const theme = resolveEmailBrand(props.brand);
|
|
186
|
+
const pageStyle = {
|
|
187
|
+
backgroundColor: theme.background,
|
|
188
|
+
fontFamily: theme.fontFamily,
|
|
189
|
+
margin: 0,
|
|
190
|
+
padding: "28px 12px",
|
|
191
|
+
};
|
|
192
|
+
return (_jsxs(Html, { lang: "en", children: [_jsx(Head, {}), _jsx(Preview, { children: props.subject ?? "Message" }), _jsx(Body, { style: pageStyle, children: _jsx(EmailCard, { ...props }) })] }));
|
|
193
|
+
};
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVtcGxhdGUudHN4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7R0FRRztBQUNILE9BQU8sRUFBRSxLQUFLLEVBQXFCLE1BQU0sd0JBQXdCLENBQUM7QUFDbEUsT0FBTyxFQUNMLElBQUksRUFDSixNQUFNLEVBQ04sU0FBUyxFQUNULElBQUksRUFDSixPQUFPLEVBQ1AsRUFBRSxFQUNGLElBQUksRUFDSixHQUFHLEVBQ0gsSUFBSSxFQUNKLFFBQVEsRUFDUixPQUFPLEVBQ1AsR0FBRyxFQUNILE9BQU8sRUFDUCxJQUFJLEdBQ0wsTUFBTSx5QkFBeUIsQ0FBQztBQXFDakMsU0FBUyxvQkFBb0IsQ0FBQyxLQUF5QjtJQUNyRCxPQUFPLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSwwQkFBMEIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDN0UsQ0FBQztBQUVELCtEQUErRDtBQUMvRCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLE9BQXFCLEVBQWMsRUFBRTtJQUN6RSxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDO0lBQ25FLE9BQU87UUFDTCxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxPQUFPO1FBQzlCLFFBQVEsRUFBRSxTQUFTO1FBQ25CLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVSxDQUFDLElBQUk7UUFDbkMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJO1FBQ2xCLFVBQVUsRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU87UUFDbEMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsVUFBVTtRQUNsQyxVQUFVLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxVQUFVO1FBQ3JDLEtB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akMsVUFBVSxFQUFFLEtBQUssQ0FBQyxVQUFVO1FBQzVCLE1BQU0sRUFBRSxDQUFDO1FBQ1QsT0FBTyxFQUFFLFdBQVc7S0FDckIsQ0FBQztJQUNGLE9BQU8sQ0FDTCxNQUFDLElBQUksSUFBQyxJQUFJLEVBQUMsSUFBSSxhQUNiLEtBQUMsSUFBSSxLQUFHLEVBQ1IsS0FBQyxPQUFPLGNBQUUsS0FBSyxDQUFDLE9BQU8sSUFBSSxTQUFTLEdBQVcsRUFDL0MsS0FBQyxJQUFJLElBQUMsS0FBSyxFQUFFLFNBQVMsWUFDcEIsS0FBQyxTQUFTLE9BQUssS0FBSyxHQUFJLEdBQ25CLElBQ0YsQ0FDUixDQUFDO0FBQ0osQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBVbml2ZXJzYWwgUmVhY3QgRW1haWwgcHJlc2VudGF0aW9uIGZvciBkYngtdG9vbHMgZW1haWwgZmxvd3MuXG4gKlxuICogVGhlIGRvY3VtZW50IGNvbXBvbmVudCByZW5kZXJzIG9uIHRoZSBzZXJ2ZXIgdGhyb3VnaCBgQHJlYWN0LWVtYWlsL3JlbmRlcmA7XG4gKiB0aGUgYm9keSBjb21wb25lbnQgaXMgcmV1c2VkIGRpcmVjdGx5IGJ5IGJyb3dzZXIgYXBwcm92YWwgYW5kIGNvbXBvc2Ugdmlld3MuXG4gKiBCb3RoIGRlZmF1bHQgdG8gdGhlIHJlcG9zaXRvcnkgYnJhbmQgd2hpbGUgYWNjZXB0aW5nIGEgY29uc3VtZXIgb3ZlcnJpZGUuXG4gKlxuICogQG1vZHVsZVxuICovXG5pbXBvcnQgeyBicmFuZCwgdHlwZSBCcmFuZENvbnRleHQgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHtcbiAgQm9keSxcbiAgQ29sdW1uLFxuICBDb250YWluZXIsXG4gIEhlYWQsXG4gIEhlYWRpbmcsXG4gIEhyLFxuICBIdG1sLFxuICBJbWcsXG4gIExpbmssXG4gIE1hcmtkb3duLFxuICBQcmV2aWV3LFxuICBSb3csXG4gIFNlY3Rpb24sXG4gIFRleHQsXG59IGZyb20gXCJAcmVhY3QtZW1haWwvY29tcG9uZW50c1wiO1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSBcInJlYWN0XCI7XG5cbi8qKiBFbWFpbC1zYWZlIGJyYW5kIHZhbHVlcyB1c2VkIGJ5IHRoZSBSZWFjdCBFbWFpbCB0ZW1wbGF0ZS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRW1haWxCcmFuZCB7XG4gIGFjY2VudDogc3RyaW5nO1xuICBvbkFjY2VudD86IHN0cmluZztcbiAgZm9udEZhbWlseTogc3RyaW5nO1xuICBuYW1lPzogc3RyaW5nO1xuICBsb2dvVXJsPzogc3RyaW5nO1xuICBiYWNrZ3JvdW5kPzogc3RyaW5nO1xuICBzdXJmYWNlPzogc3RyaW5nO1xuICBmb3JlZ3JvdW5kPzogc3RyaW5nO1xuICBtdXRlZD86IHN0cmluZztcbiAgYm9yZGVyPzogc3RyaW5nO1xuICB0YWdsaW5lPzogc3RyaW5nO1xuICB3ZWJzaXRlPzogc3RyaW5nO1xufVxuXG4vKiogQ29tcGxldGUgdmFsdWVzIGFmdGVyIGFwcGx5aW5nIHRoZSBkYngtdG9vbHMgZGVmYXVsdCBicmFuZC4gKi9cbmV4cG9ydCB0eXBlIFJlc29sdmVkRW1haWxCcmFuZCA9IFJlcXVpcmVkPFxuICBQaWNrPFxuICAgIEVtYWlsQnJhbmQsXG4gICAgfCBcImFjY2VudFwiXG4gICAgfCBcIm9uQWNjZW50XCJcbiAgICB8IFwiZm9udEZhbWlseVwiXG4gICAgfCBcIm5hbWVcIlxuICAgIHwgXCJiYWNrZ3JvdW5kXCJcbiAgICB8IFwic3VyZmFjZVwiXG4gICAgfCBcImZvcmVncm91bmRcIlxuICAgIHwgXCJtdXRlZFwiXG4gICAgfCBcImJvcmRlclwiXG4gICAgfCBcInRhZ2xpbmVcIlxuICA+XG4+ICZcbiAgUGljazxFbWFpbEJyYW5kLCBcImxvZ29VcmxcIiB8IFwid2Vic2l0ZVwiPjtcblxuZnVuY3Rpb24gaXNSZW5kZXJhYmxlSW1hZ2VVcmwodmFsdWU6IHN0cmluZyB8IHVuZGVmaW5lZCk6IHZhbHVlIGlzIHN0cmluZyB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwic3RyaW5nXCIgJiYgL14oPzpodHRwcz86fGRhdGE6fGNpZDopL2kudGVzdCh2YWx1ZSk7XG59XG5cbi8qKiBQcm9qZWN0IGEgcG9ydGFibGUgYnJhbmQgY29udGV4dCBpbnRvIGVtYWlsLXNhZmUgdmFsdWVzLiAqL1xuZXhwb3J0IGNvbnN0IGVtYWlsQnJhbmRGcm9tQ29udGV4dCA9IChjb250ZXh0OiBCcmFuZENvbnRleHQpOiBFbWFpbEJyYW5kID0+IHtcbiAgY29uc3QgbG9nbyA9IGNvbnRleHQuYXNzZXRzLmxvZ28uZGFyayA/PyBjb250ZXh0LmFzc2V0cy5sb2dvLmxpZ2h0O1xuICByZXR1cm4ge1xuICAgIGFjY2VudDogY29udGV4dC5jb2xvcnMucHJpbWFyeSxcbiAgICBvbkFjY2VudDogXCIjZmZmZmZmXCIsXG4gICAgZm9udEZhbWlseTogY29udGV4dC50eXBvZ3JhcGh5LnNhbnMsXG4gICAgbmFtZTogY29udGV4dC5uYW1lLFxuICAgIGJhY2tncm91bmQ6IGNvbnRleHQuY29sb3JzLnN1cmZhY2UsXG4gICAgc3VyZmFjZTogY29udGV4dC5jb2xvcnMuYmFja2dyb3VuZCxcbiAgICBmb3JlZ3JvdW5kOiBjb250ZXh0LmNvbG9ycy5mb3JlZ3JvdW5kLFxuICAgIG11dGVkOiBjb250ZXh0LmNvbG9ycy5tdXRlZCxcbiAgICBib3JkZXI6IGNvbnRleHQuY29sb3JzLmJvcmRlcixcbiAgICB0YWdsaW5lOiBjb250ZXh0LnRhZ2xpbmUsXG4gICAgd2Vic2l0ZTogY29udGV4dC5saW5rcy53ZWJzaXRlLFxuICAgIC4uLihpc1JlbmRlcmFibGVJbWFnZVVybChsb2dvKSA/IHsgbG9nb1VybDogbG9nbyB9IDoge30pLFxuICB9O1xufTtcblxuLyoqIFRoZSByZXBvc2l0b3J5IGJyYW5kIHVzZWQgYnkgZXZlcnkgZW1haWwgdW5sZXNzIGV4cGxpY2l0bHkgb3ZlcnJpZGRlbi4gKi9cbmV4cG9ydCBjb25zdCBkZWZhdWx0RW1haWxCcmFuZDogRW1haWxCcmFuZCA9IGVtYWlsQnJhbmRGcm9tQ29udGV4dChicmFuZC5kZWZhdWx0QnJhbmRDb250ZXh0KTtcblxuLyoqIE1lcmdlIGEgcGFydGlhbCBjb25zdW1lciBicmFuZCBvdmVyIHRoZSByZXBvc2l0b3J5IGRlZmF1bHQuICovXG5leHBvcnQgY29uc3QgcmVzb2x2ZUVtYWlsQnJhbmQgPSAoaW5wdXQ/OiBFbWFpbEJyYW5kKTogUmVzb2x2ZWRFbWFpbEJyYW5kID0+IHtcbiAgY29uc3QgbWVyZ2VkID0geyAuLi5kZWZhdWx0RW1haWxCcmFuZCwgLi4uaW5wdXQgfTtcbiAgcmV0dXJuIHtcbiAgICBhY2NlbnQ6IG1lcmdlZC5hY2NlbnQsXG4gICAgb25BY2NlbnQ6IG1lcmdlZC5vbkFjY2VudCA/PyBcIiNmZmZmZmZcIixcbiAgICBmb250RmFtaWx5OiBtZXJnZWQuZm9udEZhbWlseSxcbiAgICBuYW1lOiBtZXJnZWQubmFtZSA/PyBicmFuZC5kZWZhdWx0QnJhbmRDb250ZXh0Lm5hbWUsXG4gICAgYmFja2dyb3VuZDogbWVyZ2VkLmJhY2tncm91bmQgPz8gYnJhbmQuZGVmYXVsdEJyYW5kQ29udGV4dC5jb2xvcnMuc3VyZmFjZSxcbiAgICBzdXJmYWNlOiBtZXJnZWQuc3VyZmFjZSA/PyBicmFuZC5kZWZhdWx0QnJhbmRDb250ZXh0LmNvbG9ycy5iYWNrZ3JvdW5kLFxuICAgIGZvcmVncm91bmQ6IG1lcmdlZC5mb3JlZ3JvdW5kID8/IGJyYW5kLmRlZmF1bHRCcmFuZENvbnRleHQuY29sb3JzLmZvcmVncm91bmQsXG4gICAgbXV0ZWQ6IG1lcmdlZC5tdXRlZCA/PyBicmFuZC5kZWZhdWx0QnJhbmRDb250ZXh0LmNvbG9ycy5tdXRlZCxcbiAgICBib3JkZXI6IG1lcmdlZC5ib3JkZXIgPz8gYnJhbmQuZGVmYXVsdEJyYW5kQ29udGV4dC5jb2xvcnMuYm9yZGVyLFxuICAgIHRhZ2xpbmU6IG1lcmdlZC50YWdsaW5lID8/IGJyYW5kLmRlZmF1bHRCcmFuZENvbnRleHQudGFnbGluZSxcbiAgICAuLi4obWVyZ2VkLmxvZ29VcmwgPyB7IGxvZ29Vcmw6IG1lcmdlZC5sb2dvVXJsIH0gOiB7fSksXG4gICAgLi4uKG1lcmdlZC53ZWJzaXRlID8geyB3ZWJzaXRlOiBtZXJnZWQud2Vic2l0ZSB9IDoge30pLFxuICB9O1xufTtcblxuLyoqIE5vcm1hbGl6ZSBpbmRlbnRhdGlvbiBpbiBhdXRob3JlZCBjb250ZW50IHdpdGhvdXQgY29uc3RyYWluaW5nIGl0cyBzdHJ1Y3R1cmUuICovXG5leHBvcnQgY29uc3Qgbm9ybWFsaXplRW1haWxNYXJrZG93biA9ICh2YWx1ZTogc3RyaW5nKTogc3RyaW5nID0+IHtcbiAgY29uc3QgbGluZXMgPSB2YWx1ZS5yZXBsYWNlKC9cXHJcXG4/L2csIFwiXFxuXCIpLnNwbGl0KFwiXFxuXCIpO1xuICB3aGlsZSAobGluZXNbMF0/LnRyaW0oKSA9PT0gXCJcIikgbGluZXMuc2hpZnQoKTtcbiAgd2hpbGUgKGxpbmVzLmF0KC0xKT8udHJpbSgpID09PSBcIlwiKSBsaW5lcy5wb3AoKTtcbiAgY29uc3Qgbm9uQmxhbmsgPSBsaW5lcy5maWx0ZXIoKGxpbmUpID0+IGxpbmUudHJpbSgpLmxlbmd0aCA+IDApO1xuICBjb25zdCBpbmRlbnQgPSBub25CbGFuay5yZWR1Y2UoKG1pbmltdW0sIGxpbmUpID0+IHtcbiAgICBjb25zdCB3aWR0aCA9IGxpbmUubWF0Y2goL15cXHMqLyk/LlswXS5sZW5ndGggPz8gMDtcbiAgICByZXR1cm4gTWF0aC5taW4obWluaW11bSwgd2lkdGgpO1xuICB9LCBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFkpO1xuICBpZiAoIU51bWJlci5pc0Zpbml0ZShpbmRlbnQpIHx8IGluZGVudCA9PT0gMCkgcmV0dXJuIGxpbmVzLmpvaW4oXCJcXG5cIik7XG4gIHJldHVybiBsaW5lcy5tYXAoKGxpbmUpID0+IGxpbmUuc2xpY2UoTWF0aC5taW4oaW5kZW50LCBsaW5lLmxlbmd0aCkpKS5qb2luKFwiXFxuXCIpO1xufTtcblxuLyoqIFByb3BzIGZvciB0aGUgcmV1c2FibGUgbWVzc2FnZS1ib2R5IGNvbXBvbmVudC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRW1haWxCb2R5UHJvcHMge1xuICBib2R5OiBzdHJpbmc7XG4gIGJyYW5kPzogRW1haWxCcmFuZDtcbn1cblxuLyoqIFJlbmRlciByaWNoIG1lc3NhZ2UgY29udGVudCB3aXRoIGVtYWlsLWNsaWVudC1zYWZlIGlubGluZSBzdHlsZXMuICovXG5leHBvcnQgY29uc3QgRW1haWxCb2R5ID0gKHsgYm9keSwgYnJhbmQ6IGJyYW5kSW5wdXQgfTogRW1haWxCb2R5UHJvcHMpID0+IHtcbiAgY29uc3QgdGhlbWUgPSByZXNvbHZlRW1haWxCcmFuZChicmFuZElucHV0KTtcbiAgcmV0dXJuIChcbiAgICA8TWFya2Rvd25cbiAgICAgIG1hcmtkb3duQ29udGFpbmVyU3R5bGVzPXt7XG4gICAgICAgIGNvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLFxuICAgICAgICBmb250RmFtaWx5OiB0aGVtZS5mb250RmFtaWx5LFxuICAgICAgICBmb250U2l6ZTogXCIxNXB4XCIsXG4gICAgICAgIGxpbmVIZWlnaHQ6IFwiMS42NVwiLFxuICAgICAgfX1cbiAgICAgIG1hcmtkb3duQ3VzdG9tU3R5bGVzPXt7XG4gICAgICAgIGgxOiB7IGNvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLCBmb250U2l6ZTogXCIzMHB4XCIsIGxpbmVIZWlnaHQ6IFwiMS4yXCIsIG1hcmdpbjogXCIwIDAgMjBweFwiIH0sXG4gICAgICAgIGgyOiB7IGNvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLCBmb250U2l6ZTogXCIyM3B4XCIsIGxpbmVIZWlnaHQ6IFwiMS4zXCIsIG1hcmdpbjogXCIyOHB4IDAgMTJweFwiIH0sXG4gICAgICAgIGgzOiB7IGNvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLCBmb250U2l6ZTogXCIxOHB4XCIsIGxpbmVIZWlnaHQ6IFwiMS40XCIsIG1hcmdpbjogXCIyNHB4IDAgMTBweFwiIH0sXG4gICAgICAgIHA6IHsgbWFyZ2luOiBcIjAgMCAxNnB4XCIgfSxcbiAgICAgICAgbGluazogeyBjb2xvcjogdGhlbWUuYWNjZW50LCBmb250V2VpZ2h0OiA2MDAsIHRleHREZWNvcmF0aW9uOiBcInVuZGVybGluZVwiIH0sXG4gICAgICAgIGJsb2NrUXVvdGU6IHtcbiAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IHRoZW1lLmJhY2tncm91bmQsXG4gICAgICAgICAgYm9yZGVyTGVmdDogYDRweCBzb2xpZCAke3RoZW1lLmFjY2VudH1gLFxuICAgICAgICAgIGJvcmRlclJhZGl1czogXCIwIDEwcHggMTBweCAwXCIsXG4gICAgICAgICAgY29sb3I6IHRoZW1lLmZvcmVncm91bmQsXG4gICAgICAgICAgbWFyZ2luOiBcIjIwcHggMFwiLFxuICAgICAgICAgIHBhZGRpbmc6IFwiMTRweCAxOHB4XCIsXG4gICAgICAgIH0sXG4gICAgICAgIGNvZGVJbmxpbmU6IHtcbiAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IHRoZW1lLmJhY2tncm91bmQsXG4gICAgICAgICAgYm9yZGVyUmFkaXVzOiBcIjVweFwiLFxuICAgICAgICAgIGNvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLFxuICAgICAgICAgIGZvbnRGYW1pbHk6IGJyYW5kLmRlZmF1bHRCcmFuZENvbnRleHQudHlwb2dyYXBoeS5tb25vLFxuICAgICAgICAgIGZvbnRTaXplOiBcIjEzcHhcIixcbiAgICAgICAgICBwYWRkaW5nOiBcIjJweCA1cHhcIixcbiAgICAgICAgfSxcbiAgICAgICAgY29kZUJsb2NrOiB7XG4gICAgICAgICAgYmFja2dyb3VuZENvbG9yOiB0aGVtZS5mb3JlZ3JvdW5kLFxuICAgICAgICAgIGJvcmRlclJhZGl1czogXCIxMHB4XCIsXG4gICAgICAgICAgY29sb3I6IHRoZW1lLnN1cmZhY2UsXG4gICAgICAgICAgZm9udEZhbWlseTogYnJhbmQuZGVmYXVsdEJyYW5kQ29udGV4dC50eXBvZ3JhcGh5Lm1vbm8sXG4gICAgICAgICAgZm9udFNpemU6IFwiMTNweFwiLFxuICAgICAgICAgIGxpbmVIZWlnaHQ6IFwiMS41NVwiLFxuICAgICAgICAgIG1hcmdpbjogXCIyMHB4IDBcIixcbiAgICAgICAgICBwYWRkaW5nOiBcIjE2cHhcIixcbiAgICAgICAgfSxcbiAgICAgICAgaHI6IHsgYm9yZGVyQ29sb3I6IHRoZW1lLmJvcmRlciwgbWFyZ2luOiBcIjI4cHggMFwiIH0sXG4gICAgICAgIGltYWdlOiB7IGJvcmRlclJhZGl1czogXCIxMHB4XCIsIGhlaWdodDogXCJhdXRvXCIsIG1heFdpZHRoOiBcIjEwMCVcIiB9LFxuICAgICAgICBsaTogeyBtYXJnaW46IFwiNnB4IDBcIiB9LFxuICAgICAgICB0YWJsZTogeyBib3JkZXJDb2xsYXBzZTogXCJjb2xsYXBzZVwiLCBtYXJnaW46IFwiMjBweCAwXCIsIHdpZHRoOiBcIjEwMCVcIiB9LFxuICAgICAgICB0aDoge1xuICAgICAgICAgIGJhY2tncm91bmRDb2xvcjogdGhlbWUuYmFja2dyb3VuZCxcbiAgICAgICAgICBib3JkZXI6IGAxcHggc29saWQgJHt0aGVtZS5ib3JkZXJ9YCxcbiAgICAgICAgICBwYWRkaW5nOiBcIjlweCAxMnB4XCIsXG4gICAgICAgICAgdGV4dEFsaWduOiBcImxlZnRcIixcbiAgICAgICAgfSxcbiAgICAgICAgdGQ6IHtcbiAgICAgICAgICBib3JkZXI6IGAxcHggc29saWQgJHt0aGVtZS5ib3JkZXJ9YCxcbiAgICAgICAgICBwYWRkaW5nOiBcIjlweCAxMnB4XCIsXG4gICAgICAgICAgdGV4dEFsaWduOiBcImxlZnRcIixcbiAgICAgICAgfSxcbiAgICAgIH19XG4gICAgPlxuICAgICAge25vcm1hbGl6ZUVtYWlsTWFya2Rvd24oYm9keSl9XG4gICAgPC9NYXJrZG93bj5cbiAgKTtcbn07XG5cbi8qKiBQcm9wcyBmb3IgdGhlIGNvbXBsZXRlIFJlYWN0IEVtYWlsIGRvY3VtZW50LiAqL1xuZXhwb3J0IGludGVyZmFjZSBFbWFpbERvY3VtZW50UHJvcHMgZXh0ZW5kcyBFbWFpbEJvZHlQcm9wcyB7XG4gIHN1YmplY3Q/OiBzdHJpbmc7XG4gIGhlYWRlcnM/OiBSZWFkb25seUFycmF5PHJlYWRvbmx5IFtzdHJpbmcsIHN0cmluZ10+O1xuICBmb290ZXI/OiBzdHJpbmc7XG59XG5cbmZ1bmN0aW9uIEJyYW5kTWFyayh7IHRoZW1lIH06IHsgdGhlbWU6IFJlc29sdmVkRW1haWxCcmFuZCB9KTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgaWYgKHRoZW1lLmxvZ29VcmwpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEltZ1xuICAgICAgICBzcmM9e3RoZW1lLmxvZ29Vcmx9XG4gICAgICAgIGFsdD17dGhlbWUubmFtZX1cbiAgICAgICAgaGVpZ2h0PVwiMzRcIlxuICAgICAgICBzdHlsZT17eyBkaXNwbGF5OiBcImJsb2NrXCIsIGhlaWdodDogXCIzNHB4XCIsIHdpZHRoOiBcImF1dG9cIiB9fVxuICAgICAgLz5cbiAgICApO1xuICB9XG4gIHJldHVybiAoXG4gICAgPFRleHRcbiAgICAgIHN0eWxlPXt7XG4gICAgICAgIGNvbG9yOiB0aGVtZS5vbkFjY2VudCxcbiAgICAgICAgZm9udFNpemU6IFwiMjBweFwiLFxuICAgICAgICBmb250V2VpZ2h0OiA4MDAsXG4gICAgICAgIGxldHRlclNwYWNpbmc6IFwiLTAuM3B4XCIsXG4gICAgICAgIG1hcmdpbjogMCxcbiAgICAgIH19XG4gICAgPlxuICAgICAge3RoZW1lLm5hbWV9XG4gICAgPC9UZXh0PlxuICApO1xufVxuXG5mdW5jdGlvbiBFbnZlbG9wZUhlYWRlcnMoe1xuICBoZWFkZXJzLFxuICB0aGVtZSxcbn06IHtcbiAgaGVhZGVyczogRW1haWxEb2N1bWVudFByb3BzW1wiaGVhZGVyc1wiXTtcbiAgdGhlbWU6IFJlc29sdmVkRW1haWxCcmFuZDtcbn0pIHtcbiAgaWYgKCFoZWFkZXJzPy5sZW5ndGgpIHJldHVybiBudWxsO1xuICByZXR1cm4gKFxuICAgIDxTZWN0aW9uXG4gICAgICBzdHlsZT17e1xuICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IHRoZW1lLmJhY2tncm91bmQsXG4gICAgICAgIGJvcmRlclJhZGl1czogXCIxMHB4XCIsXG4gICAgICAgIG1hcmdpbjogXCIwIDAgMjRweFwiLFxuICAgICAgICBwYWRkaW5nOiBcIjE0cHggMTZweFwiLFxuICAgICAgfX1cbiAgICA+XG4gICAgICB7aGVhZGVycy5tYXAoKFtsYWJlbCwgdmFsdWVdKSA9PiAoXG4gICAgICAgIDxSb3cga2V5PXtgJHtsYWJlbH06JHt2YWx1ZX1gfT5cbiAgICAgICAgICA8Q29sdW1uXG4gICAgICAgICAgICBzdHlsZT17e1xuICAgICAgICAgICAgICBjb2xvcjogdGhlbWUubXV0ZWQsXG4gICAgICAgICAgICAgIGZvbnRTaXplOiBcIjEycHhcIixcbiAgICAgICAgICAgICAgZm9udFdlaWdodDogNzAwLFxuICAgICAgICAgICAgICBwYWRkaW5nOiBcIjNweCAxMnB4IDNweCAwXCIsXG4gICAgICAgICAgICAgIHdpZHRoOiBcIjcwcHhcIixcbiAgICAgICAgICAgIH19XG4gICAgICAgICAgPlxuICAgICAgICAgICAge2xhYmVsfVxuICAgICAgICAgIDwvQ29sdW1uPlxuICAgICAgICAgIDxDb2x1bW4gc3R5bGU9e3sgY29sb3I6IHRoZW1lLmZvcmVncm91bmQsIGZvbnRTaXplOiBcIjEzcHhcIiwgcGFkZGluZzogXCIzcHggMFwiIH19PlxuICAgICAgICAgICAge3ZhbHVlfVxuICAgICAgICAgIDwvQ29sdW1uPlxuICAgICAgICA8L1Jvdz5cbiAgICAgICkpfVxuICAgIDwvU2VjdGlvbj5cbiAgKTtcbn1cblxuLyoqIEJyYW5kZWQgbWVzc2FnZSBjYXJkIHNoYXJlZCBieSB0aGUgZnVsbCBkb2N1bWVudCBhbmQgYnJvd3NlciBwcmV2aWV3cy4gKi9cbmV4cG9ydCBjb25zdCBFbWFpbENhcmQgPSAoe1xuICBib2R5LFxuICBzdWJqZWN0ID0gXCJNZXNzYWdlXCIsXG4gIGhlYWRlcnMsXG4gIGZvb3RlcixcbiAgYnJhbmQ6IGJyYW5kSW5wdXQsXG59OiBFbWFpbERvY3VtZW50UHJvcHMpID0+IHtcbiAgY29uc3QgdGhlbWUgPSByZXNvbHZlRW1haWxCcmFuZChicmFuZElucHV0KTtcbiAgcmV0dXJuIChcbiAgICA8Q29udGFpbmVyXG4gICAgICBzdHlsZT17e1xuICAgICAgICBiYWNrZ3JvdW5kQ29sb3I6IHRoZW1lLnN1cmZhY2UsXG4gICAgICAgIGJvcmRlcjogYDFweCBzb2xpZCAke3RoZW1lLmJvcmRlcn1gLFxuICAgICAgICBib3JkZXJSYWRpdXM6IFwiMTZweFwiLFxuICAgICAgICBib3hTaGFkb3c6IFwiMCA4cHggMzBweCByZ2JhKDI3LDQ5LDU3LDAuMDgpXCIsXG4gICAgICAgIGZvbnRGYW1pbHk6IHRoZW1lLmZvbnRGYW1pbHksXG4gICAgICAgIG1hcmdpbjogXCIwIGF1dG9cIixcbiAgICAgICAgbWF4V2lkdGg6IFwiNjIwcHhcIixcbiAgICAgICAgb3ZlcmZsb3c6IFwiaGlkZGVuXCIsXG4gICAgICB9fVxuICAgID5cbiAgICAgIDxTZWN0aW9uIHN0eWxlPXt7IGJhY2tncm91bmRDb2xvcjogdGhlbWUuYWNjZW50LCBwYWRkaW5nOiBcIjIycHggMzBweFwiIH19PlxuICAgICAgICA8QnJhbmRNYXJrIHRoZW1lPXt0aGVtZX0gLz5cbiAgICAgIDwvU2VjdGlvbj5cbiAgICAgIDxTZWN0aW9uIHN0eWxlPXt7IHBhZGRpbmc6IFwiMzRweCAzNHB4IDI0cHhcIiB9fT5cbiAgICAgICAgPEhlYWRpbmdcbiAgICAgICAgICBzdHlsZT17e1xuICAgICAgICAgICAgY29sb3I6IHRoZW1lLmZvcmVncm91bmQsXG4gICAgICAgICAgICBmb250U2l6ZTogXCIyOHB4XCIsXG4gICAgICAgICAgICBsZXR0ZXJTcGFjaW5nOiBcIi0wLjVweFwiLFxuICAgICAgICAgICAgbGluZUhlaWdodDogXCIxLjJcIixcbiAgICAgICAgICAgIG1hcmdpbjogXCIwIDAgMjRweFwiLFxuICAgICAgICAgIH19XG4gICAgICAgID5cbiAgICAgICAgICB7c3ViamVjdH1cbiAgICAgICAgPC9IZWFkaW5nPlxuICAgICAgICA8RW52ZWxvcGVIZWFkZXJzIGhlYWRlcnM9e2hlYWRlcnN9IHRoZW1lPXt0aGVtZX0gLz5cbiAgICAgICAgPEVtYWlsQm9keSBib2R5PXtib2R5fSBicmFuZD17dGhlbWV9IC8+XG4gICAgICA8L1NlY3Rpb24+XG4gICAgICA8SHIgc3R5bGU9e3sgYm9yZGVyQ29sb3I6IHRoZW1lLmJvcmRlciwgbWFyZ2luOiAwIH19IC8+XG4gICAgICA8U2VjdGlvbiBzdHlsZT17eyBwYWRkaW5nOiBcIjIwcHggMzRweCAyNnB4XCIgfX0+XG4gICAgICAgIDxUZXh0XG4gICAgICAgICAgc3R5bGU9e3tcbiAgICAgICAgICAgIGNvbG9yOiB0aGVtZS5tdXRlZCxcbiAgICAgICAgICAgIGZvbnRTaXplOiBcIjEycHhcIixcbiAgICAgICAgICAgIGxpbmVIZWlnaHQ6IFwiMS41NVwiLFxuICAgICAgICAgICAgbWFyZ2luOiAwLFxuICAgICAgICAgIH19XG4gICAgICAgID5cbiAgICAgICAgICB7Zm9vdGVyID8/IHRoZW1lLnRhZ2xpbmV9e1wiIFwifVxuICAgICAgICAgIHt0aGVtZS53ZWJzaXRlID8gKFxuICAgICAgICAgICAgPExpbmtcbiAgICAgICAgICAgICAgaHJlZj17dGhlbWUud2Vic2l0ZX1cbiAgICAgICAgICAgICAgc3R5bGU9e3tcbiAgICAgICAgICAgICAgICBjb2xvcjogdGhlbWUuYWNjZW50LFxuICAgICAgICAgICAgICAgIHRleHREZWNvcmF0aW9uOiBcIm5vbmVcIixcbiAgICAgICAgICAgICAgfX1cbiAgICAgICAgICAgID5cbiAgICAgICAgICAgICAge3RoZW1lLm5hbWV9XG4gICAgICAgICAgICA8L0xpbms+XG4gICAgICAgICAgKSA6IG51bGx9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgIDwvU2VjdGlvbj5cbiAgICA8L0NvbnRhaW5lcj5cbiAgKTtcbn07XG5cbi8qKiBDb21wbGV0ZSByZXNwb25zaXZlLCBicmFuZGVkIFJlYWN0IEVtYWlsIGRvY3VtZW50LiAqL1xuZXhwb3J0IGNvbnN0IEVtYWlsRG9jdW1lbnQgPSAocHJvcHM6IEVtYWlsRG9jdW1lbnRQcm9wcykgPT4ge1xuICBjb25zdCB0aGVtZSA9IHJlc29sdmVFbWFpbEJyYW5kKHByb3BzLmJyYW5kKTtcbiAgY29uc3QgcGFnZVN0eWxlOiBSZWFjdC5DU1NQcm9wZXJ0aWVzID0ge1xuICAgIGJhY2tncm91bmRDb2xvcjogdGhlbWUuYmFja2dyb3VuZCxcbiAgICBmb250RmFtaWx5OiB0aGVtZS5mb250RmFtaWx5LFxuICAgIG1hcmdpbjogMCxcbiAgICBwYWRkaW5nOiBcIjI4cHggMTJweFwiLFxuICB9O1xuICByZXR1cm4gKFxuICAgIDxIdG1sIGxhbmc9XCJlblwiPlxuICAgICAgPEhlYWQgLz5cbiAgICAgIDxQcmV2aWV3Pntwcm9wcy5zdWJqZWN0ID8/IFwiTWVzc2FnZVwifTwvUHJldmlldz5cbiAgICAgIDxCb2R5IHN0eWxlPXtwYWdlU3R5bGV9PlxuICAgICAgICA8RW1haWxDYXJkIHsuLi5wcm9wc30gLz5cbiAgICAgIDwvQm9keT5cbiAgICA8L0h0bWw+XG4gICk7XG59O1xuIl19
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/template.tsx","../index.ts"],"version":"5.9.3"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dbx-tools/shared-email-template",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
6
|
+
"directory": "packages/shared/email-template"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "projen build",
|
|
10
|
+
"compile": "projen compile",
|
|
11
|
+
"default": "projen default",
|
|
12
|
+
"package": "projen package",
|
|
13
|
+
"post-compile": "projen post-compile",
|
|
14
|
+
"pre-compile": "projen pre-compile",
|
|
15
|
+
"prepack": "projen prepack",
|
|
16
|
+
"test": "projen test",
|
|
17
|
+
"watch": "projen watch",
|
|
18
|
+
"projen": "projen"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "^1.3.14",
|
|
22
|
+
"@types/node": "^24.6.0",
|
|
23
|
+
"@types/react": "^19.2.2",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@dbx-tools/shared-core": "0.6.48",
|
|
28
|
+
"@react-email/components": "^1.0.12",
|
|
29
|
+
"react": "^19.2.4"
|
|
30
|
+
},
|
|
31
|
+
"main": "./lib/index.js",
|
|
32
|
+
"license": "UNLICENSED",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"main": "./lib/index.js",
|
|
36
|
+
"types": "./lib/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./lib/index.d.ts",
|
|
40
|
+
"default": "./lib/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"version": "0.6.48",
|
|
46
|
+
"types": "./lib/index.d.ts",
|
|
47
|
+
"type": "module",
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./lib/index.d.ts",
|
|
51
|
+
"default": "./lib/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./package.json": "./package.json"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"index.ts",
|
|
57
|
+
"src",
|
|
58
|
+
"lib"
|
|
59
|
+
],
|
|
60
|
+
"dbxToolsConfig": {
|
|
61
|
+
"tags": [
|
|
62
|
+
"shared"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
66
|
+
}
|
package/src/template.tsx
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal React Email presentation for dbx-tools email flows.
|
|
3
|
+
*
|
|
4
|
+
* The document component renders on the server through `@react-email/render`;
|
|
5
|
+
* the body component is reused directly by browser approval and compose views.
|
|
6
|
+
* Both default to the repository brand while accepting a consumer override.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { brand, type BrandContext } from "@dbx-tools/shared-core";
|
|
11
|
+
import {
|
|
12
|
+
Body,
|
|
13
|
+
Column,
|
|
14
|
+
Container,
|
|
15
|
+
Head,
|
|
16
|
+
Heading,
|
|
17
|
+
Hr,
|
|
18
|
+
Html,
|
|
19
|
+
Img,
|
|
20
|
+
Link,
|
|
21
|
+
Markdown,
|
|
22
|
+
Preview,
|
|
23
|
+
Row,
|
|
24
|
+
Section,
|
|
25
|
+
Text,
|
|
26
|
+
} from "@react-email/components";
|
|
27
|
+
import * as React from "react";
|
|
28
|
+
|
|
29
|
+
/** Email-safe brand values used by the React Email template. */
|
|
30
|
+
export interface EmailBrand {
|
|
31
|
+
accent: string;
|
|
32
|
+
onAccent?: string;
|
|
33
|
+
fontFamily: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
logoUrl?: string;
|
|
36
|
+
background?: string;
|
|
37
|
+
surface?: string;
|
|
38
|
+
foreground?: string;
|
|
39
|
+
muted?: string;
|
|
40
|
+
border?: string;
|
|
41
|
+
tagline?: string;
|
|
42
|
+
website?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Complete values after applying the dbx-tools default brand. */
|
|
46
|
+
export type ResolvedEmailBrand = Required<
|
|
47
|
+
Pick<
|
|
48
|
+
EmailBrand,
|
|
49
|
+
| "accent"
|
|
50
|
+
| "onAccent"
|
|
51
|
+
| "fontFamily"
|
|
52
|
+
| "name"
|
|
53
|
+
| "background"
|
|
54
|
+
| "surface"
|
|
55
|
+
| "foreground"
|
|
56
|
+
| "muted"
|
|
57
|
+
| "border"
|
|
58
|
+
| "tagline"
|
|
59
|
+
>
|
|
60
|
+
> &
|
|
61
|
+
Pick<EmailBrand, "logoUrl" | "website">;
|
|
62
|
+
|
|
63
|
+
function isRenderableImageUrl(value: string | undefined): value is string {
|
|
64
|
+
return typeof value === "string" && /^(?:https?:|data:|cid:)/i.test(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Project a portable brand context into email-safe values. */
|
|
68
|
+
export const emailBrandFromContext = (context: BrandContext): EmailBrand => {
|
|
69
|
+
const logo = context.assets.logo.dark ?? context.assets.logo.light;
|
|
70
|
+
return {
|
|
71
|
+
accent: context.colors.primary,
|
|
72
|
+
onAccent: "#ffffff",
|
|
73
|
+
fontFamily: context.typography.sans,
|
|
74
|
+
name: context.name,
|
|
75
|
+
background: context.colors.surface,
|
|
76
|
+
surface: context.colors.background,
|
|
77
|
+
foreground: context.colors.foreground,
|
|
78
|
+
muted: context.colors.muted,
|
|
79
|
+
border: context.colors.border,
|
|
80
|
+
tagline: context.tagline,
|
|
81
|
+
website: context.links.website,
|
|
82
|
+
...(isRenderableImageUrl(logo) ? { logoUrl: logo } : {}),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** The repository brand used by every email unless explicitly overridden. */
|
|
87
|
+
export const defaultEmailBrand: EmailBrand = emailBrandFromContext(brand.defaultBrandContext);
|
|
88
|
+
|
|
89
|
+
/** Merge a partial consumer brand over the repository default. */
|
|
90
|
+
export const resolveEmailBrand = (input?: EmailBrand): ResolvedEmailBrand => {
|
|
91
|
+
const merged = { ...defaultEmailBrand, ...input };
|
|
92
|
+
return {
|
|
93
|
+
accent: merged.accent,
|
|
94
|
+
onAccent: merged.onAccent ?? "#ffffff",
|
|
95
|
+
fontFamily: merged.fontFamily,
|
|
96
|
+
name: merged.name ?? brand.defaultBrandContext.name,
|
|
97
|
+
background: merged.background ?? brand.defaultBrandContext.colors.surface,
|
|
98
|
+
surface: merged.surface ?? brand.defaultBrandContext.colors.background,
|
|
99
|
+
foreground: merged.foreground ?? brand.defaultBrandContext.colors.foreground,
|
|
100
|
+
muted: merged.muted ?? brand.defaultBrandContext.colors.muted,
|
|
101
|
+
border: merged.border ?? brand.defaultBrandContext.colors.border,
|
|
102
|
+
tagline: merged.tagline ?? brand.defaultBrandContext.tagline,
|
|
103
|
+
...(merged.logoUrl ? { logoUrl: merged.logoUrl } : {}),
|
|
104
|
+
...(merged.website ? { website: merged.website } : {}),
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Normalize indentation in authored content without constraining its structure. */
|
|
109
|
+
export const normalizeEmailMarkdown = (value: string): string => {
|
|
110
|
+
const lines = value.replace(/\r\n?/g, "\n").split("\n");
|
|
111
|
+
while (lines[0]?.trim() === "") lines.shift();
|
|
112
|
+
while (lines.at(-1)?.trim() === "") lines.pop();
|
|
113
|
+
const nonBlank = lines.filter((line) => line.trim().length > 0);
|
|
114
|
+
const indent = nonBlank.reduce((minimum, line) => {
|
|
115
|
+
const width = line.match(/^\s*/)?.[0].length ?? 0;
|
|
116
|
+
return Math.min(minimum, width);
|
|
117
|
+
}, Number.POSITIVE_INFINITY);
|
|
118
|
+
if (!Number.isFinite(indent) || indent === 0) return lines.join("\n");
|
|
119
|
+
return lines.map((line) => line.slice(Math.min(indent, line.length))).join("\n");
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Props for the reusable message-body component. */
|
|
123
|
+
export interface EmailBodyProps {
|
|
124
|
+
body: string;
|
|
125
|
+
brand?: EmailBrand;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Render rich message content with email-client-safe inline styles. */
|
|
129
|
+
export const EmailBody = ({ body, brand: brandInput }: EmailBodyProps) => {
|
|
130
|
+
const theme = resolveEmailBrand(brandInput);
|
|
131
|
+
return (
|
|
132
|
+
<Markdown
|
|
133
|
+
markdownContainerStyles={{
|
|
134
|
+
color: theme.foreground,
|
|
135
|
+
fontFamily: theme.fontFamily,
|
|
136
|
+
fontSize: "15px",
|
|
137
|
+
lineHeight: "1.65",
|
|
138
|
+
}}
|
|
139
|
+
markdownCustomStyles={{
|
|
140
|
+
h1: { color: theme.foreground, fontSize: "30px", lineHeight: "1.2", margin: "0 0 20px" },
|
|
141
|
+
h2: { color: theme.foreground, fontSize: "23px", lineHeight: "1.3", margin: "28px 0 12px" },
|
|
142
|
+
h3: { color: theme.foreground, fontSize: "18px", lineHeight: "1.4", margin: "24px 0 10px" },
|
|
143
|
+
p: { margin: "0 0 16px" },
|
|
144
|
+
link: { color: theme.accent, fontWeight: 600, textDecoration: "underline" },
|
|
145
|
+
blockQuote: {
|
|
146
|
+
backgroundColor: theme.background,
|
|
147
|
+
borderLeft: `4px solid ${theme.accent}`,
|
|
148
|
+
borderRadius: "0 10px 10px 0",
|
|
149
|
+
color: theme.foreground,
|
|
150
|
+
margin: "20px 0",
|
|
151
|
+
padding: "14px 18px",
|
|
152
|
+
},
|
|
153
|
+
codeInline: {
|
|
154
|
+
backgroundColor: theme.background,
|
|
155
|
+
borderRadius: "5px",
|
|
156
|
+
color: theme.foreground,
|
|
157
|
+
fontFamily: brand.defaultBrandContext.typography.mono,
|
|
158
|
+
fontSize: "13px",
|
|
159
|
+
padding: "2px 5px",
|
|
160
|
+
},
|
|
161
|
+
codeBlock: {
|
|
162
|
+
backgroundColor: theme.foreground,
|
|
163
|
+
borderRadius: "10px",
|
|
164
|
+
color: theme.surface,
|
|
165
|
+
fontFamily: brand.defaultBrandContext.typography.mono,
|
|
166
|
+
fontSize: "13px",
|
|
167
|
+
lineHeight: "1.55",
|
|
168
|
+
margin: "20px 0",
|
|
169
|
+
padding: "16px",
|
|
170
|
+
},
|
|
171
|
+
hr: { borderColor: theme.border, margin: "28px 0" },
|
|
172
|
+
image: { borderRadius: "10px", height: "auto", maxWidth: "100%" },
|
|
173
|
+
li: { margin: "6px 0" },
|
|
174
|
+
table: { borderCollapse: "collapse", margin: "20px 0", width: "100%" },
|
|
175
|
+
th: {
|
|
176
|
+
backgroundColor: theme.background,
|
|
177
|
+
border: `1px solid ${theme.border}`,
|
|
178
|
+
padding: "9px 12px",
|
|
179
|
+
textAlign: "left",
|
|
180
|
+
},
|
|
181
|
+
td: {
|
|
182
|
+
border: `1px solid ${theme.border}`,
|
|
183
|
+
padding: "9px 12px",
|
|
184
|
+
textAlign: "left",
|
|
185
|
+
},
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
{normalizeEmailMarkdown(body)}
|
|
189
|
+
</Markdown>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** Props for the complete React Email document. */
|
|
194
|
+
export interface EmailDocumentProps extends EmailBodyProps {
|
|
195
|
+
subject?: string;
|
|
196
|
+
headers?: ReadonlyArray<readonly [string, string]>;
|
|
197
|
+
footer?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function BrandMark({ theme }: { theme: ResolvedEmailBrand }): React.ReactNode {
|
|
201
|
+
if (theme.logoUrl) {
|
|
202
|
+
return (
|
|
203
|
+
<Img
|
|
204
|
+
src={theme.logoUrl}
|
|
205
|
+
alt={theme.name}
|
|
206
|
+
height="34"
|
|
207
|
+
style={{ display: "block", height: "34px", width: "auto" }}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return (
|
|
212
|
+
<Text
|
|
213
|
+
style={{
|
|
214
|
+
color: theme.onAccent,
|
|
215
|
+
fontSize: "20px",
|
|
216
|
+
fontWeight: 800,
|
|
217
|
+
letterSpacing: "-0.3px",
|
|
218
|
+
margin: 0,
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
{theme.name}
|
|
222
|
+
</Text>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function EnvelopeHeaders({
|
|
227
|
+
headers,
|
|
228
|
+
theme,
|
|
229
|
+
}: {
|
|
230
|
+
headers: EmailDocumentProps["headers"];
|
|
231
|
+
theme: ResolvedEmailBrand;
|
|
232
|
+
}) {
|
|
233
|
+
if (!headers?.length) return null;
|
|
234
|
+
return (
|
|
235
|
+
<Section
|
|
236
|
+
style={{
|
|
237
|
+
backgroundColor: theme.background,
|
|
238
|
+
borderRadius: "10px",
|
|
239
|
+
margin: "0 0 24px",
|
|
240
|
+
padding: "14px 16px",
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
{headers.map(([label, value]) => (
|
|
244
|
+
<Row key={`${label}:${value}`}>
|
|
245
|
+
<Column
|
|
246
|
+
style={{
|
|
247
|
+
color: theme.muted,
|
|
248
|
+
fontSize: "12px",
|
|
249
|
+
fontWeight: 700,
|
|
250
|
+
padding: "3px 12px 3px 0",
|
|
251
|
+
width: "70px",
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{label}
|
|
255
|
+
</Column>
|
|
256
|
+
<Column style={{ color: theme.foreground, fontSize: "13px", padding: "3px 0" }}>
|
|
257
|
+
{value}
|
|
258
|
+
</Column>
|
|
259
|
+
</Row>
|
|
260
|
+
))}
|
|
261
|
+
</Section>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Branded message card shared by the full document and browser previews. */
|
|
266
|
+
export const EmailCard = ({
|
|
267
|
+
body,
|
|
268
|
+
subject = "Message",
|
|
269
|
+
headers,
|
|
270
|
+
footer,
|
|
271
|
+
brand: brandInput,
|
|
272
|
+
}: EmailDocumentProps) => {
|
|
273
|
+
const theme = resolveEmailBrand(brandInput);
|
|
274
|
+
return (
|
|
275
|
+
<Container
|
|
276
|
+
style={{
|
|
277
|
+
backgroundColor: theme.surface,
|
|
278
|
+
border: `1px solid ${theme.border}`,
|
|
279
|
+
borderRadius: "16px",
|
|
280
|
+
boxShadow: "0 8px 30px rgba(27,49,57,0.08)",
|
|
281
|
+
fontFamily: theme.fontFamily,
|
|
282
|
+
margin: "0 auto",
|
|
283
|
+
maxWidth: "620px",
|
|
284
|
+
overflow: "hidden",
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
<Section style={{ backgroundColor: theme.accent, padding: "22px 30px" }}>
|
|
288
|
+
<BrandMark theme={theme} />
|
|
289
|
+
</Section>
|
|
290
|
+
<Section style={{ padding: "34px 34px 24px" }}>
|
|
291
|
+
<Heading
|
|
292
|
+
style={{
|
|
293
|
+
color: theme.foreground,
|
|
294
|
+
fontSize: "28px",
|
|
295
|
+
letterSpacing: "-0.5px",
|
|
296
|
+
lineHeight: "1.2",
|
|
297
|
+
margin: "0 0 24px",
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
{subject}
|
|
301
|
+
</Heading>
|
|
302
|
+
<EnvelopeHeaders headers={headers} theme={theme} />
|
|
303
|
+
<EmailBody body={body} brand={theme} />
|
|
304
|
+
</Section>
|
|
305
|
+
<Hr style={{ borderColor: theme.border, margin: 0 }} />
|
|
306
|
+
<Section style={{ padding: "20px 34px 26px" }}>
|
|
307
|
+
<Text
|
|
308
|
+
style={{
|
|
309
|
+
color: theme.muted,
|
|
310
|
+
fontSize: "12px",
|
|
311
|
+
lineHeight: "1.55",
|
|
312
|
+
margin: 0,
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
{footer ?? theme.tagline}{" "}
|
|
316
|
+
{theme.website ? (
|
|
317
|
+
<Link
|
|
318
|
+
href={theme.website}
|
|
319
|
+
style={{
|
|
320
|
+
color: theme.accent,
|
|
321
|
+
textDecoration: "none",
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
{theme.name}
|
|
325
|
+
</Link>
|
|
326
|
+
) : null}
|
|
327
|
+
</Text>
|
|
328
|
+
</Section>
|
|
329
|
+
</Container>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** Complete responsive, branded React Email document. */
|
|
334
|
+
export const EmailDocument = (props: EmailDocumentProps) => {
|
|
335
|
+
const theme = resolveEmailBrand(props.brand);
|
|
336
|
+
const pageStyle: React.CSSProperties = {
|
|
337
|
+
backgroundColor: theme.background,
|
|
338
|
+
fontFamily: theme.fontFamily,
|
|
339
|
+
margin: 0,
|
|
340
|
+
padding: "28px 12px",
|
|
341
|
+
};
|
|
342
|
+
return (
|
|
343
|
+
<Html lang="en">
|
|
344
|
+
<Head />
|
|
345
|
+
<Preview>{props.subject ?? "Message"}</Preview>
|
|
346
|
+
<Body style={pageStyle}>
|
|
347
|
+
<EmailCard {...props} />
|
|
348
|
+
</Body>
|
|
349
|
+
</Html>
|
|
350
|
+
);
|
|
351
|
+
};
|