@lacspace/email-templates 1.0.4 → 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/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
- return `<p style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
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;
@@ -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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\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;\">&nbsp;</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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\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;\">&nbsp;</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
- return `<p style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\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;\">&nbsp;</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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\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;\">&nbsp;</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.4",
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",