@formepdf/hono 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,164 @@
1
+ # @formepdf/hono
2
+
3
+ PDF generation for Hono. Middleware or standalone helpers. Runs on Cloudflare Workers, Deno, Bun, and Node.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @formepdf/hono @formepdf/react @formepdf/core hono
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { Hono } from 'hono';
15
+ import { formePdf } from '@formepdf/hono';
16
+
17
+ const app = new Hono();
18
+ app.use(formePdf());
19
+
20
+ app.get('/invoice/:id', async (c) => {
21
+ const invoice = await db.invoices.findById(c.req.param('id'));
22
+
23
+ return c.pdf('invoice', {
24
+ invoiceNumber: invoice.number,
25
+ date: invoice.date,
26
+ dueDate: invoice.dueDate,
27
+ company: { name: 'Acme Corp', initials: 'AC', address: '123 Main St', cityStateZip: 'San Francisco, CA 94105', email: 'billing@acme.com' },
28
+ billTo: invoice.customer,
29
+ shipTo: invoice.shipping,
30
+ items: invoice.lineItems,
31
+ taxRate: 0.08,
32
+ paymentTerms: 'Net 30',
33
+ });
34
+ });
35
+
36
+ export default app;
37
+ ```
38
+
39
+ ## Without Middleware
40
+
41
+ ```typescript
42
+ import { pdfResponse } from '@formepdf/hono';
43
+
44
+ app.get('/invoice/:id', async (c) => {
45
+ const data = await fetchInvoiceData(c.req.param('id'));
46
+ return pdfResponse('invoice', data);
47
+ });
48
+ ```
49
+
50
+ ## Custom Templates
51
+
52
+ ```typescript
53
+ import { MyReport } from './templates/report';
54
+
55
+ app.get('/report', async (c) => {
56
+ const data = await fetchReportData();
57
+
58
+ return c.pdf(() => MyReport(data), {
59
+ filename: 'quarterly-report.pdf',
60
+ });
61
+ });
62
+ ```
63
+
64
+ ## Download vs Preview
65
+
66
+ ```typescript
67
+ // Opens in browser PDF viewer (default)
68
+ return c.pdf('invoice', data);
69
+
70
+ // Forces download dialog
71
+ return c.pdf('invoice', data, { download: true });
72
+
73
+ // Custom filename + download
74
+ return c.pdf('invoice', data, { filename: 'invoice-001.pdf', download: true });
75
+ ```
76
+
77
+ ## Cloudflare Workers
78
+
79
+ ```typescript
80
+ // src/index.ts
81
+ import { Hono } from 'hono';
82
+ import { formePdf } from '@formepdf/hono';
83
+
84
+ const app = new Hono();
85
+ app.use(formePdf());
86
+
87
+ app.get('/invoice', async (c) => {
88
+ return c.pdf('invoice', {
89
+ invoiceNumber: 'INV-001',
90
+ date: 'February 25, 2026',
91
+ dueDate: 'March 27, 2026',
92
+ company: { name: 'Acme Corp', initials: 'AC', address: '123 Main St', cityStateZip: 'San Francisco, CA 94105', email: 'billing@acme.com' },
93
+ billTo: { name: 'Jane Smith', company: 'Smith Co', address: '456 Oak Ave', cityStateZip: 'Portland, OR 97201', email: 'jane@smith.co' },
94
+ shipTo: { name: 'Jane Smith', address: '456 Oak Ave', cityStateZip: 'Portland, OR 97201' },
95
+ items: [
96
+ { description: 'Consulting', quantity: 10, unitPrice: 150 },
97
+ ],
98
+ taxRate: 0.08,
99
+ paymentTerms: 'Net 30',
100
+ });
101
+ });
102
+
103
+ export default app;
104
+ ```
105
+
106
+ ```toml
107
+ # wrangler.toml
108
+ name = "pdf-api"
109
+ compatibility_date = "2024-01-01"
110
+ ```
111
+
112
+ ```bash
113
+ wrangler deploy
114
+ ```
115
+
116
+ PDF API running at the edge. No servers, no Puppeteer, no headless Chrome.
117
+
118
+ ## API
119
+
120
+ ### `formePdf(options?)`
121
+
122
+ Returns Hono middleware that adds `c.pdf()` to the context.
123
+
124
+ | Option | Type | Default | Description |
125
+ |--------|------|---------|-------------|
126
+ | `defaultDownload` | `boolean` | `false` | Default disposition for all responses |
127
+
128
+ ### `c.pdf(template, data, options?)`
129
+
130
+ Render a built-in template and return as Response.
131
+
132
+ ### `c.pdf(renderFn, options?)`
133
+
134
+ Render a custom template and return as Response.
135
+
136
+ ### `pdfResponse(template, data, options?)`
137
+
138
+ Standalone function. Returns a Response with the PDF. No middleware required.
139
+
140
+ ### Options
141
+
142
+ | Option | Type | Default | Description |
143
+ |--------|------|---------|-------------|
144
+ | `filename` | `string` | `{template}.pdf` | Filename in Content-Disposition |
145
+ | `download` | `boolean` | `false` | Force download vs browser preview |
146
+
147
+ ## Built-in Templates
148
+
149
+ | Template | Description |
150
+ |----------|-------------|
151
+ | `invoice` | Line items, tax, totals, company/customer info |
152
+ | `receipt` | Payment confirmation, items, total, payment method |
153
+ | `report` | Multi-section document with title, headings, body text |
154
+ | `letter` | Business letter with letterhead, date, recipient, body |
155
+ | `shipping-label` | From/to addresses, 4x6 label format |
156
+
157
+ ## Why Hono + Forme
158
+
159
+ Forme's engine is Rust compiled to WASM. It runs anywhere JavaScript runs, including edge runtimes where Puppeteer can't. If you're building an API on Cloudflare Workers and need PDF generation, Forme is one of the only options.
160
+
161
+ ## Links
162
+
163
+ - [Forme](https://github.com/formepdf/forme) -- PDF generation with JSX
164
+ - [Docs](https://docs.formepdf.com)
@@ -0,0 +1,19 @@
1
+ import { listTemplates } from './templates/index.js';
2
+ import type { MiddlewareHandler } from 'hono';
3
+ import type { ReactElement } from 'react';
4
+ interface PdfOptions {
5
+ filename?: string;
6
+ download?: boolean;
7
+ }
8
+ interface FormePdfOptions {
9
+ defaultDownload?: boolean;
10
+ }
11
+ export declare function pdfResponse(templateOrRenderFn: string | (() => ReactElement), dataOrOptions?: Record<string, any> | PdfOptions, maybeOptions?: PdfOptions): Promise<Response>;
12
+ declare module 'hono' {
13
+ interface Context {
14
+ pdf: (templateOrRenderFn: string | (() => ReactElement), dataOrOptions?: Record<string, any> | PdfOptions, maybeOptions?: PdfOptions) => Promise<Response>;
15
+ }
16
+ }
17
+ export declare function formePdf(opts?: FormePdfOptions): MiddlewareHandler;
18
+ export { listTemplates };
19
+ export type { PdfOptions, FormePdfOptions };
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import { renderDocument } from '@formepdf/core';
2
+ import { getTemplate, listTemplates } from './templates/index.js';
3
+ // --- Standalone pdfResponse (no middleware needed) ---
4
+ export async function pdfResponse(templateOrRenderFn, dataOrOptions, maybeOptions) {
5
+ let element;
6
+ let options;
7
+ if (typeof templateOrRenderFn === 'string') {
8
+ const template = templateOrRenderFn;
9
+ const data = dataOrOptions || {};
10
+ options = maybeOptions || {};
11
+ const templateFn = getTemplate(template);
12
+ if (!templateFn) {
13
+ return Response.json({ error: `Unknown template: "${template}"` }, { status: 400 });
14
+ }
15
+ element = templateFn(data);
16
+ options.filename = options.filename || `${template}.pdf`;
17
+ }
18
+ else {
19
+ const renderFn = templateOrRenderFn;
20
+ options = dataOrOptions || {};
21
+ element = renderFn();
22
+ options.filename = options.filename || 'document.pdf';
23
+ }
24
+ const pdfBytes = await renderDocument(element);
25
+ const disposition = (options.download ?? false) ? 'attachment' : 'inline';
26
+ return new Response(pdfBytes, {
27
+ headers: {
28
+ 'Content-Type': 'application/pdf',
29
+ 'Content-Disposition': `${disposition}; filename="${options.filename}"`,
30
+ 'Content-Length': String(pdfBytes.byteLength),
31
+ },
32
+ });
33
+ }
34
+ export function formePdf(opts) {
35
+ const defaultDownload = opts?.defaultDownload ?? false;
36
+ return async (c, next) => {
37
+ c.pdf = async function (templateOrRenderFn, dataOrOptions, maybeOptions) {
38
+ let options;
39
+ if (typeof templateOrRenderFn === 'string') {
40
+ options = maybeOptions || {};
41
+ }
42
+ else {
43
+ options = dataOrOptions || {};
44
+ }
45
+ if (options.download === undefined) {
46
+ options.download = defaultDownload;
47
+ }
48
+ return pdfResponse(templateOrRenderFn, dataOrOptions, typeof templateOrRenderFn === 'string' ? options : undefined);
49
+ };
50
+ await next();
51
+ };
52
+ }
53
+ 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,33 @@
1
+ {
2
+ "name": "@formepdf/hono",
3
+ "version": "0.5.0",
4
+ "description": "PDF middleware and responses for Hono — works on Workers, Deno, Bun, Node",
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
+ "hono": ">=4.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "react": "^19.0.0",
24
+ "hono": "^4.0.0",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/formepdf/forme",
31
+ "directory": "packages/hono"
32
+ }
33
+ }