@lacspace/email-templates 1.0.3 → 1.0.5
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 -0
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -108,4 +108,14 @@ This package is **free** under the **[Lacspace Free Licence](https://lacspace.co
|
|
|
108
108
|
|
|
109
109
|
Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
|
|
110
110
|
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
<div align="center">
|
|
114
|
+
|
|
115
|
+
**Part of the Lacspace ecosystem — 35 zero-dependency, isomorphic TypeScript packages.**
|
|
116
|
+
|
|
117
|
+
[All packages ↗](https://lacspace.com/packages) · [npm org ↗](https://www.npmjs.com/org/lacspace) · [Licence Centre ↗](https://lacspace.com/licenses) · [GitHub ↗](https://github.com/lacspace/npm-packages)
|
|
118
|
+
|
|
119
|
+
</div>
|
|
120
|
+
|
|
111
121
|
<div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> · Lacspace Free Licence · <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
|
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,13 @@ function theme(t) {
|
|
|
20
20
|
function heading(text2, opts = {}) {
|
|
21
21
|
const t = theme(opts.theme);
|
|
22
22
|
const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;
|
|
23
|
-
return `<h1 style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
|
|
23
|
+
return `<h1 class="t" style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
|
|
24
24
|
}
|
|
25
25
|
function text(content, opts = {}) {
|
|
26
26
|
const t = theme(opts.theme);
|
|
27
27
|
const color = opts.muted ? t.mutedColor : t.textColor;
|
|
28
|
-
|
|
28
|
+
const cls = opts.muted ? "" : ` class="t"`;
|
|
29
|
+
return `<p${cls} style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
|
|
29
30
|
}
|
|
30
31
|
function html(raw) {
|
|
31
32
|
return raw;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n return `<p style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\"> </div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 class=\"t\" style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n // Only primary body text carries `.t`; muted text keeps its lighter grey, which\n // already reads on a dark background (see the dark-mode @media rule in render()).\n const cls = opts.muted ? \"\" : ` class=\"t\"`;\n return `<p${cls} style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\"> </div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -18,12 +18,13 @@ function theme(t) {
|
|
|
18
18
|
function heading(text2, opts = {}) {
|
|
19
19
|
const t = theme(opts.theme);
|
|
20
20
|
const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;
|
|
21
|
-
return `<h1 style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
|
|
21
|
+
return `<h1 class="t" style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
|
|
22
22
|
}
|
|
23
23
|
function text(content, opts = {}) {
|
|
24
24
|
const t = theme(opts.theme);
|
|
25
25
|
const color = opts.muted ? t.mutedColor : t.textColor;
|
|
26
|
-
|
|
26
|
+
const cls = opts.muted ? "" : ` class="t"`;
|
|
27
|
+
return `<p${cls} style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
|
|
27
28
|
}
|
|
28
29
|
function html(raw) {
|
|
29
30
|
return raw;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n return `<p style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\"> </div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 class=\"t\" style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n // Only primary body text carries `.t`; muted text keeps its lighter grey, which\n // already reads on a dark background (see the dark-mode @media rule in render()).\n const cls = opts.muted ? \"\" : ` class=\"t\"`;\n return `<p${cls} style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\"> </div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/email-templates",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Compose bulletproof, responsive, dark-mode-aware HTML emails from simple blocks — buttons, OTP codes, invoices — with ready-made OTP/welcome/alert/invoice templates. Zero-dependency, isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -35,7 +35,13 @@
|
|
|
35
35
|
"otp-email",
|
|
36
36
|
"invoice-email",
|
|
37
37
|
"mjml-alternative",
|
|
38
|
-
"typescript"
|
|
38
|
+
"typescript",
|
|
39
|
+
"email-html",
|
|
40
|
+
"dark-mode-email",
|
|
41
|
+
"email-components",
|
|
42
|
+
"password-reset-email",
|
|
43
|
+
"magic-link-email",
|
|
44
|
+
"react-email-alternative"
|
|
39
45
|
],
|
|
40
46
|
"author": "Lacspace <contact@lacspace.com>",
|
|
41
47
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -50,5 +56,8 @@
|
|
|
50
56
|
},
|
|
51
57
|
"engines": {
|
|
52
58
|
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
53
62
|
}
|
|
54
63
|
}
|