pdfn 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/cli.js +2217 -0
- package/dist/cli.js.map +1 -0
- package/dist/server/index.d.ts +186 -0
- package/dist/server/index.js +634 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +61 -13
- package/templates/inline/contract.tsx +217 -0
- package/templates/inline/invoice.tsx +188 -0
- package/templates/inline/letter.tsx +129 -0
- package/templates/inline/poster.tsx +128 -0
- package/templates/inline/ticket.tsx +138 -0
- package/templates/tailwind/contract.tsx +220 -0
- package/templates/tailwind/invoice.tsx +191 -0
- package/templates/tailwind/letter.tsx +132 -0
- package/templates/tailwind/poster.tsx +124 -0
- package/templates/tailwind/ticket.tsx +140 -0
- package/cli.js +0 -14
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Document, Page, Thead, PageNumber, TotalPages } from "@pdfn/react";
|
|
2
|
+
import { Tailwind } from "@pdfn/tailwind";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Professional Invoice template using Tailwind CSS
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates:
|
|
8
|
+
* - Thead with repeat for multi-page tables
|
|
9
|
+
* - PageNumber and TotalPages in footer
|
|
10
|
+
* - Configurable tax rate
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface InvoiceProps {
|
|
14
|
+
number?: string;
|
|
15
|
+
date?: string;
|
|
16
|
+
dueDate?: string;
|
|
17
|
+
customer?: {
|
|
18
|
+
name: string;
|
|
19
|
+
address: string;
|
|
20
|
+
city: string;
|
|
21
|
+
};
|
|
22
|
+
items?: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
qty: number;
|
|
26
|
+
price: number;
|
|
27
|
+
}>;
|
|
28
|
+
taxRate?: number;
|
|
29
|
+
notes?: string;
|
|
30
|
+
company?: {
|
|
31
|
+
name: string;
|
|
32
|
+
address: string;
|
|
33
|
+
email: string;
|
|
34
|
+
phone: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function Invoice({
|
|
39
|
+
number = "INV-2025-001",
|
|
40
|
+
date = "January 15, 2025",
|
|
41
|
+
dueDate = "February 14, 2025",
|
|
42
|
+
customer = {
|
|
43
|
+
name: "Acme Corporation",
|
|
44
|
+
address: "456 Enterprise Blvd, Suite 100",
|
|
45
|
+
city: "Austin, TX 78701",
|
|
46
|
+
},
|
|
47
|
+
items = [
|
|
48
|
+
{ name: "Web Development", description: "Frontend development with React", qty: 40, price: 150 },
|
|
49
|
+
{ name: "API Integration", description: "REST API setup and configuration", qty: 20, price: 175 },
|
|
50
|
+
{ name: "UI/UX Design", description: "User interface design", qty: 15, price: 125 },
|
|
51
|
+
],
|
|
52
|
+
taxRate = 0.1,
|
|
53
|
+
notes = "Payment is due within 30 days. Thank you for your business!",
|
|
54
|
+
company = {
|
|
55
|
+
name: "Your Company",
|
|
56
|
+
address: "123 Business St, San Francisco, CA 94102",
|
|
57
|
+
email: "hello@yourcompany.com",
|
|
58
|
+
phone: "+1 (555) 123-4567",
|
|
59
|
+
},
|
|
60
|
+
}: InvoiceProps) {
|
|
61
|
+
const subtotal = items.reduce((sum, item) => sum + item.qty * item.price, 0);
|
|
62
|
+
const tax = subtotal * taxRate;
|
|
63
|
+
const total = subtotal + tax;
|
|
64
|
+
|
|
65
|
+
const formatCurrency = (amount: number) =>
|
|
66
|
+
"$" + amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Document title={`Invoice ${number}`}>
|
|
70
|
+
<Tailwind>
|
|
71
|
+
<Page
|
|
72
|
+
size="A4"
|
|
73
|
+
margin="1in"
|
|
74
|
+
footer={
|
|
75
|
+
<div className="flex justify-between items-center text-xs text-gray-500 border-t border-gray-200 pt-3">
|
|
76
|
+
<div>
|
|
77
|
+
{company.name} • {company.email} • {company.phone}
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
Page <PageNumber /> of <TotalPages />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
}
|
|
84
|
+
>
|
|
85
|
+
{/* Header */}
|
|
86
|
+
<div className="flex justify-between items-start mb-8">
|
|
87
|
+
<div>
|
|
88
|
+
<div className="text-2xl font-bold text-gray-900">{company.name}</div>
|
|
89
|
+
<div className="text-xs text-gray-500 mt-1">{company.address}</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="text-right">
|
|
92
|
+
<div className="text-3xl font-bold text-gray-900 tracking-tight">INVOICE</div>
|
|
93
|
+
<div className="text-lg font-semibold text-gray-600 mt-1">{number}</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* Invoice Details & Bill To */}
|
|
98
|
+
<div className="flex justify-between mb-8">
|
|
99
|
+
<div>
|
|
100
|
+
<div className="text-xs font-semibold text-gray-500 uppercase mb-2">Bill To</div>
|
|
101
|
+
<div className="text-sm font-semibold text-gray-900">{customer.name}</div>
|
|
102
|
+
<div className="text-sm text-gray-600 mt-0.5">{customer.address}</div>
|
|
103
|
+
<div className="text-sm text-gray-600">{customer.city}</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div className="text-right">
|
|
106
|
+
<table className="ml-auto text-sm">
|
|
107
|
+
<tbody>
|
|
108
|
+
<tr>
|
|
109
|
+
<td className="text-gray-500 pr-4 py-0.5">Invoice Date:</td>
|
|
110
|
+
<td className="text-gray-900 py-0.5">{date}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td className="text-gray-500 pr-4 py-0.5">Due Date:</td>
|
|
114
|
+
<td className="text-gray-900 py-0.5">{dueDate}</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td className="text-gray-500 pr-4 py-1.5 font-semibold">Amount Due:</td>
|
|
118
|
+
<td className="text-gray-900 py-1.5 font-bold text-lg">{formatCurrency(total)}</td>
|
|
119
|
+
</tr>
|
|
120
|
+
</tbody>
|
|
121
|
+
</table>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Items Table */}
|
|
126
|
+
<table className="w-full mb-6 border-collapse">
|
|
127
|
+
<Thead repeat>
|
|
128
|
+
<tr className="bg-gray-800 text-white">
|
|
129
|
+
<th className="text-left py-3 px-4 text-xs font-semibold uppercase">Description</th>
|
|
130
|
+
<th className="text-center py-3 px-4 text-xs font-semibold uppercase w-16">Qty</th>
|
|
131
|
+
<th className="text-right py-3 px-4 text-xs font-semibold uppercase w-24">Rate</th>
|
|
132
|
+
<th className="text-right py-3 px-4 text-xs font-semibold uppercase w-28">Amount</th>
|
|
133
|
+
</tr>
|
|
134
|
+
</Thead>
|
|
135
|
+
<tbody>
|
|
136
|
+
{items.map((item, i) => (
|
|
137
|
+
<tr key={i} className={i % 2 === 0 ? "bg-white" : "bg-gray-50"}>
|
|
138
|
+
<td className="py-3 px-4 border-b border-gray-100">
|
|
139
|
+
<div className="font-medium text-gray-900 text-sm">{item.name}</div>
|
|
140
|
+
{item.description && (
|
|
141
|
+
<div className="text-xs text-gray-500 mt-0.5">{item.description}</div>
|
|
142
|
+
)}
|
|
143
|
+
</td>
|
|
144
|
+
<td className="text-center py-3 px-4 text-gray-700 text-sm border-b border-gray-100">
|
|
145
|
+
{item.qty}
|
|
146
|
+
</td>
|
|
147
|
+
<td className="text-right py-3 px-4 text-gray-700 text-sm border-b border-gray-100">
|
|
148
|
+
{formatCurrency(item.price)}
|
|
149
|
+
</td>
|
|
150
|
+
<td className="text-right py-3 px-4 font-medium text-gray-900 text-sm border-b border-gray-100">
|
|
151
|
+
{formatCurrency(item.qty * item.price)}
|
|
152
|
+
</td>
|
|
153
|
+
</tr>
|
|
154
|
+
))}
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
|
|
158
|
+
{/* Totals */}
|
|
159
|
+
<div className="flex justify-end mb-8">
|
|
160
|
+
<table className="w-64 text-sm">
|
|
161
|
+
<tbody>
|
|
162
|
+
<tr>
|
|
163
|
+
<td className="py-2 text-gray-600">Subtotal</td>
|
|
164
|
+
<td className="py-2 text-right text-gray-900">{formatCurrency(subtotal)}</td>
|
|
165
|
+
</tr>
|
|
166
|
+
<tr>
|
|
167
|
+
<td className="py-2 text-gray-600">Tax ({(taxRate * 100).toFixed(0)}%)</td>
|
|
168
|
+
<td className="py-2 text-right text-gray-900">{formatCurrency(tax)}</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr className="border-t-2 border-gray-800">
|
|
171
|
+
<td className="pt-3 pb-2 font-bold text-gray-900 text-base">Total Due</td>
|
|
172
|
+
<td className="pt-3 pb-2 text-right font-bold text-gray-900 text-lg">
|
|
173
|
+
{formatCurrency(total)}
|
|
174
|
+
</td>
|
|
175
|
+
</tr>
|
|
176
|
+
</tbody>
|
|
177
|
+
</table>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Notes */}
|
|
181
|
+
{notes && (
|
|
182
|
+
<div className="bg-gray-50 p-4 rounded-lg">
|
|
183
|
+
<div className="text-xs font-semibold text-gray-700 uppercase mb-1">Notes</div>
|
|
184
|
+
<div className="text-sm text-gray-600">{notes}</div>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</Page>
|
|
188
|
+
</Tailwind>
|
|
189
|
+
</Document>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Document, Page } from "@pdfn/react";
|
|
2
|
+
import { Tailwind } from "@pdfn/tailwind";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Business Letter template - US Letter size
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates:
|
|
8
|
+
* - Professional letterhead
|
|
9
|
+
* - Proper business letter format
|
|
10
|
+
* - Single page layout
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface LetterProps {
|
|
14
|
+
sender?: {
|
|
15
|
+
name: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
company: string;
|
|
18
|
+
address: string;
|
|
19
|
+
city: string;
|
|
20
|
+
email: string;
|
|
21
|
+
phone: string;
|
|
22
|
+
};
|
|
23
|
+
recipient?: {
|
|
24
|
+
name: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
company: string;
|
|
27
|
+
address: string;
|
|
28
|
+
city: string;
|
|
29
|
+
};
|
|
30
|
+
date?: string;
|
|
31
|
+
subject?: string;
|
|
32
|
+
body?: string[];
|
|
33
|
+
closing?: string;
|
|
34
|
+
signature?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function Letter({
|
|
38
|
+
sender = {
|
|
39
|
+
name: "Alex Chen",
|
|
40
|
+
title: "Head of Partnerships",
|
|
41
|
+
company: "Your Company",
|
|
42
|
+
address: "123 Business St, Suite 100",
|
|
43
|
+
city: "San Francisco, CA 94102",
|
|
44
|
+
email: "alex@yourcompany.com",
|
|
45
|
+
phone: "+1 (555) 123-4567",
|
|
46
|
+
},
|
|
47
|
+
recipient = {
|
|
48
|
+
name: "Sarah Johnson",
|
|
49
|
+
title: "Chief Technology Officer",
|
|
50
|
+
company: "Acme Corporation",
|
|
51
|
+
address: "456 Enterprise Blvd, Suite 100",
|
|
52
|
+
city: "Austin, TX 78701",
|
|
53
|
+
},
|
|
54
|
+
date = "January 15, 2025",
|
|
55
|
+
subject = "Partnership Proposal",
|
|
56
|
+
body = [
|
|
57
|
+
"I hope this letter finds you well. Following our conversation last month, I wanted to formally present our partnership proposal for your consideration.",
|
|
58
|
+
"Our solution has helped over 500 companies streamline their workflows, reducing development time by an average of 60%. We believe our platform would be an excellent fit for your needs.",
|
|
59
|
+
"I would welcome the opportunity to schedule a technical demo with your team. Please let me know if you would be available for a call next week.",
|
|
60
|
+
],
|
|
61
|
+
closing = "Best regards",
|
|
62
|
+
signature = "Alex Chen",
|
|
63
|
+
}: LetterProps) {
|
|
64
|
+
return (
|
|
65
|
+
<Document title={`Letter - ${subject}`}>
|
|
66
|
+
<Tailwind>
|
|
67
|
+
<Page size="Letter" margin="1in">
|
|
68
|
+
{/* Letterhead */}
|
|
69
|
+
<div className="mb-6 pb-3 border-b-2 border-gray-800">
|
|
70
|
+
<div className="flex justify-between items-start">
|
|
71
|
+
<div>
|
|
72
|
+
<div className="text-xl font-bold text-gray-900">{sender.company}</div>
|
|
73
|
+
<div className="text-xs text-gray-500 mt-1">
|
|
74
|
+
{sender.address} • {sender.city}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="text-right text-xs text-gray-500">
|
|
78
|
+
<div>{sender.email}</div>
|
|
79
|
+
<div>{sender.phone}</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Date */}
|
|
85
|
+
<div className="text-sm text-gray-700 mb-6">{date}</div>
|
|
86
|
+
|
|
87
|
+
{/* Recipient Info */}
|
|
88
|
+
<div className="mb-6">
|
|
89
|
+
<div className="text-sm font-semibold text-gray-900">{recipient.name}</div>
|
|
90
|
+
{recipient.title && (
|
|
91
|
+
<div className="text-sm text-gray-600">{recipient.title}</div>
|
|
92
|
+
)}
|
|
93
|
+
<div className="text-sm text-gray-600">{recipient.company}</div>
|
|
94
|
+
<div className="text-sm text-gray-500">
|
|
95
|
+
{recipient.address}, {recipient.city}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{/* Subject Line */}
|
|
100
|
+
<div className="mb-4 py-1.5 border-l-4 border-gray-800 pl-3 bg-gray-50">
|
|
101
|
+
<span className="text-sm font-bold text-gray-900 uppercase tracking-wide">Re: </span>
|
|
102
|
+
<span className="text-sm font-medium text-gray-800">{subject}</span>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Salutation */}
|
|
106
|
+
<div className="text-sm text-gray-900 mb-3">Dear {recipient.name},</div>
|
|
107
|
+
|
|
108
|
+
{/* Body */}
|
|
109
|
+
<div className="space-y-3 mb-6">
|
|
110
|
+
{body.map((paragraph, i) => (
|
|
111
|
+
<p key={i} className="text-sm text-gray-700 leading-relaxed">
|
|
112
|
+
{paragraph}
|
|
113
|
+
</p>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Closing & Signature */}
|
|
118
|
+
<div className="mt-6">
|
|
119
|
+
<div className="text-sm text-gray-900 mb-6">{closing},</div>
|
|
120
|
+
<div className="border-b border-gray-300 w-40 mb-1"></div>
|
|
121
|
+
<div className="text-sm font-bold text-gray-900">{signature}</div>
|
|
122
|
+
{sender.title && (
|
|
123
|
+
<div className="text-xs text-gray-600">
|
|
124
|
+
{sender.title}, {sender.company}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</Page>
|
|
129
|
+
</Tailwind>
|
|
130
|
+
</Document>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Document, Page } from "@pdfn/react";
|
|
2
|
+
import { Tailwind } from "@pdfn/tailwind";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event Poster template - Tabloid size, Landscape orientation
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates:
|
|
8
|
+
* - Large format (Tabloid: 17" x 11")
|
|
9
|
+
* - Landscape orientation
|
|
10
|
+
* - Full-bleed design (margin: 0)
|
|
11
|
+
* - Bold typography and visual hierarchy
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface PosterProps {
|
|
15
|
+
headline?: string;
|
|
16
|
+
year?: string;
|
|
17
|
+
subheadline?: string;
|
|
18
|
+
date?: string;
|
|
19
|
+
venue?: string;
|
|
20
|
+
highlights?: string[];
|
|
21
|
+
cta?: string;
|
|
22
|
+
website?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function Poster({
|
|
26
|
+
headline = "Tech Conference",
|
|
27
|
+
year = "2025",
|
|
28
|
+
subheadline = "Innovation Meets Inspiration",
|
|
29
|
+
date = "March 15-17, 2025",
|
|
30
|
+
venue = "Convention Center, San Francisco",
|
|
31
|
+
highlights = ["50+ Speakers", "Workshops", "Networking"],
|
|
32
|
+
cta = "Get Tickets",
|
|
33
|
+
website = "techconf2025.com",
|
|
34
|
+
}: PosterProps) {
|
|
35
|
+
// Tabloid landscape dimensions
|
|
36
|
+
const pageHeight = "792pt"; // 11 inches
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Document title={`Poster - ${headline}`}>
|
|
40
|
+
<Tailwind>
|
|
41
|
+
<Page size="Tabloid" orientation="landscape" margin="0">
|
|
42
|
+
{/* Full bleed dark background */}
|
|
43
|
+
<div
|
|
44
|
+
className="bg-gray-900 text-white p-12 flex flex-col"
|
|
45
|
+
style={{ minHeight: pageHeight, height: pageHeight }}
|
|
46
|
+
>
|
|
47
|
+
{/* Top Section: Logo and Accent */}
|
|
48
|
+
<div className="flex justify-between items-start mb-4">
|
|
49
|
+
<div className="text-2xl font-black text-white">PDFN</div>
|
|
50
|
+
<div className="flex gap-2">
|
|
51
|
+
<div className="h-1.5 w-32 bg-cyan-500 rounded-full"></div>
|
|
52
|
+
<div className="h-1.5 w-16 bg-cyan-500/50 rounded-full"></div>
|
|
53
|
+
<div className="h-1.5 w-8 bg-cyan-500/25 rounded-full"></div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Main Content - Vertically Centered */}
|
|
58
|
+
<div className="flex-1 flex flex-col justify-center">
|
|
59
|
+
{/* Headline */}
|
|
60
|
+
<h1 className="text-8xl font-black tracking-tight leading-none mb-6">
|
|
61
|
+
{headline}
|
|
62
|
+
{year && <span className="text-cyan-400"> {year}</span>}
|
|
63
|
+
</h1>
|
|
64
|
+
|
|
65
|
+
{subheadline && (
|
|
66
|
+
<p className="text-3xl text-gray-400 font-light max-w-3xl mb-12">
|
|
67
|
+
{subheadline}
|
|
68
|
+
</p>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* Event Details */}
|
|
72
|
+
<div className="flex gap-20">
|
|
73
|
+
<div>
|
|
74
|
+
<div className="text-sm text-cyan-500 uppercase tracking-widest font-bold mb-2">
|
|
75
|
+
Date
|
|
76
|
+
</div>
|
|
77
|
+
<div className="text-4xl font-bold">{date}</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
<div className="text-sm text-cyan-500 uppercase tracking-widest font-bold mb-2">
|
|
81
|
+
Venue
|
|
82
|
+
</div>
|
|
83
|
+
<div className="text-4xl font-bold">{venue}</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Bottom Section */}
|
|
89
|
+
<div className="flex items-end justify-between">
|
|
90
|
+
{/* Highlights/Tags */}
|
|
91
|
+
<div className="flex gap-4">
|
|
92
|
+
{highlights.map((highlight, i) => (
|
|
93
|
+
<div
|
|
94
|
+
key={i}
|
|
95
|
+
className="border-2 border-gray-600 text-white px-6 py-3 rounded-full text-base font-semibold"
|
|
96
|
+
>
|
|
97
|
+
{highlight}
|
|
98
|
+
</div>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* CTA */}
|
|
103
|
+
<div className="text-right">
|
|
104
|
+
<div className="inline-block bg-cyan-500 text-gray-900 text-2xl font-black px-10 py-5 rounded-xl">
|
|
105
|
+
{cta}
|
|
106
|
+
</div>
|
|
107
|
+
<div className="text-base text-gray-500 mt-4 font-mono tracking-wide">
|
|
108
|
+
{website}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Bottom Accent */}
|
|
114
|
+
<div className="flex justify-end gap-2 mt-8">
|
|
115
|
+
<div className="h-1.5 w-8 bg-cyan-500/25 rounded-full"></div>
|
|
116
|
+
<div className="h-1.5 w-16 bg-cyan-500/50 rounded-full"></div>
|
|
117
|
+
<div className="h-1.5 w-32 bg-cyan-500 rounded-full"></div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</Page>
|
|
121
|
+
</Tailwind>
|
|
122
|
+
</Document>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Document, Page } from "@pdfn/react";
|
|
2
|
+
import { Tailwind } from "@pdfn/tailwind";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event Ticket template - A5 size (smaller format)
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates:
|
|
8
|
+
* - Compact page size (A5: 148mm x 210mm)
|
|
9
|
+
* - Creative visual design with banner
|
|
10
|
+
* - QR code placeholder
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface TicketProps {
|
|
14
|
+
event?: string;
|
|
15
|
+
year?: string;
|
|
16
|
+
tagline?: string;
|
|
17
|
+
date?: string;
|
|
18
|
+
time?: string;
|
|
19
|
+
venue?: string;
|
|
20
|
+
venueAddress?: string;
|
|
21
|
+
attendee?: string;
|
|
22
|
+
ticketType?: string;
|
|
23
|
+
ticketNumber?: string;
|
|
24
|
+
price?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function Ticket({
|
|
28
|
+
event = "Tech Conference",
|
|
29
|
+
year = "2025",
|
|
30
|
+
tagline = "Innovation Meets Inspiration",
|
|
31
|
+
date = "March 15, 2025",
|
|
32
|
+
time = "9:00 AM - 6:00 PM",
|
|
33
|
+
venue = "Convention Center",
|
|
34
|
+
venueAddress = "123 Main St, San Francisco, CA",
|
|
35
|
+
attendee = "John Smith",
|
|
36
|
+
ticketType = "VIP Access",
|
|
37
|
+
ticketNumber = "TC25-VIP-001234",
|
|
38
|
+
price = "$599.00",
|
|
39
|
+
}: TicketProps) {
|
|
40
|
+
return (
|
|
41
|
+
<Document title={`Ticket - ${event}`}>
|
|
42
|
+
<Tailwind>
|
|
43
|
+
<Page size="A5" margin="0">
|
|
44
|
+
{/* Header Banner */}
|
|
45
|
+
<div className="bg-gray-900 px-6 py-7 text-center">
|
|
46
|
+
<div className="text-3xl font-black text-white tracking-wide">
|
|
47
|
+
{event}
|
|
48
|
+
{year && <span className="text-cyan-400"> {year}</span>}
|
|
49
|
+
</div>
|
|
50
|
+
{tagline && (
|
|
51
|
+
<div className="text-sm text-cyan-400 mt-2 font-medium uppercase tracking-widest">
|
|
52
|
+
{tagline}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Ticket Content */}
|
|
58
|
+
<div className="px-6 py-5">
|
|
59
|
+
{/* Ticket Type Badge */}
|
|
60
|
+
<div className="flex justify-center -mt-10 mb-5">
|
|
61
|
+
<div className="bg-cyan-500 text-gray-900 text-xs font-bold uppercase px-5 py-2 rounded-full tracking-widest">
|
|
62
|
+
{ticketType}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Event Details Grid */}
|
|
67
|
+
<div className="grid grid-cols-2 gap-4 mb-5">
|
|
68
|
+
<div className="bg-gray-50 rounded-lg p-3.5 text-center">
|
|
69
|
+
<div className="text-xs text-gray-500 uppercase tracking-wider font-medium">
|
|
70
|
+
Date
|
|
71
|
+
</div>
|
|
72
|
+
<div className="text-base font-bold text-gray-900 mt-1">{date}</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="bg-gray-50 rounded-lg p-3.5 text-center">
|
|
75
|
+
<div className="text-xs text-gray-500 uppercase tracking-wider font-medium">
|
|
76
|
+
Time
|
|
77
|
+
</div>
|
|
78
|
+
<div className="text-base font-bold text-gray-900 mt-1">{time}</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Venue */}
|
|
83
|
+
<div className="bg-gray-50 rounded-lg p-3.5 text-center mb-5">
|
|
84
|
+
<div className="text-xs text-gray-500 uppercase tracking-wider font-medium">
|
|
85
|
+
Venue
|
|
86
|
+
</div>
|
|
87
|
+
<div className="text-base font-bold text-gray-900 mt-1">{venue}</div>
|
|
88
|
+
<div className="text-xs text-gray-500 mt-1">{venueAddress}</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Tear Line */}
|
|
92
|
+
<div className="flex items-center gap-2 my-5">
|
|
93
|
+
<div className="flex-1 border-t-2 border-dashed border-gray-300"></div>
|
|
94
|
+
<div className="text-sm text-gray-400">✂</div>
|
|
95
|
+
<div className="flex-1 border-t-2 border-dashed border-gray-300"></div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Attendee Section */}
|
|
99
|
+
<div className="text-center mb-5">
|
|
100
|
+
<div className="text-xs text-gray-500 uppercase tracking-wider font-medium mb-1">
|
|
101
|
+
Admit One
|
|
102
|
+
</div>
|
|
103
|
+
<div className="text-4xl font-black text-gray-900">{attendee}</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* QR Code Area */}
|
|
107
|
+
<div className="flex justify-center mb-5">
|
|
108
|
+
<div className="w-36 h-36 bg-white border-2 border-gray-900 rounded-xl p-2 flex items-center justify-center">
|
|
109
|
+
{/* Simulated QR pattern */}
|
|
110
|
+
<div className="grid grid-cols-5 gap-1 w-full h-full">
|
|
111
|
+
{[...Array(25)].map((_, i) => (
|
|
112
|
+
<div
|
|
113
|
+
key={i}
|
|
114
|
+
className={`rounded-sm ${
|
|
115
|
+
[0, 1, 2, 4, 5, 6, 10, 12, 14, 18, 19, 20, 22, 23, 24].includes(i)
|
|
116
|
+
? "bg-gray-900"
|
|
117
|
+
: "bg-gray-100"
|
|
118
|
+
}`}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Footer */}
|
|
126
|
+
<div className="flex justify-between items-center text-sm border-t-2 border-gray-900 pt-3">
|
|
127
|
+
<div className="font-mono text-xs text-gray-600">{ticketNumber}</div>
|
|
128
|
+
<div className="font-bold text-lg text-gray-900">{price}</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Terms */}
|
|
132
|
+
<p className="text-center text-xs text-gray-400 mt-3">
|
|
133
|
+
Non-transferable • Non-refundable • Present at entry
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
</Page>
|
|
137
|
+
</Tailwind>
|
|
138
|
+
</Document>
|
|
139
|
+
);
|
|
140
|
+
}
|
package/cli.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
console.log(`
|
|
4
|
-
╔═══════════════════════════════════════════╗
|
|
5
|
-
║ ║
|
|
6
|
-
║ PDFN - The React framework for PDFs ║
|
|
7
|
-
║ ║
|
|
8
|
-
║ Coming soon! ║
|
|
9
|
-
║ ║
|
|
10
|
-
║ https://pdfn.dev ║
|
|
11
|
-
║ https://github.com/pdfnjs/pdfn ║
|
|
12
|
-
║ ║
|
|
13
|
-
╚═══════════════════════════════════════════╝
|
|
14
|
-
`);
|