Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/demo/src/hooks/useRenderTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function useRenderTemplate(templateId: string): RenderResult {
let plainText = "";

try {
const parts = renderToHtmlParts(element, { mode: "email" });
const parts = renderToHtmlParts(element, { mode: entry.mode ?? "email" });
head = parts.head;
html = parts.body;
} catch (e) {
Expand Down
9 changes: 8 additions & 1 deletion packages/demo/src/sections/TemplateShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ const categoryLabels: Record<string, string> = {
transactional: "Transactional",
notification: "Notification",
security: "Security",
document: "Document",
};

const modeLabels: Record<string, string> = {
email: "Email",
web: "Web",
document: "Document",
};

export default function TemplateShowcase() {
Expand Down Expand Up @@ -215,7 +222,7 @@ export default function TemplateShowcase() {
<span className="hidden sm:inline text-text-tertiary">&mdash;</span>
<span className="hidden sm:inline truncate">{selected.description}</span>
<span className="ml-auto text-[11px] text-text-tertiary hidden md:inline flex-shrink-0">
Rendering as <span className="text-accent font-medium">Email</span> mode
Rendering as <span className="text-accent font-medium">{modeLabels[selected.mode ?? "email"]}</span> mode
</span>
</div>
</div>
Expand Down
351 changes: 351 additions & 0 deletions packages/demo/src/templates/InvoiceReceipt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
import type { ReactElement } from "react";
import type { TableValues } from "@unlayer/react-elements";
import {
Document,
Row,
Column,
Paragraph,
Heading,
Divider,
Image,
Table,
ColumnLayouts,
} from "@unlayer/react-elements";

// Print-ready invoice built with <Document>. renderToHtml() returns a complete
// print/PDF-friendly HTML document that converts to PDF with Puppeteer:
//
// import { renderToHtml } from "@unlayer/react-elements";
// import puppeteer from "puppeteer";
//
// const html = renderToHtml(InvoiceReceipt(), { title: "Invoice INV-2041" });
// const browser = await puppeteer.launch();
// const page = await browser.newPage();
// await page.setContent(html, { waitUntil: "networkidle0" });
// await page.pdf({ path: "invoice.pdf", format: "A4", printBackground: true });
// await browser.close();
//
// Or with Playwright: chromium.launch() → page.setContent(html) → page.pdf().

const sansFont = {
label: "Sans Serif",
value: "system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
};

// Line items via the `values` escape hatch: the shorthand headers/data API
// can't right-align individual columns. Typed Partial — the exporter merges
// defaults for anything omitted.
const lineItemsTable: Partial<TableValues> = {
columns: 4,
rows: 3,
enableHeader: true,
headerBackgroundColor: "#f6f8fa",
headerColor: "#687385",
headerFontSize: "11px",
headerFontWeight: 600,
headerPadding: "10px 12px",
headerFontFamily: sansFont,
contentColor: "#1a1f36",
contentFontSize: "13px",
contentPadding: "12px 12px",
contentFontFamily: sansFont,
stripedRows: false,
border: {
borderTopWidth: "0px",
borderTopStyle: "solid",
borderTopColor: "#e3e8ee",
borderLeftWidth: "0px",
borderLeftStyle: "solid",
borderLeftColor: "#e3e8ee",
borderRightWidth: "0px",
borderRightStyle: "solid",
borderRightColor: "#e3e8ee",
borderBottomWidth: "1px",
borderBottomStyle: "solid",
borderBottomColor: "#e3e8ee",
},
table: {
headers: [
{
height: 0,
cells: [
{ text: "DESCRIPTION", width: 46, textAlign: "left" },
{ text: "QTY", width: 14, textAlign: "right" },
{ text: "UNIT PRICE", width: 20, textAlign: "right" },
{ text: "AMOUNT", width: 20, textAlign: "right" },
],
},
],
rows: [
{
height: 0,
cells: [
{ text: "Design system audit", width: 46, textAlign: "left" },
{ text: "1", width: 14, textAlign: "right" },
{ text: "$2,400.00", width: 20, textAlign: "right" },
{ text: "$2,400.00", width: 20, textAlign: "right" },
],
},
{
height: 0,
cells: [
{ text: "Component library development", width: 46, textAlign: "left" },
{ text: "40", width: 14, textAlign: "right" },
{ text: "$120.00", width: 20, textAlign: "right" },
{ text: "$4,800.00", width: 20, textAlign: "right" },
],
},
{
height: 0,
cells: [
{ text: "Accessibility review", width: 46, textAlign: "left" },
{ text: "1", width: 14, textAlign: "right" },
{ text: "$950.00", width: 20, textAlign: "right" },
{ text: "$950.00", width: 20, textAlign: "right" },
],
},
],
footers: [],
},
};

export default function InvoiceReceipt(): ReactElement {
return (
<Document
backgroundColor="#ffffff"
textColor="#1a1f36"
contentAlign="center"
contentWidth="700px"
fontFamily={sansFont}
>
{/* Header — logo and invoice label */}
<Row layout={ColumnLayouts.TwoEqual} backgroundColor="#ffffff" padding="40px 56px 28px 56px">
<Column>
<Image
src={{ url: "https://placehold.co/120x28/1a1f36/ffffff?text=ACME", width: 120, height: 28 }}
altText="Acme Inc."
textAlign="left"
/>
<Paragraph
html="1 Infinite Loop, Cupertino, CA 95014"
fontSize="12px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
</Column>
<Column>
<Heading
text="INVOICE"
headingType="h1"
fontSize="20px"
fontWeight={700}
color="#635bff"
textAlign="right"
lineHeight="1.3"
fontFamily={sansFont}
/>
<Paragraph
html="INV-2041"
fontSize="12px"
color="#697386"
textAlign="right"
lineHeight="1.5"
fontFamily={sansFont}
/>
</Column>
</Row>

{/* Full-bleed accent bar */}
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#635bff" padding="0px">
<Column>
<Divider
borderTopWidth="3px"
borderTopColor="#635bff"
borderTopStyle="solid"
/>
</Column>
</Row>

{/* Bill-to and invoice details */}
<Row layout={ColumnLayouts.TwoEqual} backgroundColor="#ffffff" padding="28px 56px 0px 56px">
<Column>
<Heading
text="BILLED TO"
headingType="h2"
fontSize="10px"
fontWeight={700}
color="#8792a2"
textAlign="left"
lineHeight="1.4"
fontFamily={sansFont}
/>
<Paragraph
html="Maple Studio LLC"
fontSize="14px"
color="#1a1f36"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
<Paragraph
html="510 Townsend Street"
fontSize="14px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
<Paragraph
html="San Francisco, CA 94103"
fontSize="14px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
</Column>
<Column>
<Heading
text="DETAILS"
headingType="h2"
fontSize="10px"
fontWeight={700}
color="#8792a2"
textAlign="left"
lineHeight="1.4"
fontFamily={sansFont}
/>
<Paragraph
html="Issue date: June 30, 2026"
fontSize="14px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
<Paragraph
html="Due date: July 30, 2026"
fontSize="14px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
<Paragraph
html="Status: <b>Paid</b>"
fontSize="14px"
color="#228403"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
</Column>
</Row>

{/* Line items */}
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#ffffff" padding="32px 56px 24px 56px">
<Column>
<Table values={lineItemsTable as TableValues} />
</Column>
</Row>

{/* Totals — tinted band */}
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#f7f6ff" padding="20px 56px 24px 56px">
<Column>
<Paragraph
html="Subtotal: $8,150.00"
fontSize="14px"
color="#697386"
textAlign="right"
lineHeight="1.6"
fontFamily={sansFont}
/>
<Paragraph
html="Tax (8.5%): $692.75"
fontSize="14px"
color="#697386"
textAlign="right"
lineHeight="1.6"
fontFamily={sansFont}
/>
<Divider
borderTopWidth="1px"
borderTopColor="#1a1f36"
borderTopStyle="solid"
/>
<Heading
text="$8,842.75"
headingType="h2"
fontSize="24px"
fontWeight={700}
color="#1a1f36"
textAlign="right"
lineHeight="1.3"
fontFamily={sansFont}
/>
<Paragraph
html="Invoice total (USD)"
fontSize="12px"
color="#8792a2"
textAlign="right"
lineHeight="1.4"
fontFamily={sansFont}
/>
</Column>
</Row>

{/* Payment details */}
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#ffffff" padding="24px 56px 32px 56px">
<Column>
<Divider
borderTopWidth="1px"
borderTopColor="#e3e8ee"
borderTopStyle="solid"
/>
<Heading
text="PAYMENT"
headingType="h2"
fontSize="10px"
fontWeight={700}
color="#8792a2"
textAlign="left"
lineHeight="1.4"
fontFamily={sansFont}
/>
<Paragraph
html="Paid on July 2, 2026 · Visa ending in 4242 · Reference PAY-88213"
fontSize="14px"
color="#697386"
textAlign="left"
lineHeight="1.5"
fontFamily={sansFont}
/>
</Column>
</Row>

{/* Footer band */}
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#f6f8fa" padding="16px 56px 24px 56px">
<Column>
<Paragraph
html="Thank you for your business."
fontSize="12px"
color="#697386"
textAlign="center"
lineHeight="1.6"
fontFamily={sansFont}
/>
<Paragraph
html="Questions about this invoice? Contact billing@acme.com"
fontSize="12px"
color="#8792a2"
textAlign="center"
lineHeight="1.6"
fontFamily={sansFont}
/>
</Column>
</Row>
</Document>
);
}
Loading