diff --git a/packages/email-renderer/package.json b/packages/email-renderer/package.json
index b787e6fc..c12b1a75 100644
--- a/packages/email-renderer/package.json
+++ b/packages/email-renderer/package.json
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/email-renderer",
- "version": "0.1.45",
+ "version": "0.1.46",
"type": "module",
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
"exports": {
diff --git a/packages/email-renderer/src/blocks.ts b/packages/email-renderer/src/blocks.ts
index 83fc242c..fbf8f1f5 100644
--- a/packages/email-renderer/src/blocks.ts
+++ b/packages/email-renderer/src/blocks.ts
@@ -116,18 +116,22 @@ const buttonVisuals = (node: BlockNode) => ({
fontSize: emailStyleValue("fontSize", styleBase(node, "fontSize")) || "16px",
fontWeight: emailStyleValue("fontWeight", styleBase(node, "fontWeight")) || "700",
radius: emailStyleValue("borderRadius", styleBase(node, "borderRadius")) || "6px",
+ padding: emailStyleValue("padding", styleBase(node, "padding")) || "12px 20px",
});
const buttonVisualCss = (node: BlockNode) => {
const values = buttonVisuals(node);
- return `color:${values.color};font-size:${values.fontSize};font-weight:${values.fontWeight};border-radius:${values.radius}`;
+ return `color:${values.color};font-size:${values.fontSize};font-weight:${values.fontWeight};border-radius:${values.radius};padding:${values.padding}`;
};
const buttonAlignment = (node: BlockNode, context: unknown) => {
const value = emailStyleValue("textAlign", styleBase(node, "textAlign")) || "start";
const rtl = (context as { direction?: string } | undefined)?.direction === "rtl";
return value === "start" ? (rtl ? "right" : "left") : value === "end" ? (rtl ? "left" : "right") : value;
};
-const emailCell = (body: string, node: BlockNode, align?: string) =>
- `
| ${body} |
`;
+const emailCell = (body: string, node: BlockNode, align?: string) => {
+ // Button visuals belong to the clickable element, never the full-width cell.
+ const cellNode = node.type === "content.button" ? { ...node, styles: Object.fromEntries(Object.entries(node.styles).filter(([key]) => ["textAlign", "margin"].includes(key))) } : node;
+ return `| ${body} |
`;
+};
export const emailDocumentBlock = defineBlock({
manifest: {
@@ -401,7 +405,7 @@ export const buttonBlock = leaf(
{
emailHtml: ({ node, context }) =>
emailCell(
- ``,
+ ``,
node,
),
ampEmail: ({ node, context }) =>
@@ -411,7 +415,7 @@ export const buttonBlock = leaf(
buttonAlignment(node, context),
),
mjml: ({ node, context }) =>
- `${html(attrs(node).label)}`,
+ `${html(attrs(node).label)}`,
},
({ node }) => `${String(attrs(node).label)}: ${String(attrs(node).href)}\n\n`,
);
diff --git a/packages/email-renderer/src/mjml.ts b/packages/email-renderer/src/mjml.ts
index 4b13b4df..536c85fb 100644
--- a/packages/email-renderer/src/mjml.ts
+++ b/packages/email-renderer/src/mjml.ts
@@ -262,6 +262,7 @@ export function importMjml(
"font-size": ["fontSize", /^\d+(?:\.\d+)?(?:px|rem|em|%)$/],
"font-weight": ["fontWeight", /^(normal|bold|[1-9]00)$/],
"border-radius": ["borderRadius", /^\d+(?:\.\d+)?(?:px|rem|em|%)$/],
+ "inner-padding": ["padding", /^\d+(?:\.\d+)?(?:px|rem|em|%)(?:\s+\d+(?:\.\d+)?(?:px|rem|em|%)){0,3}$/],
};
for (const [attribute, raw] of Object.entries(a)) {
if (attribute === "href") continue;
diff --git a/packages/email-renderer/test/button-surface.test.ts b/packages/email-renderer/test/button-surface.test.ts
new file mode 100644
index 00000000..ddc7d0e1
--- /dev/null
+++ b/packages/email-renderer/test/button-surface.test.ts
@@ -0,0 +1,16 @@
+import { expect, test } from "bun:test";
+import { buttonBlock, ampLayoutCss } from "../src/blocks.ts";
+
+test("button visuals target the clickable surface rather than the full-width wrapper", async () => {
+ const node = {id:"cta",type:"content.button",version:1,props:{label:"Act",href:"https://example.com"},styles:{backgroundColor:{base:"#123456"},padding:{base:"8px 32px"},borderRadius:{base:"20px"}},slots:{}};
+ const context = {node,context:{},slots:{}} as never;
+ const html = String(await buttonBlock.renderers!.emailHtml!(context));
+ const wrapper = html.match(/]*>/)![0];
+ expect(wrapper).not.toContain("#123456");
+ expect(wrapper).not.toContain("20px");
+ expect(wrapper).not.toContain("32px");
+ expect(html).toContain('bgcolor="#123456"');
+ expect(html.match(/]*>/)![0]).toContain("padding:8px 32px");
+ expect(ampLayoutCss(node)).toContain("padding:8px 32px");
+ expect(String(await buttonBlock.renderers!.mjml!(context))).toContain('inner-padding="8px 32px"');
+});
|