@formepdf/next 0.5.0

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 ADDED
@@ -0,0 +1,158 @@
1
+ # @formepdf/next
2
+
3
+ PDF generation for Next.js App Router. Route handlers, server actions, and custom templates.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @formepdf/next @formepdf/react @formepdf/core
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ // app/api/invoice/[id]/route.ts
15
+ import { pdfHandler } from '@formepdf/next';
16
+
17
+ export const GET = pdfHandler('invoice', async (req, { params }) => {
18
+ const invoice = await db.invoices.findById(params.id);
19
+
20
+ return {
21
+ invoiceNumber: invoice.number,
22
+ date: invoice.date,
23
+ dueDate: invoice.dueDate,
24
+ company: { name: 'Acme Corp', initials: 'AC', address: '123 Main St', cityStateZip: 'San Francisco, CA 94105', email: 'billing@acme.com' },
25
+ billTo: invoice.customer,
26
+ shipTo: invoice.shipping,
27
+ items: invoice.lineItems,
28
+ taxRate: 0.08,
29
+ paymentTerms: 'Net 30',
30
+ };
31
+ });
32
+ ```
33
+
34
+ Hit `GET /api/invoice/123`. Get a PDF.
35
+
36
+ ## Three Levels of Control
37
+
38
+ ### `pdfHandler` -- complete route handler
39
+
40
+ ```typescript
41
+ export const GET = pdfHandler('invoice', fetchInvoiceData, {
42
+ filename: 'invoice-001.pdf',
43
+ download: true,
44
+ });
45
+ ```
46
+
47
+ ### `pdfResponse` -- inside an existing route handler
48
+
49
+ ```typescript
50
+ export async function GET(req: NextRequest) {
51
+ const type = new URL(req.url).searchParams.get('type');
52
+
53
+ if (type === 'invoice') return pdfResponse('invoice', invoiceData);
54
+ if (type === 'receipt') return pdfResponse('receipt', receiptData);
55
+
56
+ return Response.json({ error: 'Unknown type' }, { status: 400 });
57
+ }
58
+ ```
59
+
60
+ ### `renderPdf` -- raw bytes for server actions
61
+
62
+ ```typescript
63
+ 'use server';
64
+
65
+ import { renderPdf } from '@formepdf/next';
66
+ import { put } from '@vercel/blob';
67
+
68
+ export async function generateInvoice(invoiceId: string) {
69
+ const invoice = await db.invoices.findById(invoiceId);
70
+ const pdfBytes = await renderPdf('invoice', {
71
+ invoiceNumber: invoice.number,
72
+ date: invoice.date,
73
+ dueDate: invoice.dueDate,
74
+ company: { name: 'Acme Corp', initials: 'AC', address: '123 Main St', cityStateZip: 'San Francisco, CA 94105', email: 'billing@acme.com' },
75
+ billTo: invoice.customer,
76
+ shipTo: invoice.shipping,
77
+ items: invoice.lineItems,
78
+ taxRate: 0.08,
79
+ paymentTerms: 'Net 30',
80
+ });
81
+
82
+ const { url } = await put(`invoices/${invoice.number}.pdf`, pdfBytes, {
83
+ access: 'public',
84
+ contentType: 'application/pdf',
85
+ });
86
+
87
+ return url;
88
+ }
89
+ ```
90
+
91
+ ## Custom Templates
92
+
93
+ ```typescript
94
+ import { MyReport } from '@/templates/quarterly-report';
95
+
96
+ export const GET = pdfHandler(async (req) => {
97
+ const data = await fetchReportData();
98
+ return () => MyReport(data);
99
+ }, { filename: 'Q1-report.pdf' });
100
+ ```
101
+
102
+ ## API
103
+
104
+ ### `pdfHandler(template, dataFn, options?)`
105
+
106
+ Creates a Next.js route handler from a built-in template.
107
+
108
+ - `template` -- template name
109
+ - `dataFn` -- `async (req, context) => data`
110
+ - `options` -- `{ filename?, download? }`
111
+
112
+ ### `pdfHandler(renderFnFactory, options?)`
113
+
114
+ Creates a route handler with a custom render function.
115
+
116
+ - `renderFnFactory` -- `async (req, context) => () => ReactElement`
117
+
118
+ ### `pdfResponse(template, data, options?)`
119
+
120
+ Returns a `Response` with the PDF. Use inside existing route handlers.
121
+
122
+ ### `pdfResponse(renderFn, options?)`
123
+
124
+ Same with a custom render function.
125
+
126
+ ### `renderPdf(template, data)`
127
+
128
+ Returns raw `Uint8Array` bytes. Use in server actions or anywhere you need bytes without a Response.
129
+
130
+ ### `renderPdf(renderFn)`
131
+
132
+ Same with a custom render function.
133
+
134
+ ### Options
135
+
136
+ | Option | Type | Default | Description |
137
+ |--------|------|---------|-------------|
138
+ | `filename` | `string` | `{template}.pdf` | Filename in Content-Disposition |
139
+ | `download` | `boolean` | `false` | Force download vs browser preview |
140
+
141
+ ## Built-in Templates
142
+
143
+ | Template | Description |
144
+ |----------|-------------|
145
+ | `invoice` | Line items, tax, totals, company/customer info |
146
+ | `receipt` | Payment confirmation, items, total, payment method |
147
+ | `report` | Multi-section document with title, headings, body text |
148
+ | `letter` | Business letter with letterhead, date, recipient, body |
149
+ | `shipping-label` | From/to addresses, 4x6 label format |
150
+
151
+ ## No Puppeteer
152
+
153
+ This runs in a standard Vercel function. The PDF engine is a 3MB WASM binary, not a 200MB headless browser. A 4-page invoice renders in about 28ms.
154
+
155
+ ## Links
156
+
157
+ - [Forme](https://github.com/formepdf/forme) -- PDF generation with JSX
158
+ - [Docs](https://docs.formepdf.com)
@@ -0,0 +1,11 @@
1
+ import { listTemplates } from './templates/index.js';
2
+ import type { ReactElement } from 'react';
3
+ interface PdfOptions {
4
+ filename?: string;
5
+ download?: boolean;
6
+ }
7
+ export declare function renderPdf(templateOrRenderFn: string | (() => ReactElement), data?: Record<string, any>): Promise<Uint8Array>;
8
+ export declare function pdfResponse(templateOrRenderFn: string | (() => ReactElement), dataOrOptions?: Record<string, any> | PdfOptions, maybeOptions?: PdfOptions): Promise<Response>;
9
+ export declare function pdfHandler(templateOrRenderFn: string | ((req: Request, context: any) => Promise<() => ReactElement>), dataFnOrOptions?: ((req: Request, context: any) => Promise<Record<string, any>>) | PdfOptions, maybeOptions?: PdfOptions): (req: Request, context: any) => Promise<Response>;
10
+ export { listTemplates };
11
+ export type { PdfOptions };
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ import { renderDocument } from '@formepdf/core';
2
+ import { getTemplate, listTemplates } from './templates/index.js';
3
+ // --- renderPdf: returns raw bytes ---
4
+ export async function renderPdf(templateOrRenderFn, data) {
5
+ let element;
6
+ if (typeof templateOrRenderFn === 'string') {
7
+ const templateFn = getTemplate(templateOrRenderFn);
8
+ if (!templateFn) {
9
+ throw new Error(`Unknown template: "${templateOrRenderFn}"`);
10
+ }
11
+ element = templateFn(data || {});
12
+ }
13
+ else {
14
+ element = templateOrRenderFn();
15
+ }
16
+ return renderDocument(element);
17
+ }
18
+ // --- pdfResponse: returns a Response object ---
19
+ export async function pdfResponse(templateOrRenderFn, dataOrOptions, maybeOptions) {
20
+ let pdfBytes;
21
+ let options;
22
+ if (typeof templateOrRenderFn === 'string') {
23
+ const data = dataOrOptions || {};
24
+ options = maybeOptions || {};
25
+ pdfBytes = await renderPdf(templateOrRenderFn, data);
26
+ options.filename = options.filename || `${templateOrRenderFn}.pdf`;
27
+ }
28
+ else {
29
+ options = dataOrOptions || {};
30
+ pdfBytes = await renderPdf(templateOrRenderFn);
31
+ options.filename = options.filename || 'document.pdf';
32
+ }
33
+ const disposition = options.download ? 'attachment' : 'inline';
34
+ return new Response(pdfBytes, {
35
+ headers: {
36
+ 'Content-Type': 'application/pdf',
37
+ 'Content-Disposition': `${disposition}; filename="${options.filename}"`,
38
+ 'Content-Length': String(pdfBytes.byteLength),
39
+ },
40
+ });
41
+ }
42
+ // --- pdfHandler: creates a route handler ---
43
+ export function pdfHandler(templateOrRenderFn, dataFnOrOptions, maybeOptions) {
44
+ if (typeof templateOrRenderFn === 'string') {
45
+ const template = templateOrRenderFn;
46
+ const dataFn = dataFnOrOptions;
47
+ const options = maybeOptions || {};
48
+ return async (req, context) => {
49
+ try {
50
+ const data = await dataFn(req, context);
51
+ return pdfResponse(template, data, options);
52
+ }
53
+ catch (err) {
54
+ return Response.json({ error: 'PDF render failed', message: err.message }, { status: 500 });
55
+ }
56
+ };
57
+ }
58
+ else {
59
+ const renderFnFactory = templateOrRenderFn;
60
+ const options = dataFnOrOptions || {};
61
+ return async (req, context) => {
62
+ try {
63
+ const renderFn = await renderFnFactory(req, context);
64
+ return pdfResponse(renderFn, options);
65
+ }
66
+ catch (err) {
67
+ return Response.json({ error: 'PDF render failed', message: err.message }, { status: 500 });
68
+ }
69
+ };
70
+ }
71
+ }
72
+ export { listTemplates };
@@ -0,0 +1,5 @@
1
+ import type { ReactElement } from 'react';
2
+ export declare function getTemplate(name: string): ((data: any) => ReactElement) | null;
3
+ export declare function listTemplates(): {
4
+ name: string;
5
+ }[];
@@ -0,0 +1,18 @@
1
+ import Invoice from './invoice.js';
2
+ import Receipt from './receipt.js';
3
+ import Report from './report.js';
4
+ import ShippingLabel from './shipping-label.js';
5
+ import Letter from './letter.js';
6
+ const templates = {
7
+ invoice: Invoice,
8
+ receipt: Receipt,
9
+ report: Report,
10
+ 'shipping-label': ShippingLabel,
11
+ letter: Letter,
12
+ };
13
+ export function getTemplate(name) {
14
+ return templates[name] || null;
15
+ }
16
+ export function listTemplates() {
17
+ return Object.keys(templates).map(name => ({ name }));
18
+ }
@@ -0,0 +1 @@
1
+ export default function Invoice(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text, Table, Row, Cell, Fixed } from '@formepdf/react';
3
+ export default function Invoice(data) {
4
+ const items = data.items || [];
5
+ const subtotal = items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
6
+ const tax = subtotal * (data.taxRate || 0);
7
+ const total = subtotal + tax;
8
+ return (_jsx(Document, { title: `Invoice ${data.invoiceNumber}`, author: data.company.name, children: _jsxs(Page, { size: "Letter", margin: 48, children: [_jsx(Fixed, { position: "footer", children: _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 8, borderTopWidth: 1, borderColor: '#e2e8f0' }, children: [_jsx(Text, { style: { fontSize: 8, color: '#94a3b8' }, children: data.company.name }), _jsxs(Text, { style: { fontSize: 8, color: '#94a3b8' }, children: ["Page ", '{{pageNumber}}', " of ", '{{totalPages}}'] })] }) }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 32 }, children: [_jsxs(View, { children: [_jsx(View, { style: { width: 48, height: 48, backgroundColor: '#2563eb', borderRadius: 8, marginBottom: 12, justifyContent: 'center', alignItems: 'center' }, children: _jsx(Text, { style: { fontSize: 18, fontWeight: 700, color: '#ffffff', textAlign: 'center', lineHeight: 1.2 }, children: data.company.initials }) }), _jsx(Text, { style: { fontSize: 16, fontWeight: 700, color: '#1e293b' }, children: data.company.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: data.company.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.company.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.company.email })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [_jsx(Text, { style: { fontSize: 32, fontWeight: 700, color: '#2563eb' }, children: "INVOICE" }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 8 }, children: ["Invoice No: ", data.invoiceNumber] }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: ["Date: ", data.date] }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: ["Due: ", data.dueDate] })] })] }), _jsxs(View, { style: { flexDirection: 'row', gap: 32, marginBottom: 24 }, children: [_jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#2563eb', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "Bill To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.billTo.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.company }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.email })] }), _jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#2563eb', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "Ship To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.shipTo.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.shipTo.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.shipTo.cityStateZip })] })] }), _jsxs(Table, { columns: [
9
+ { width: { fraction: 0.45 } },
10
+ { width: { fraction: 0.15 } },
11
+ { width: { fraction: 0.2 } },
12
+ { width: { fraction: 0.2 } }
13
+ ], children: [_jsxs(Row, { header: true, style: { backgroundColor: '#2563eb' }, children: [_jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff' }, children: "Description" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'center' }, children: "Qty" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' }, children: "Unit Price" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' }, children: "Amount" }) })] }), items.map((item, i) => (_jsxs(Row, { style: { backgroundColor: i % 2 === 0 ? '#ffffff' : '#f8fafc' }, children: [_jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: item.description }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, color: '#475569', textAlign: 'center' }, children: item.quantity }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsxs(Text, { style: { fontSize: 9, color: '#475569', textAlign: 'right' }, children: ["$", item.unitPrice.toFixed(2)] }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsxs(Text, { style: { fontSize: 9, color: '#1e293b', textAlign: 'right' }, children: ["$", (item.quantity * item.unitPrice).toFixed(2)] }) })] }, i)))] }), _jsx(View, { style: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 16 }, children: _jsxs(View, { style: { width: 200 }, children: [_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Subtotal" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", subtotal.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Tax (", (data.taxRate * 100).toFixed(0), "%)"] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", tax.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 12, backgroundColor: '#2563eb', borderRadius: 4, marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 11, fontWeight: 700, color: '#ffffff' }, children: "Total Due" }), _jsxs(Text, { style: { fontSize: 11, fontWeight: 700, color: '#ffffff' }, children: ["$", total.toFixed(2)] })] })] }) }), _jsxs(View, { style: { marginTop: 32, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#1e293b', marginBottom: 8 }, children: "Payment Terms" }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.paymentTerms })] }), data.notes && (_jsxs(View, { style: { marginTop: 16 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#1e293b', marginBottom: 4 }, children: "Notes" }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.notes })] }))] }) }));
14
+ }
@@ -0,0 +1 @@
1
+ export default function Letter(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function Letter(data) {
4
+ return (_jsx(Document, { title: `Letter to ${data.recipient.name}`, author: data.sender.name, children: _jsxs(Page, { size: "Letter", margin: { top: 72, right: 72, bottom: 72, left: 72 }, children: [_jsxs(View, { style: { marginBottom: 32 }, children: [_jsx(Text, { style: { fontSize: 16, fontWeight: 700, color: '#1e293b' }, children: data.sender.company }), _jsx(View, { style: { borderTopWidth: 2, borderColor: '#2563eb', marginTop: 8, marginBottom: 12 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between' }, children: [_jsxs(View, { children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.sender.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.sender.cityStateZip })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [data.sender.phone && (_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.sender.phone })), data.sender.email && (_jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.sender.email }))] })] })] }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginBottom: 24 }, children: data.date }), _jsxs(View, { style: { marginBottom: 24 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#1e293b' }, children: data.recipient.name }), data.recipient.title && (_jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.title })), data.recipient.company && (_jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.company })), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.address }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.cityStateZip })] }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginBottom: 16 }, children: data.salutation }), data.body.map((paragraph, i) => (_jsx(Text, { style: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 12 }, children: paragraph }, i))), _jsxs(View, { style: { marginTop: 16 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#1e293b' }, children: data.closing }), _jsx(View, { style: { height: 48 } }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.signatureName }), data.signatureTitle && (_jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.signatureTitle }))] })] }) }));
5
+ }
@@ -0,0 +1 @@
1
+ export default function Receipt(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function Receipt(data) {
4
+ const items = data.items || [];
5
+ const subtotal = items.reduce((sum, item) => sum + item.price * (item.quantity || 1), 0);
6
+ const tax = subtotal * (data.taxRate || 0);
7
+ const total = subtotal + tax;
8
+ return (_jsx(Document, { title: `Receipt ${data.receiptNumber}`, author: data.store.name, children: _jsxs(Page, { size: "Letter", margin: { top: 72, right: 120, bottom: 72, left: 120 }, children: [_jsxs(View, { style: { alignItems: 'center', marginBottom: 24 }, children: [_jsx(Text, { style: { fontSize: 20, fontWeight: 700, color: '#1e293b' }, children: data.store.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: data.store.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.store.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.store.phone })] }), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#e2e8f0', marginBottom: 16 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 16 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Receipt #", data.receiptNumber] }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.date })] }), items.map((item, i) => (_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 6, paddingBottom: 6 }, children: [_jsxs(View, { style: { flexDirection: 'row', gap: 8, flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: item.name }), (item.quantity || 1) > 1 && (_jsxs(Text, { style: { fontSize: 9, color: '#94a3b8' }, children: ["x", item.quantity] }))] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", (item.price * (item.quantity || 1)).toFixed(2)] })] }, i))), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#e2e8f0', marginTop: 12, marginBottom: 12 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 4, paddingBottom: 4 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Subtotal" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", subtotal.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 4, paddingBottom: 4 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Tax (", (data.taxRate * 100).toFixed(1), "%)"] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", tax.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 12, paddingBottom: 12, borderTopWidth: 2, borderColor: '#1e293b', marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b' }, children: "Total" }), _jsxs(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b' }, children: ["$", total.toFixed(2)] })] }), _jsxs(View, { style: { marginTop: 16, paddingTop: 12, paddingBottom: 12, borderTopWidth: 1, borderColor: '#e2e8f0' }, children: [_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between' }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Payment Method" }), _jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: data.paymentMethod })] }), data.cardLastFour && (_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Card" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["****", data.cardLastFour] })] }))] }), _jsxs(View, { style: { alignItems: 'center', marginTop: 32 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Thank you for your purchase!" }), _jsx(Text, { style: { fontSize: 8, color: '#94a3b8', marginTop: 8 }, children: data.store.website })] })] }) }));
9
+ }
@@ -0,0 +1 @@
1
+ export default function Report(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,179 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text, Svg, Table, Row, Cell, Fixed, PageBreak, StyleSheet } from '@formepdf/react';
3
+ const styles = StyleSheet.create({
4
+ sectionTitle: { fontSize: 20, fontWeight: 700, color: '#0f172a', marginBottom: 12 },
5
+ bodyText: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 12 },
6
+ introText: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 16 },
7
+ headerFooterText: { fontSize: 8, color: '#94a3b8' },
8
+ headerBar: { flexDirection: 'row', justifyContent: 'space-between', paddingBottom: 8, borderBottomWidth: 1, borderColor: '#e2e8f0', marginBottom: 16 },
9
+ footerBar: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 8, borderTopWidth: 1, borderColor: '#e2e8f0' },
10
+ tableHeaderCell: { padding: 8 },
11
+ tableHeaderText: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' },
12
+ tableCell: { padding: 8 },
13
+ tableCellText: { fontSize: 9, color: '#334155', textAlign: 'right' },
14
+ chartTitle: { fontSize: 10, fontWeight: 700, color: '#334155', marginBottom: 6 },
15
+ recCard: { flexDirection: 'row', gap: 24, marginBottom: 16, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4, borderLeftWidth: 3, borderColor: '#0f172a' },
16
+ recBadge: { width: 24, height: 24, backgroundColor: '#0f172a', borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
17
+ recBadgeText: { fontSize: 10, fontWeight: 700, color: '#ffffff', lineHeight: 1.2 },
18
+ recTitle: { fontSize: 11, fontWeight: 700, color: '#0f172a', marginBottom: 4 },
19
+ recBody: { fontSize: 9, color: '#475569', lineHeight: 1.5 },
20
+ recLabel: { fontSize: 8, fontWeight: 700, color: '#64748b' },
21
+ recValue: { fontSize: 8, color: '#334155' },
22
+ });
23
+ // ── Chart SVG generators ─────────────────────────────────────────────
24
+ const CHART_COLORS = ['#3b82f6', '#0f172a', '#64748b', '#94a3b8', '#cbd5e1'];
25
+ function renderBarChart(tableData) {
26
+ const totals = tableData.map((r) => ({
27
+ label: r.region,
28
+ value: parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
29
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')),
30
+ }));
31
+ const maxVal = Math.max(...totals.map(t => t.value));
32
+ const w = 230, h = 150;
33
+ const barAreaTop = 8, barAreaBottom = h - 20;
34
+ const barAreaH = barAreaBottom - barAreaTop;
35
+ const barW = Math.min(32, (w - 20) / totals.length - 8);
36
+ const gap = (w - barW * totals.length) / (totals.length + 1);
37
+ let svg = '';
38
+ // Grid lines
39
+ for (let i = 0; i <= 4; i++) {
40
+ const y = barAreaTop + (barAreaH / 4) * i;
41
+ svg += `<line x1="0" y1="${y}" x2="${w}" y2="${y}" stroke="#e2e8f0" stroke-width="0.5"/>`;
42
+ }
43
+ // Bars and labels
44
+ totals.forEach((t, i) => {
45
+ const x = gap + i * (barW + gap);
46
+ const barH = (t.value / maxVal) * barAreaH;
47
+ const y = barAreaBottom - barH;
48
+ svg += `<rect x="${x}" y="${y}" width="${barW}" height="${barH}" fill="${CHART_COLORS[i % CHART_COLORS.length]}" rx="2"/>`;
49
+ // Region label below bar
50
+ const labelX = x + barW / 2;
51
+ svg += `<rect x="${labelX - 2}" y="${barAreaBottom + 4}" width="4" height="4" fill="${CHART_COLORS[i % CHART_COLORS.length]}" rx="1"/>`;
52
+ });
53
+ return svg;
54
+ }
55
+ /** Approximate a circular arc as cubic bezier segments. */
56
+ function arcPath(cx, cy, r, startAngle, endAngle) {
57
+ let path = '';
58
+ const step = Math.PI / 2; // max 90 degrees per segment
59
+ let a1 = startAngle;
60
+ while (a1 < endAngle - 0.001) {
61
+ const a2 = Math.min(a1 + step, endAngle);
62
+ const alpha = a2 - a1;
63
+ const k = (4 / 3) * Math.tan(alpha / 4);
64
+ const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
65
+ const x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2);
66
+ const cp1x = x1 - k * r * Math.sin(a1), cp1y = y1 + k * r * Math.cos(a1);
67
+ const cp2x = x2 + k * r * Math.sin(a2), cp2y = y2 - k * r * Math.cos(a2);
68
+ if (a1 === startAngle) {
69
+ path += `M ${f(x1)} ${f(y1)} `;
70
+ }
71
+ path += `C ${f(cp1x)} ${f(cp1y)} ${f(cp2x)} ${f(cp2y)} ${f(x2)} ${f(y2)} `;
72
+ a1 = a2;
73
+ }
74
+ return path;
75
+ }
76
+ function f(n) { return n.toFixed(2); }
77
+ function renderDonutChart(tableData) {
78
+ const totals = tableData.map((r) => ({
79
+ label: r.region,
80
+ value: parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
81
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')),
82
+ }));
83
+ const sum = totals.reduce((s, t) => s + t.value, 0);
84
+ const cx = 55, cy = 75, r = 48, innerR = 28;
85
+ let svg = '';
86
+ let angle = -Math.PI / 2; // start at top
87
+ totals.forEach((t, i) => {
88
+ const sliceAngle = (t.value / sum) * Math.PI * 2;
89
+ if (sliceAngle < 0.01) {
90
+ angle += sliceAngle;
91
+ return;
92
+ }
93
+ const endAngle = angle + sliceAngle;
94
+ const ix2 = cx + innerR * Math.cos(endAngle), iy2 = cy + innerR * Math.sin(endAngle);
95
+ const innerPath = arcPathReverse(cx, cy, innerR, angle, endAngle);
96
+ const fullD = arcPath(cx, cy, r, angle, endAngle) + `L ${f(ix2)} ${f(iy2)} ` + innerPath + 'Z';
97
+ svg += `<path d="${fullD}" fill="${CHART_COLORS[i % CHART_COLORS.length]}"/>`;
98
+ angle = endAngle;
99
+ });
100
+ return svg;
101
+ }
102
+ /** Reverse arc: draws from endAngle back to startAngle using line-to after the first point. */
103
+ function arcPathReverse(cx, cy, r, startAngle, endAngle) {
104
+ const step = Math.PI / 2;
105
+ const segments = [];
106
+ let a = startAngle;
107
+ while (a < endAngle - 0.001) {
108
+ const a2 = Math.min(a + step, endAngle);
109
+ segments.push({ a1: a, a2 });
110
+ a = a2;
111
+ }
112
+ // Reverse and draw each segment backwards
113
+ let path = '';
114
+ for (let i = segments.length - 1; i >= 0; i--) {
115
+ const { a1, a2 } = segments[i];
116
+ const alpha = a2 - a1;
117
+ const k = (4 / 3) * Math.tan(alpha / 4);
118
+ const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
119
+ const x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2);
120
+ const cp1x = x1 - k * r * Math.sin(a1), cp1y = y1 + k * r * Math.cos(a1);
121
+ const cp2x = x2 + k * r * Math.sin(a2), cp2y = y2 - k * r * Math.cos(a2);
122
+ // Draw from (x2,y2) to (x1,y1) with swapped control points
123
+ path += `C ${f(cp2x)} ${f(cp2y)} ${f(cp1x)} ${f(cp1y)} ${f(x1)} ${f(y1)} `;
124
+ }
125
+ return path;
126
+ }
127
+ function renderLineChart(tableData) {
128
+ const quarters = ['q1', 'q2', 'q3', 'q4'];
129
+ const qTotals = quarters.map(q => tableData.reduce((sum, r) => sum + parseFloat(r[q].replace(/[$,]/g, '')), 0));
130
+ const minVal = Math.min(...qTotals) * 0.9;
131
+ const maxVal = Math.max(...qTotals) * 1.05;
132
+ const w = 484, h = 140;
133
+ const padL = 16, padR = 16, padT = 12, padB = 24;
134
+ const plotW = w - padL - padR, plotH = h - padT - padB;
135
+ let svg = '';
136
+ // Grid lines
137
+ for (let i = 0; i <= 4; i++) {
138
+ const y = padT + (plotH / 4) * i;
139
+ svg += `<line x1="${padL}" y1="${y}" x2="${w - padR}" y2="${y}" stroke="#e2e8f0" stroke-width="0.5"/>`;
140
+ }
141
+ // Plot points
142
+ const points = qTotals.map((v, i) => ({
143
+ x: padL + (plotW / (quarters.length - 1)) * i,
144
+ y: padT + plotH - ((v - minVal) / (maxVal - minVal)) * plotH,
145
+ }));
146
+ // Fill area under the line
147
+ const areaPath = `M ${f(points[0].x)} ${f(padT + plotH)} L ${points.map(p => `${f(p.x)} ${f(p.y)}`).join(' L ')} L ${f(points[points.length - 1].x)} ${f(padT + plotH)} Z`;
148
+ svg += `<path d="${areaPath}" fill="#3b82f6" fill-opacity="0.08"/>`;
149
+ // Line
150
+ const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${f(p.x)} ${f(p.y)}`).join(' ');
151
+ svg += `<path d="${linePath}" fill="none" stroke="#3b82f6" stroke-width="2"/>`;
152
+ // Dots
153
+ points.forEach(p => {
154
+ svg += `<circle cx="${f(p.x)}" cy="${f(p.y)}" r="3.5" fill="#ffffff" stroke="#3b82f6" stroke-width="2"/>`;
155
+ });
156
+ // Quarter markers on x-axis
157
+ points.forEach((p) => {
158
+ svg += `<line x1="${f(p.x)}" y1="${f(padT + plotH)}" x2="${f(p.x)}" y2="${f(padT + plotH + 4)}" stroke="#94a3b8" stroke-width="1"/>`;
159
+ });
160
+ return svg;
161
+ }
162
+ // ── Template ─────────────────────────────────────────────────────────
163
+ export default function Report(data) {
164
+ const tableData = data.sections[1].tableData || [];
165
+ return (_jsxs(Document, { title: data.title, author: data.author, children: [_jsx(Page, { size: "Letter", margin: 72, children: _jsxs(View, { style: { flexGrow: 1, justifyContent: 'center' }, children: [_jsxs(View, { style: { backgroundColor: '#0f172a', padding: 32, borderRadius: 4, marginBottom: 32 }, children: [_jsx(Text, { style: { fontSize: 32, fontWeight: 700, color: '#ffffff' }, children: data.title }), _jsx(Text, { style: { fontSize: 14, color: '#94a3b8', marginTop: 12 }, children: data.subtitle })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 24 }, children: [_jsxs(View, { children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Prepared by" }), _jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b', marginTop: 4 }, children: data.author }), _jsx(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: data.department })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Date" }), _jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b', marginTop: 4 }, children: data.date }), _jsx(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: data.classification })] })] })] }) }), _jsxs(Page, { size: "Letter", margin: 54, children: [_jsx(Fixed, { position: "header", children: _jsxs(View, { style: styles.headerBar, children: [_jsx(Text, { style: styles.headerFooterText, children: data.company }), _jsx(Text, { style: styles.headerFooterText, children: data.title })] }) }), _jsx(Fixed, { position: "footer", children: _jsxs(View, { style: styles.footerBar, children: [_jsx(Text, { style: styles.headerFooterText, children: data.classification }), _jsxs(Text, { style: styles.headerFooterText, children: ["Page ", '{{pageNumber}}', " of ", '{{totalPages}}'] })] }) }), _jsx(Text, { style: styles.sectionTitle, children: "Table of Contents" }), data.sections.map((section, i) => (_jsx(View, { href: `#${section.title}`, style: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 6, borderBottomWidth: 1, borderColor: '#f1f5f9' }, children: _jsxs(Text, { style: { fontSize: 10, color: '#2563eb', textDecoration: 'underline' }, children: [i + 1, ". ", section.title] }) }, i))), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[0].title, style: styles.sectionTitle, children: ["1. ", data.sections[0].title] }), (data.sections[0].paragraphs || []).map((p, i) => (_jsx(Text, { style: styles.bodyText, children: p }, i))), data.keyMetrics && (_jsx(View, { style: { flexDirection: 'row', gap: 12, marginTop: 8, marginBottom: 24 }, children: data.keyMetrics.map((metric, i) => (_jsxs(View, { style: { flexGrow: 1, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0' }, children: [_jsx(Text, { style: { fontSize: 20, fontWeight: 700, color: '#0f172a' }, children: metric.value }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: metric.label })] }, i))) })), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[1].title, style: styles.sectionTitle, children: ["2. ", data.sections[1].title] }), _jsx(Text, { style: styles.introText, children: data.sections[1].intro }), _jsxs(Table, { columns: [
166
+ { width: { fraction: 0.28 } },
167
+ { width: { fraction: 0.18 } },
168
+ { width: { fraction: 0.18 } },
169
+ { width: { fraction: 0.18 } },
170
+ { width: { fraction: 0.18 } }
171
+ ], children: [_jsxs(Row, { header: true, style: { backgroundColor: '#0f172a' }, children: [_jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff' }, children: "Region" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q1" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q2" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q3" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q4" }) })] }), tableData.map((row, i) => (_jsxs(Row, { style: { backgroundColor: i % 2 === 0 ? '#ffffff' : '#f8fafc' }, children: [_jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: { fontSize: 9, color: '#334155', fontWeight: 700 }, children: row.region }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q1 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q2 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q3 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q4 }) })] }, i)))] }), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[2].title, style: styles.sectionTitle, children: ["3. ", data.sections[2].title] }), _jsx(Text, { style: styles.introText, children: data.sections[2].intro }), _jsxs(View, { style: { flexDirection: 'row', gap: 16, marginBottom: 24 }, children: [_jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: styles.chartTitle, children: "Revenue by Region" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', padding: 8 }, children: [_jsx(Svg, { width: 230, height: 150, viewBox: "0 0 230 150", content: renderBarChart(tableData) }), _jsx(View, { style: { gap: 3, marginTop: 8 }, children: tableData.map((row, i) => (_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 4 }, children: [_jsx(View, { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: CHART_COLORS[i % CHART_COLORS.length] } }), _jsx(Text, { style: { fontSize: 7, color: '#64748b' }, children: row.region })] }, i))) })] })] }), _jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: styles.chartTitle, children: "Market Share" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', padding: 8 }, children: [_jsx(View, { style: { alignItems: 'center' }, children: _jsx(Svg, { width: 110, height: 150, viewBox: "0 0 110 150", content: renderDonutChart(tableData) }) }), _jsx(View, { style: { gap: 3, marginTop: 8 }, children: tableData.map((row, i) => {
172
+ const total = tableData.reduce((s, r) => s + parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
173
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')), 0);
174
+ const rowTotal = parseFloat(row.q1.replace(/[$,]/g, '')) + parseFloat(row.q2.replace(/[$,]/g, ''))
175
+ + parseFloat(row.q3.replace(/[$,]/g, '')) + parseFloat(row.q4.replace(/[$,]/g, ''));
176
+ const pct = ((rowTotal / total) * 100).toFixed(0);
177
+ return (_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 3 }, children: [_jsx(View, { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: CHART_COLORS[i % CHART_COLORS.length] } }), _jsxs(Text, { style: { fontSize: 7, color: '#64748b' }, children: [row.region, " ", pct, "%"] })] }, i));
178
+ }) })] })] })] }), _jsx(Text, { style: styles.chartTitle, children: "Quarterly Growth Trend" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', paddingVertical: 12, marginBottom: 24 }, children: [_jsx(Svg, { width: 484, height: 140, viewBox: "0 0 484 140", content: renderLineChart(tableData) }), _jsx(View, { style: { position: 'relative', height: 14, marginTop: 4 }, children: ['Q1', 'Q2', 'Q3', 'Q4'].map((q, i) => (_jsx(Text, { style: { position: 'absolute', left: 16 + (452 / 3) * i - 12, width: 24, fontSize: 8, color: '#64748b', textAlign: 'center' }, children: q }, i))) })] }), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[3].title, style: styles.sectionTitle, children: ["4. ", data.sections[3].title] }), _jsx(Text, { style: styles.introText, children: data.sections[3].intro }), (data.sections[3].items || []).map((item, i) => (_jsxs(View, { style: styles.recCard, children: [_jsx(View, { style: styles.recBadge, children: _jsx(Text, { style: styles.recBadgeText, children: i + 1 }) }), _jsxs(View, { style: { flexGrow: 1, flexShrink: 1 }, children: [_jsx(Text, { style: styles.recTitle, children: item.title }), _jsx(Text, { style: styles.recBody, children: item.description }), _jsxs(View, { style: { flexDirection: 'row', gap: 16, marginTop: 8 }, children: [_jsxs(View, { style: { flexDirection: 'row', gap: 4 }, children: [_jsx(Text, { style: styles.recLabel, children: "Priority:" }), _jsx(Text, { style: styles.recValue, children: item.priority })] }), _jsxs(View, { style: { flexDirection: 'row', gap: 4 }, children: [_jsx(Text, { style: styles.recLabel, children: "Timeline:" }), _jsx(Text, { style: styles.recValue, children: item.timeline })] })] })] })] }, i)))] })] }));
179
+ }
@@ -0,0 +1 @@
1
+ export default function ShippingLabel(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function ShippingLabel(data) {
4
+ return (_jsx(Document, { title: `Shipping Label - ${data.tracking}`, children: _jsxs(Page, { size: { width: 288, height: 432 }, margin: 16, children: [_jsxs(View, { style: { marginBottom: 12, padding: 8 }, children: [_jsx(Text, { style: { fontSize: 7, fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 4 }, children: "From" }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.name }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.address }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.cityStateZip })] }), _jsx(View, { style: { borderTopWidth: 2, borderColor: '#0f172a', marginBottom: 12 } }), _jsxs(View, { style: { padding: 12, marginBottom: 12 }, children: [_jsx(Text, { style: { fontSize: 7, fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a' }, children: data.to.name }), _jsx(Text, { style: { fontSize: 10, color: '#0f172a', marginTop: 4 }, children: data.to.address }), data.to.address2 && (_jsx(Text, { style: { fontSize: 10, color: '#0f172a' }, children: data.to.address2 })), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.to.cityStateZip })] }), _jsxs(View, { style: { marginBottom: 8, padding: 8 }, children: [_jsx(View, { style: { alignItems: 'center', marginBottom: 8 }, children: _jsxs(View, { style: { flexDirection: 'row', gap: 2 }, children: [_jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } })] }) }), _jsx(Text, { style: { fontSize: 8, fontWeight: 700, color: '#0f172a', letterSpacing: 2, textAlign: 'center' }, children: data.tracking })] }), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#cbd5e1', marginBottom: 8 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { style: { fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Weight" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.weight })] }), _jsxs(View, { style: { flex: 2, justifyContent: 'center', alignItems: 'center' }, children: [_jsx(Text, { style: { width: '100%', textAlign: 'center', fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Dimensions" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.dimensions })] }), _jsxs(View, { style: { flex: 1, justifyContent: 'center', alignItems: 'flex-end' }, children: [_jsx(Text, { style: { width: '100%', textAlign: 'right', fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Service" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.service })] })] }), data.stamps && data.stamps.length > 0 && (_jsx(View, { style: { flexDirection: 'row', gap: 8, marginTop: 8, padding: 8 }, children: data.stamps.map((stamp, i) => (_jsx(View, { style: { flex: 1, textAlign: 'center', paddingVertical: 6, paddingHorizontal: 12, borderWidth: 2, borderColor: '#dc2626', borderRadius: 2 }, children: _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#dc2626', textTransform: 'uppercase' }, children: stamp }) }, i))) }))] }) }));
5
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@formepdf/next",
3
+ "version": "0.5.0",
4
+ "description": "PDF route handlers and responses for Next.js App Router",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "dependencies": {
15
+ "@formepdf/react": "0.5.0",
16
+ "@formepdf/core": "0.5.0"
17
+ },
18
+ "peerDependencies": {
19
+ "next": ">=14.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "react": "^19.0.0",
24
+ "typescript": "^5.7.0"
25
+ },
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/formepdf/forme",
30
+ "directory": "packages/next"
31
+ }
32
+ }