@gmoney2000/dygarn-pdf-kit 0.1.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 +155 -0
- package/dist/index.cjs +332 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +292 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf-lib.cjs +474 -0
- package/dist/pdf-lib.cjs.map +1 -0
- package/dist/pdf-lib.d.cts +119 -0
- package/dist/pdf-lib.d.ts +119 -0
- package/dist/pdf-lib.js +440 -0
- package/dist/pdf-lib.js.map +1 -0
- package/dist/react-pdf.cjs +241 -0
- package/dist/react-pdf.cjs.map +1 -0
- package/dist/react-pdf.d.cts +69 -0
- package/dist/react-pdf.d.ts +69 -0
- package/dist/react-pdf.js +211 -0
- package/dist/react-pdf.js.map +1 -0
- package/dist/types-CHgvU4U1.d.cts +37 -0
- package/dist/types-CHgvU4U1.d.ts +37 -0
- package/package.json +61 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react-pdf/index.ts
|
|
21
|
+
var react_pdf_exports = {};
|
|
22
|
+
__export(react_pdf_exports, {
|
|
23
|
+
BookendFooter: () => BookendFooter,
|
|
24
|
+
BookendHeader: () => BookendHeader,
|
|
25
|
+
LineCardHeader: () => LineCardHeader,
|
|
26
|
+
resolveBookendTheme: () => resolveBookendTheme
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(react_pdf_exports);
|
|
29
|
+
|
|
30
|
+
// src/react-pdf/bookend-header.tsx
|
|
31
|
+
var import_renderer = require("@react-pdf/renderer");
|
|
32
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
|
+
var styles = import_renderer.StyleSheet.create({
|
|
34
|
+
bandRow: {
|
|
35
|
+
flexDirection: "row",
|
|
36
|
+
justifyContent: "space-between",
|
|
37
|
+
alignItems: "flex-start",
|
|
38
|
+
paddingHorizontal: 40,
|
|
39
|
+
paddingVertical: 22
|
|
40
|
+
},
|
|
41
|
+
logoAndName: {
|
|
42
|
+
flexDirection: "row",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
gap: 12
|
|
45
|
+
},
|
|
46
|
+
agencyName: {
|
|
47
|
+
fontSize: 18,
|
|
48
|
+
fontFamily: "Helvetica-Bold",
|
|
49
|
+
letterSpacing: 0.5
|
|
50
|
+
},
|
|
51
|
+
agencyTagline: {
|
|
52
|
+
fontSize: 7.5,
|
|
53
|
+
marginTop: 4,
|
|
54
|
+
letterSpacing: 0.5
|
|
55
|
+
},
|
|
56
|
+
metaBox: { alignItems: "flex-end" },
|
|
57
|
+
metaLabel: {
|
|
58
|
+
fontSize: 7,
|
|
59
|
+
letterSpacing: 2,
|
|
60
|
+
textTransform: "uppercase",
|
|
61
|
+
marginBottom: 3
|
|
62
|
+
},
|
|
63
|
+
metaNum: {
|
|
64
|
+
fontSize: 20,
|
|
65
|
+
fontFamily: "Courier-Bold",
|
|
66
|
+
marginTop: 2
|
|
67
|
+
},
|
|
68
|
+
metaDate: {
|
|
69
|
+
fontSize: 8,
|
|
70
|
+
marginTop: 5
|
|
71
|
+
},
|
|
72
|
+
accentStripe: {
|
|
73
|
+
height: 3
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
function BookendHeader({
|
|
77
|
+
theme,
|
|
78
|
+
logoUrl,
|
|
79
|
+
agencyName,
|
|
80
|
+
docLabel,
|
|
81
|
+
docNumber,
|
|
82
|
+
docDate,
|
|
83
|
+
docExtra,
|
|
84
|
+
logoWidth = 80,
|
|
85
|
+
logoHeight = 48
|
|
86
|
+
}) {
|
|
87
|
+
const { bandColor, accentColor, bandTextColor } = theme;
|
|
88
|
+
const mutedText = theme.themeMode === "dark" ? "#8a9aaa" : "#666666";
|
|
89
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_renderer.View, { fixed: true, children: [
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_renderer.View, { style: [styles.bandRow, { backgroundColor: bandColor }], children: [
|
|
91
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_renderer.View, { style: styles.logoAndName, children: [
|
|
92
|
+
logoUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Image, { src: logoUrl, style: { width: logoWidth, height: logoHeight, objectFit: "contain" } }),
|
|
93
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.View, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Text, { style: [styles.agencyName, { color: bandTextColor }], children: agencyName }) })
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_renderer.View, { style: styles.metaBox, children: [
|
|
96
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Text, { style: [styles.metaLabel, { color: mutedText }], children: docLabel }),
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Text, { style: [styles.metaNum, { color: bandTextColor }], children: docNumber }),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Text, { style: [styles.metaDate, { color: mutedText }], children: docDate }),
|
|
99
|
+
docExtra && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.Text, { style: [styles.metaDate, { color: mutedText }], children: docExtra })
|
|
100
|
+
] })
|
|
101
|
+
] }),
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_renderer.View, { style: [styles.accentStripe, { backgroundColor: accentColor }] })
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/react-pdf/bookend-footer.tsx
|
|
107
|
+
var import_renderer2 = require("@react-pdf/renderer");
|
|
108
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
109
|
+
var styles2 = import_renderer2.StyleSheet.create({
|
|
110
|
+
footer: {
|
|
111
|
+
position: "absolute",
|
|
112
|
+
bottom: 20,
|
|
113
|
+
left: 40,
|
|
114
|
+
right: 40,
|
|
115
|
+
flexDirection: "row",
|
|
116
|
+
justifyContent: "space-between",
|
|
117
|
+
borderTopWidth: 1,
|
|
118
|
+
paddingTop: 8
|
|
119
|
+
},
|
|
120
|
+
footerLeft: { flexDirection: "column" },
|
|
121
|
+
footerText: { fontSize: 6.5 },
|
|
122
|
+
footerRight: { alignItems: "flex-end" }
|
|
123
|
+
});
|
|
124
|
+
function BookendFooter({
|
|
125
|
+
theme,
|
|
126
|
+
agencyName,
|
|
127
|
+
agencyPhone,
|
|
128
|
+
agencyEmail,
|
|
129
|
+
dateStamp
|
|
130
|
+
}) {
|
|
131
|
+
const { accentColor } = theme;
|
|
132
|
+
const muted = "#666666";
|
|
133
|
+
const contactParts = [];
|
|
134
|
+
if (agencyPhone) contactParts.push(agencyPhone);
|
|
135
|
+
if (agencyEmail) contactParts.push(agencyEmail);
|
|
136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_renderer2.View, { style: [styles2.footer, { borderTopColor: accentColor }], fixed: true, children: [
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_renderer2.View, { style: styles2.footerLeft, children: [
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_renderer2.Text, { style: [styles2.footerText, { color: muted }], children: agencyName }),
|
|
139
|
+
contactParts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_renderer2.Text, { style: [styles2.footerText, { color: muted }], children: contactParts.join(" \xB7 ") })
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_renderer2.View, { style: styles2.footerRight, children: [
|
|
142
|
+
dateStamp && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_renderer2.Text, { style: [styles2.footerText, { color: muted }], children: dateStamp }),
|
|
143
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
144
|
+
import_renderer2.Text,
|
|
145
|
+
{
|
|
146
|
+
style: [styles2.footerText, { color: muted }],
|
|
147
|
+
render: ({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`,
|
|
148
|
+
fixed: true
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/react-pdf/line-card-header.tsx
|
|
156
|
+
var import_renderer3 = require("@react-pdf/renderer");
|
|
157
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
158
|
+
var styles3 = import_renderer3.StyleSheet.create({
|
|
159
|
+
row: {
|
|
160
|
+
flexDirection: "row",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
justifyContent: "space-between",
|
|
163
|
+
paddingHorizontal: 44,
|
|
164
|
+
paddingTop: 22,
|
|
165
|
+
paddingBottom: 16
|
|
166
|
+
},
|
|
167
|
+
logo: { height: 64, width: 180, objectFit: "contain" },
|
|
168
|
+
fallbackName: { fontSize: 20, fontFamily: "Helvetica-Bold", letterSpacing: 0.3 },
|
|
169
|
+
contactBlock: { alignItems: "flex-end" },
|
|
170
|
+
contactLine: { fontSize: 8.5, lineHeight: 1.4 },
|
|
171
|
+
contactPhone: { fontSize: 9.5, fontFamily: "Helvetica-Bold", marginTop: 2 },
|
|
172
|
+
stripe: { height: 3, marginHorizontal: 44 }
|
|
173
|
+
});
|
|
174
|
+
function LineCardHeader({
|
|
175
|
+
theme,
|
|
176
|
+
logoUrl,
|
|
177
|
+
agencyName,
|
|
178
|
+
agencyPhone,
|
|
179
|
+
agencyEmail,
|
|
180
|
+
agencyWebsite
|
|
181
|
+
}) {
|
|
182
|
+
const { accentColor, bandColor, bandTextColor } = theme;
|
|
183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_renderer3.View, { style: [styles3.row, { backgroundColor: bandColor }], children: [
|
|
185
|
+
logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Image, { src: logoUrl, style: styles3.logo }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Text, { style: [styles3.fallbackName, { color: bandTextColor }], children: agencyName }),
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_renderer3.View, { style: styles3.contactBlock, children: [
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Text, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.8 }], children: agencyName }),
|
|
188
|
+
agencyPhone && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Text, { style: [styles3.contactPhone, { color: bandTextColor }], children: agencyPhone }),
|
|
189
|
+
agencyEmail && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Text, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.7 }], children: agencyEmail }),
|
|
190
|
+
agencyWebsite && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.Text, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.7 }], children: agencyWebsite })
|
|
191
|
+
] })
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_renderer3.View, { style: [styles3.stripe, { backgroundColor: accentColor }] })
|
|
194
|
+
] });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/brand.ts
|
|
198
|
+
function resolveBandThemeMode(brand) {
|
|
199
|
+
if (brand.pdfHeaderTheme === "dark") return "dark";
|
|
200
|
+
if (brand.pdfHeaderTheme === "light") return "light";
|
|
201
|
+
if (brand.logoDarkUrl) return "dark";
|
|
202
|
+
const candidate = (brand.logoWordmarkUrl ?? brand.logoUrl ?? brand.logoMarkUrl ?? "").toLowerCase();
|
|
203
|
+
if (candidate.includes("white") || candidate.includes("-on-dark") || candidate.includes("_dark")) return "dark";
|
|
204
|
+
return "light";
|
|
205
|
+
}
|
|
206
|
+
function darkenHex(hex, amount) {
|
|
207
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
208
|
+
if (!m) return hex;
|
|
209
|
+
const n = Number.parseInt(m[1], 16);
|
|
210
|
+
let r = n >> 16 & 255;
|
|
211
|
+
let g = n >> 8 & 255;
|
|
212
|
+
let b = n & 255;
|
|
213
|
+
r = Math.max(0, Math.round(r * (1 - amount)));
|
|
214
|
+
g = Math.max(0, Math.round(g * (1 - amount)));
|
|
215
|
+
b = Math.max(0, Math.round(b * (1 - amount)));
|
|
216
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/react-pdf/theme.ts
|
|
220
|
+
function resolveBookendTheme(brand) {
|
|
221
|
+
const themeVars = brand.themeVars ?? {};
|
|
222
|
+
const themeMode = resolveBandThemeMode(brand);
|
|
223
|
+
let bandColor;
|
|
224
|
+
if (themeMode === "dark") {
|
|
225
|
+
bandColor = themeVars["--sidebar-bg"] ?? themeVars["--header-bg"] ?? (brand.primaryColor ? darkenHex(brand.primaryColor, 0.45) : "#1e3a5f");
|
|
226
|
+
} else {
|
|
227
|
+
bandColor = "#fbfaf6";
|
|
228
|
+
}
|
|
229
|
+
const accentColor = brand.primaryColor ?? themeVars["--accent"] ?? "#1e3a5f";
|
|
230
|
+
const creamColor = "#faf9f4";
|
|
231
|
+
const bandTextColor = themeMode === "dark" ? "#ffffff" : "#111111";
|
|
232
|
+
return { bandColor, accentColor, creamColor, bandTextColor, themeMode };
|
|
233
|
+
}
|
|
234
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
235
|
+
0 && (module.exports = {
|
|
236
|
+
BookendFooter,
|
|
237
|
+
BookendHeader,
|
|
238
|
+
LineCardHeader,
|
|
239
|
+
resolveBookendTheme
|
|
240
|
+
});
|
|
241
|
+
//# sourceMappingURL=react-pdf.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react-pdf/index.ts","../src/react-pdf/bookend-header.tsx","../src/react-pdf/bookend-footer.tsx","../src/react-pdf/line-card-header.tsx","../src/brand.ts","../src/react-pdf/theme.ts"],"sourcesContent":["export { BookendHeader } from \"./bookend-header\";\nexport type { BookendHeaderProps } from \"./bookend-header\";\n\nexport { BookendFooter } from \"./bookend-footer\";\nexport type { BookendFooterProps } from \"./bookend-footer\";\n\nexport { LineCardHeader } from \"./line-card-header\";\nexport type { LineCardHeaderProps } from \"./line-card-header\";\n\nexport { resolveBookendTheme } from \"./theme\";\nexport type { BookendTheme } from \"./theme\";\n","import * as React from \"react\";\nimport { Image, StyleSheet, Text, View } from \"@react-pdf/renderer\";\n\nimport type { BookendTheme } from \"./theme\";\n\nexport interface BookendHeaderProps {\n theme: BookendTheme;\n logoUrl: string | null;\n agencyName: string;\n /** Doc label like \"Quotation\" or \"Purchase Order\" */\n docLabel: string;\n /** Doc number like \"#Q-2026-1234\" */\n docNumber: string;\n /** Formatted date string */\n docDate: string;\n /** Optional second line below date (e.g. \"Expires ...\") */\n docExtra?: string;\n logoWidth?: number;\n logoHeight?: number;\n}\n\nconst styles = StyleSheet.create({\n bandRow: {\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"flex-start\",\n paddingHorizontal: 40,\n paddingVertical: 22,\n },\n logoAndName: {\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: 12,\n },\n agencyName: {\n fontSize: 18,\n fontFamily: \"Helvetica-Bold\",\n letterSpacing: 0.5,\n },\n agencyTagline: {\n fontSize: 7.5,\n marginTop: 4,\n letterSpacing: 0.5,\n },\n metaBox: { alignItems: \"flex-end\" },\n metaLabel: {\n fontSize: 7,\n letterSpacing: 2,\n textTransform: \"uppercase\",\n marginBottom: 3,\n },\n metaNum: {\n fontSize: 20,\n fontFamily: \"Courier-Bold\",\n marginTop: 2,\n },\n metaDate: {\n fontSize: 8,\n marginTop: 5,\n },\n accentStripe: {\n height: 3,\n },\n});\n\n/**\n * Dark-or-light branded header band with logo + agency name on the left,\n * doc label/number/date on the right, accent stripe underneath.\n *\n * Renders on every page (uses React-PDF's `fixed` flag).\n */\nexport function BookendHeader({\n theme,\n logoUrl,\n agencyName,\n docLabel,\n docNumber,\n docDate,\n docExtra,\n logoWidth = 80,\n logoHeight = 48,\n}: BookendHeaderProps): React.ReactElement {\n const { bandColor, accentColor, bandTextColor } = theme;\n const mutedText = theme.themeMode === \"dark\" ? \"#8a9aaa\" : \"#666666\";\n\n return (\n <View fixed>\n <View style={[styles.bandRow, { backgroundColor: bandColor }]}>\n <View style={styles.logoAndName}>\n {logoUrl && <Image src={logoUrl} style={{ width: logoWidth, height: logoHeight, objectFit: \"contain\" }} />}\n <View>\n <Text style={[styles.agencyName, { color: bandTextColor }]}>{agencyName}</Text>\n </View>\n </View>\n <View style={styles.metaBox}>\n <Text style={[styles.metaLabel, { color: mutedText }]}>{docLabel}</Text>\n <Text style={[styles.metaNum, { color: bandTextColor }]}>{docNumber}</Text>\n <Text style={[styles.metaDate, { color: mutedText }]}>{docDate}</Text>\n {docExtra && <Text style={[styles.metaDate, { color: mutedText }]}>{docExtra}</Text>}\n </View>\n </View>\n <View style={[styles.accentStripe, { backgroundColor: accentColor }]} />\n </View>\n );\n}\n","import * as React from \"react\";\nimport { StyleSheet, Text, View } from \"@react-pdf/renderer\";\n\nimport type { BookendTheme } from \"./theme\";\n\nexport interface BookendFooterProps {\n theme: BookendTheme;\n agencyName: string;\n agencyPhone?: string | null;\n agencyEmail?: string | null;\n dateStamp?: string | null;\n}\n\nconst styles = StyleSheet.create({\n footer: {\n position: \"absolute\",\n bottom: 20,\n left: 40,\n right: 40,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n borderTopWidth: 1,\n paddingTop: 8,\n },\n footerLeft: { flexDirection: \"column\" },\n footerText: { fontSize: 6.5 },\n footerRight: { alignItems: \"flex-end\" },\n});\n\n/**\n * Fixed bottom footer with agency name + contact left, date + page numbers right.\n * Renders on every page via React-PDF's `fixed` flag.\n */\nexport function BookendFooter({\n theme,\n agencyName,\n agencyPhone,\n agencyEmail,\n dateStamp,\n}: BookendFooterProps): React.ReactElement {\n const { accentColor } = theme;\n const muted = \"#666666\";\n\n const contactParts: string[] = [];\n if (agencyPhone) contactParts.push(agencyPhone);\n if (agencyEmail) contactParts.push(agencyEmail);\n\n return (\n <View style={[styles.footer, { borderTopColor: accentColor }]} fixed>\n <View style={styles.footerLeft}>\n <Text style={[styles.footerText, { color: muted }]}>{agencyName}</Text>\n {contactParts.length > 0 && (\n <Text style={[styles.footerText, { color: muted }]}>{contactParts.join(\" · \")}</Text>\n )}\n </View>\n <View style={styles.footerRight}>\n {dateStamp && <Text style={[styles.footerText, { color: muted }]}>{dateStamp}</Text>}\n <Text\n style={[styles.footerText, { color: muted }]}\n render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`}\n fixed\n />\n </View>\n </View>\n );\n}\n","import * as React from \"react\";\nimport { Image, StyleSheet, Text, View } from \"@react-pdf/renderer\";\n\nimport type { BookendTheme } from \"./theme\";\n\nexport interface LineCardHeaderProps {\n theme: BookendTheme;\n logoUrl: string | null;\n agencyName: string;\n agencyPhone?: string | null;\n agencyEmail?: string | null;\n agencyWebsite?: string | null;\n}\n\nconst styles = StyleSheet.create({\n row: {\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n paddingHorizontal: 44,\n paddingTop: 22,\n paddingBottom: 16,\n },\n logo: { height: 64, width: 180, objectFit: \"contain\" },\n fallbackName: { fontSize: 20, fontFamily: \"Helvetica-Bold\", letterSpacing: 0.3 },\n contactBlock: { alignItems: \"flex-end\" },\n contactLine: { fontSize: 8.5, lineHeight: 1.4 },\n contactPhone: { fontSize: 9.5, fontFamily: \"Helvetica-Bold\", marginTop: 2 },\n stripe: { height: 3, marginHorizontal: 44 },\n});\n\n/**\n * Variant header for line-card style documents: logo dominant on the left,\n * contact stack on the right, accent stripe underneath. Lower-weight than the\n * standard BookendHeader (no doc number).\n */\nexport function LineCardHeader({\n theme,\n logoUrl,\n agencyName,\n agencyPhone,\n agencyEmail,\n agencyWebsite,\n}: LineCardHeaderProps): React.ReactElement {\n const { accentColor, bandColor, bandTextColor } = theme;\n\n return (\n <>\n <View style={[styles.row, { backgroundColor: bandColor }]}>\n {logoUrl ? (\n <Image src={logoUrl} style={styles.logo} />\n ) : (\n <Text style={[styles.fallbackName, { color: bandTextColor }]}>{agencyName}</Text>\n )}\n <View style={styles.contactBlock}>\n <Text style={[styles.contactLine, { color: bandTextColor, opacity: 0.8 }]}>{agencyName}</Text>\n {agencyPhone && <Text style={[styles.contactPhone, { color: bandTextColor }]}>{agencyPhone}</Text>}\n {agencyEmail && <Text style={[styles.contactLine, { color: bandTextColor, opacity: 0.7 }]}>{agencyEmail}</Text>}\n {agencyWebsite && (\n <Text style={[styles.contactLine, { color: bandTextColor, opacity: 0.7 }]}>{agencyWebsite}</Text>\n )}\n </View>\n </View>\n <View style={[styles.stripe, { backgroundColor: accentColor }]} />\n </>\n );\n}\n","import type { BrandInput } from \"./types\";\n\n/**\n * Adapter: RepFirm TenantBranding shape -> BrandInput.\n *\n * RepFirm tenants store branding on the `tenants.branding` JSONB column with\n * keys like primary_color, logo_url, logo_dark_url, etc. (snake_case). Pair\n * with the tenant name from the parent row when calling.\n */\nexport interface TenantBrandingShape {\n primary_color?: string;\n logo_url?: string | null;\n logo_wordmark_url?: string | null;\n logo_mark_url?: string | null;\n logo_dark_url?: string | null;\n logo_light_url?: string | null;\n pdf_header_theme?: \"dark\" | \"light\" | \"auto\";\n theme_vars?: Record<string, string>;\n tagline?: string | null;\n company_address?: string | null;\n company_phone?: string | null;\n company_email?: string | null;\n}\n\nexport function brandFromTenantBranding(\n tenantName: string,\n branding: TenantBrandingShape | null | undefined,\n fallbackPrimary = \"#1e3a5f\",\n): BrandInput {\n const b = branding ?? {};\n return {\n agencyName: tenantName,\n primaryColor: b.primary_color ?? fallbackPrimary,\n logoUrl: b.logo_url ?? null,\n logoDarkUrl: b.logo_dark_url ?? null,\n logoLightUrl: b.logo_light_url ?? null,\n logoWordmarkUrl: b.logo_wordmark_url ?? null,\n logoMarkUrl: b.logo_mark_url ?? null,\n pdfHeaderTheme: b.pdf_header_theme ?? \"auto\",\n themeVars: b.theme_vars ?? {},\n tagline: b.tagline ?? null,\n address: b.company_address ?? null,\n phone: b.company_phone ?? null,\n email: b.company_email ?? null,\n };\n}\n\n/**\n * Adapter: dygarn-dashboard prospect brand shape -> BrandInput.\n *\n * Dygarn-dashboard stores per-prospect brand kits in outreach_prospect_brands\n * with simpler shape (camelCase or snake — be liberal in what we accept).\n */\nexport interface ProspectBrandShape {\n agency_name?: string;\n agencyName?: string;\n legal_name?: string | null;\n legalName?: string | null;\n logo_url?: string | null;\n logoUrl?: string | null;\n primary_color?: string;\n primaryColor?: string;\n secondary_color?: string;\n secondaryColor?: string;\n address?: string | null;\n phone?: string | null;\n website?: string | null;\n}\n\nexport function brandFromProspectBrand(p: ProspectBrandShape): BrandInput {\n const agency = p.agencyName ?? p.agency_name ?? \"Agency\";\n return {\n agencyName: agency,\n legalName: p.legalName ?? p.legal_name ?? null,\n logoUrl: p.logoUrl ?? p.logo_url ?? null,\n primaryColor: p.primaryColor ?? p.primary_color ?? \"#1a73e8\",\n address: p.address ?? null,\n phone: p.phone ?? null,\n website: p.website ?? null,\n pdfHeaderTheme: \"auto\",\n };\n}\n\n/**\n * Determine whether to use a dark or light header band for this brand.\n *\n * - Explicit override: respect pdfHeaderTheme if set to 'dark' or 'light'.\n * - Auto (default): use dark theme only when a logoDarkUrl (white-on-dark\n * variant) is available, OR when the logo filename hints at a white/dark\n * variant. Otherwise default to LIGHT (safe for multi-color logos).\n */\nexport function resolveBandThemeMode(brand: BrandInput): \"dark\" | \"light\" {\n if (brand.pdfHeaderTheme === \"dark\") return \"dark\";\n if (brand.pdfHeaderTheme === \"light\") return \"light\";\n if (brand.logoDarkUrl) return \"dark\";\n const candidate = (brand.logoWordmarkUrl ?? brand.logoUrl ?? brand.logoMarkUrl ?? \"\").toLowerCase();\n if (candidate.includes(\"white\") || candidate.includes(\"-on-dark\") || candidate.includes(\"_dark\")) return \"dark\";\n return \"light\";\n}\n\n/**\n * Pick the best logo URL for the resolved theme mode.\n * Returns null if no logo available.\n */\nexport function resolveBestLogoUrl(brand: BrandInput, mode: \"dark\" | \"light\"): string | null {\n if (mode === \"dark\") {\n return brand.logoDarkUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? null;\n }\n return (\n brand.logoLightUrl ?? brand.logoUrl ?? brand.logoWordmarkUrl ?? brand.logoMarkUrl ?? brand.logoDarkUrl ?? null\n );\n}\n\n/**\n * Darken a hex color by a 0-1 amount. Used to derive a dark band color from\n * a brand primary when no explicit dark variant exists.\n */\nexport function darkenHex(hex: string, amount: number): string {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex);\n if (!m) return hex;\n const n = Number.parseInt(m[1], 16);\n let r = (n >> 16) & 0xff;\n let g = (n >> 8) & 0xff;\n let b = n & 0xff;\n r = Math.max(0, Math.round(r * (1 - amount)));\n g = Math.max(0, Math.round(g * (1 - amount)));\n b = Math.max(0, Math.round(b * (1 - amount)));\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/**\n * Convert hex to {r,g,b} normalized 0-1. Used by both React-PDF (color objects)\n * and pdf-lib (rgb() arguments).\n */\nexport function hexToRgb01(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\").trim();\n const r = Number.parseInt(h.slice(0, 2), 16) / 255;\n const g = Number.parseInt(h.slice(2, 4), 16) / 255;\n const b = Number.parseInt(h.slice(4, 6), 16) / 255;\n return [Number.isFinite(r) ? r : 0.12, Number.isFinite(g) ? g : 0.23, Number.isFinite(b) ? b : 0.37];\n}\n","import type { BrandInput } from \"../types\";\nimport { darkenHex, resolveBandThemeMode } from \"../brand\";\n\n/**\n * Theme derived from a BrandInput for use in React-PDF bookend components.\n * Mirrors the resolution logic of pdf-lib branded-bookend.ts so the two\n * rendering layers produce identical visual output.\n */\nexport interface BookendTheme {\n bandColor: string;\n accentColor: string;\n creamColor: string;\n bandTextColor: string;\n themeMode: \"dark\" | \"light\";\n}\n\nexport function resolveBookendTheme(brand: BrandInput): BookendTheme {\n const themeVars = brand.themeVars ?? {};\n const themeMode = resolveBandThemeMode(brand);\n\n let bandColor: string;\n if (themeMode === \"dark\") {\n bandColor =\n themeVars[\"--sidebar-bg\"] ??\n themeVars[\"--header-bg\"] ??\n (brand.primaryColor ? darkenHex(brand.primaryColor, 0.45) : \"#1e3a5f\");\n } else {\n bandColor = \"#fbfaf6\";\n }\n\n const accentColor = brand.primaryColor ?? themeVars[\"--accent\"] ?? \"#1e3a5f\";\n const creamColor = \"#faf9f4\";\n const bandTextColor = themeMode === \"dark\" ? \"#ffffff\" : \"#111111\";\n\n return { bandColor, accentColor, creamColor, bandTextColor, themeMode };\n}\n"],"mappings":"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","names":["import_renderer","import_jsx_runtime","styles","import_renderer","import_jsx_runtime","styles"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { B as BrandInput } from './types-CHgvU4U1.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Theme derived from a BrandInput for use in React-PDF bookend components.
|
|
6
|
+
* Mirrors the resolution logic of pdf-lib branded-bookend.ts so the two
|
|
7
|
+
* rendering layers produce identical visual output.
|
|
8
|
+
*/
|
|
9
|
+
interface BookendTheme {
|
|
10
|
+
bandColor: string;
|
|
11
|
+
accentColor: string;
|
|
12
|
+
creamColor: string;
|
|
13
|
+
bandTextColor: string;
|
|
14
|
+
themeMode: "dark" | "light";
|
|
15
|
+
}
|
|
16
|
+
declare function resolveBookendTheme(brand: BrandInput): BookendTheme;
|
|
17
|
+
|
|
18
|
+
interface BookendHeaderProps {
|
|
19
|
+
theme: BookendTheme;
|
|
20
|
+
logoUrl: string | null;
|
|
21
|
+
agencyName: string;
|
|
22
|
+
/** Doc label like "Quotation" or "Purchase Order" */
|
|
23
|
+
docLabel: string;
|
|
24
|
+
/** Doc number like "#Q-2026-1234" */
|
|
25
|
+
docNumber: string;
|
|
26
|
+
/** Formatted date string */
|
|
27
|
+
docDate: string;
|
|
28
|
+
/** Optional second line below date (e.g. "Expires ...") */
|
|
29
|
+
docExtra?: string;
|
|
30
|
+
logoWidth?: number;
|
|
31
|
+
logoHeight?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Dark-or-light branded header band with logo + agency name on the left,
|
|
35
|
+
* doc label/number/date on the right, accent stripe underneath.
|
|
36
|
+
*
|
|
37
|
+
* Renders on every page (uses React-PDF's `fixed` flag).
|
|
38
|
+
*/
|
|
39
|
+
declare function BookendHeader({ theme, logoUrl, agencyName, docLabel, docNumber, docDate, docExtra, logoWidth, logoHeight, }: BookendHeaderProps): React.ReactElement;
|
|
40
|
+
|
|
41
|
+
interface BookendFooterProps {
|
|
42
|
+
theme: BookendTheme;
|
|
43
|
+
agencyName: string;
|
|
44
|
+
agencyPhone?: string | null;
|
|
45
|
+
agencyEmail?: string | null;
|
|
46
|
+
dateStamp?: string | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fixed bottom footer with agency name + contact left, date + page numbers right.
|
|
50
|
+
* Renders on every page via React-PDF's `fixed` flag.
|
|
51
|
+
*/
|
|
52
|
+
declare function BookendFooter({ theme, agencyName, agencyPhone, agencyEmail, dateStamp, }: BookendFooterProps): React.ReactElement;
|
|
53
|
+
|
|
54
|
+
interface LineCardHeaderProps {
|
|
55
|
+
theme: BookendTheme;
|
|
56
|
+
logoUrl: string | null;
|
|
57
|
+
agencyName: string;
|
|
58
|
+
agencyPhone?: string | null;
|
|
59
|
+
agencyEmail?: string | null;
|
|
60
|
+
agencyWebsite?: string | null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Variant header for line-card style documents: logo dominant on the left,
|
|
64
|
+
* contact stack on the right, accent stripe underneath. Lower-weight than the
|
|
65
|
+
* standard BookendHeader (no doc number).
|
|
66
|
+
*/
|
|
67
|
+
declare function LineCardHeader({ theme, logoUrl, agencyName, agencyPhone, agencyEmail, agencyWebsite, }: LineCardHeaderProps): React.ReactElement;
|
|
68
|
+
|
|
69
|
+
export { BookendFooter, type BookendFooterProps, BookendHeader, type BookendHeaderProps, type BookendTheme, LineCardHeader, type LineCardHeaderProps, resolveBookendTheme };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { B as BrandInput } from './types-CHgvU4U1.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Theme derived from a BrandInput for use in React-PDF bookend components.
|
|
6
|
+
* Mirrors the resolution logic of pdf-lib branded-bookend.ts so the two
|
|
7
|
+
* rendering layers produce identical visual output.
|
|
8
|
+
*/
|
|
9
|
+
interface BookendTheme {
|
|
10
|
+
bandColor: string;
|
|
11
|
+
accentColor: string;
|
|
12
|
+
creamColor: string;
|
|
13
|
+
bandTextColor: string;
|
|
14
|
+
themeMode: "dark" | "light";
|
|
15
|
+
}
|
|
16
|
+
declare function resolveBookendTheme(brand: BrandInput): BookendTheme;
|
|
17
|
+
|
|
18
|
+
interface BookendHeaderProps {
|
|
19
|
+
theme: BookendTheme;
|
|
20
|
+
logoUrl: string | null;
|
|
21
|
+
agencyName: string;
|
|
22
|
+
/** Doc label like "Quotation" or "Purchase Order" */
|
|
23
|
+
docLabel: string;
|
|
24
|
+
/** Doc number like "#Q-2026-1234" */
|
|
25
|
+
docNumber: string;
|
|
26
|
+
/** Formatted date string */
|
|
27
|
+
docDate: string;
|
|
28
|
+
/** Optional second line below date (e.g. "Expires ...") */
|
|
29
|
+
docExtra?: string;
|
|
30
|
+
logoWidth?: number;
|
|
31
|
+
logoHeight?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Dark-or-light branded header band with logo + agency name on the left,
|
|
35
|
+
* doc label/number/date on the right, accent stripe underneath.
|
|
36
|
+
*
|
|
37
|
+
* Renders on every page (uses React-PDF's `fixed` flag).
|
|
38
|
+
*/
|
|
39
|
+
declare function BookendHeader({ theme, logoUrl, agencyName, docLabel, docNumber, docDate, docExtra, logoWidth, logoHeight, }: BookendHeaderProps): React.ReactElement;
|
|
40
|
+
|
|
41
|
+
interface BookendFooterProps {
|
|
42
|
+
theme: BookendTheme;
|
|
43
|
+
agencyName: string;
|
|
44
|
+
agencyPhone?: string | null;
|
|
45
|
+
agencyEmail?: string | null;
|
|
46
|
+
dateStamp?: string | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fixed bottom footer with agency name + contact left, date + page numbers right.
|
|
50
|
+
* Renders on every page via React-PDF's `fixed` flag.
|
|
51
|
+
*/
|
|
52
|
+
declare function BookendFooter({ theme, agencyName, agencyPhone, agencyEmail, dateStamp, }: BookendFooterProps): React.ReactElement;
|
|
53
|
+
|
|
54
|
+
interface LineCardHeaderProps {
|
|
55
|
+
theme: BookendTheme;
|
|
56
|
+
logoUrl: string | null;
|
|
57
|
+
agencyName: string;
|
|
58
|
+
agencyPhone?: string | null;
|
|
59
|
+
agencyEmail?: string | null;
|
|
60
|
+
agencyWebsite?: string | null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Variant header for line-card style documents: logo dominant on the left,
|
|
64
|
+
* contact stack on the right, accent stripe underneath. Lower-weight than the
|
|
65
|
+
* standard BookendHeader (no doc number).
|
|
66
|
+
*/
|
|
67
|
+
declare function LineCardHeader({ theme, logoUrl, agencyName, agencyPhone, agencyEmail, agencyWebsite, }: LineCardHeaderProps): React.ReactElement;
|
|
68
|
+
|
|
69
|
+
export { BookendFooter, type BookendFooterProps, BookendHeader, type BookendHeaderProps, type BookendTheme, LineCardHeader, type LineCardHeaderProps, resolveBookendTheme };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// src/react-pdf/bookend-header.tsx
|
|
2
|
+
import { Image, StyleSheet, Text, View } from "@react-pdf/renderer";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var styles = StyleSheet.create({
|
|
5
|
+
bandRow: {
|
|
6
|
+
flexDirection: "row",
|
|
7
|
+
justifyContent: "space-between",
|
|
8
|
+
alignItems: "flex-start",
|
|
9
|
+
paddingHorizontal: 40,
|
|
10
|
+
paddingVertical: 22
|
|
11
|
+
},
|
|
12
|
+
logoAndName: {
|
|
13
|
+
flexDirection: "row",
|
|
14
|
+
alignItems: "center",
|
|
15
|
+
gap: 12
|
|
16
|
+
},
|
|
17
|
+
agencyName: {
|
|
18
|
+
fontSize: 18,
|
|
19
|
+
fontFamily: "Helvetica-Bold",
|
|
20
|
+
letterSpacing: 0.5
|
|
21
|
+
},
|
|
22
|
+
agencyTagline: {
|
|
23
|
+
fontSize: 7.5,
|
|
24
|
+
marginTop: 4,
|
|
25
|
+
letterSpacing: 0.5
|
|
26
|
+
},
|
|
27
|
+
metaBox: { alignItems: "flex-end" },
|
|
28
|
+
metaLabel: {
|
|
29
|
+
fontSize: 7,
|
|
30
|
+
letterSpacing: 2,
|
|
31
|
+
textTransform: "uppercase",
|
|
32
|
+
marginBottom: 3
|
|
33
|
+
},
|
|
34
|
+
metaNum: {
|
|
35
|
+
fontSize: 20,
|
|
36
|
+
fontFamily: "Courier-Bold",
|
|
37
|
+
marginTop: 2
|
|
38
|
+
},
|
|
39
|
+
metaDate: {
|
|
40
|
+
fontSize: 8,
|
|
41
|
+
marginTop: 5
|
|
42
|
+
},
|
|
43
|
+
accentStripe: {
|
|
44
|
+
height: 3
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
function BookendHeader({
|
|
48
|
+
theme,
|
|
49
|
+
logoUrl,
|
|
50
|
+
agencyName,
|
|
51
|
+
docLabel,
|
|
52
|
+
docNumber,
|
|
53
|
+
docDate,
|
|
54
|
+
docExtra,
|
|
55
|
+
logoWidth = 80,
|
|
56
|
+
logoHeight = 48
|
|
57
|
+
}) {
|
|
58
|
+
const { bandColor, accentColor, bandTextColor } = theme;
|
|
59
|
+
const mutedText = theme.themeMode === "dark" ? "#8a9aaa" : "#666666";
|
|
60
|
+
return /* @__PURE__ */ jsxs(View, { fixed: true, children: [
|
|
61
|
+
/* @__PURE__ */ jsxs(View, { style: [styles.bandRow, { backgroundColor: bandColor }], children: [
|
|
62
|
+
/* @__PURE__ */ jsxs(View, { style: styles.logoAndName, children: [
|
|
63
|
+
logoUrl && /* @__PURE__ */ jsx(Image, { src: logoUrl, style: { width: logoWidth, height: logoHeight, objectFit: "contain" } }),
|
|
64
|
+
/* @__PURE__ */ jsx(View, { children: /* @__PURE__ */ jsx(Text, { style: [styles.agencyName, { color: bandTextColor }], children: agencyName }) })
|
|
65
|
+
] }),
|
|
66
|
+
/* @__PURE__ */ jsxs(View, { style: styles.metaBox, children: [
|
|
67
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.metaLabel, { color: mutedText }], children: docLabel }),
|
|
68
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.metaNum, { color: bandTextColor }], children: docNumber }),
|
|
69
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.metaDate, { color: mutedText }], children: docDate }),
|
|
70
|
+
docExtra && /* @__PURE__ */ jsx(Text, { style: [styles.metaDate, { color: mutedText }], children: docExtra })
|
|
71
|
+
] })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx(View, { style: [styles.accentStripe, { backgroundColor: accentColor }] })
|
|
74
|
+
] });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/react-pdf/bookend-footer.tsx
|
|
78
|
+
import { StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "@react-pdf/renderer";
|
|
79
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
80
|
+
var styles2 = StyleSheet2.create({
|
|
81
|
+
footer: {
|
|
82
|
+
position: "absolute",
|
|
83
|
+
bottom: 20,
|
|
84
|
+
left: 40,
|
|
85
|
+
right: 40,
|
|
86
|
+
flexDirection: "row",
|
|
87
|
+
justifyContent: "space-between",
|
|
88
|
+
borderTopWidth: 1,
|
|
89
|
+
paddingTop: 8
|
|
90
|
+
},
|
|
91
|
+
footerLeft: { flexDirection: "column" },
|
|
92
|
+
footerText: { fontSize: 6.5 },
|
|
93
|
+
footerRight: { alignItems: "flex-end" }
|
|
94
|
+
});
|
|
95
|
+
function BookendFooter({
|
|
96
|
+
theme,
|
|
97
|
+
agencyName,
|
|
98
|
+
agencyPhone,
|
|
99
|
+
agencyEmail,
|
|
100
|
+
dateStamp
|
|
101
|
+
}) {
|
|
102
|
+
const { accentColor } = theme;
|
|
103
|
+
const muted = "#666666";
|
|
104
|
+
const contactParts = [];
|
|
105
|
+
if (agencyPhone) contactParts.push(agencyPhone);
|
|
106
|
+
if (agencyEmail) contactParts.push(agencyEmail);
|
|
107
|
+
return /* @__PURE__ */ jsxs2(View2, { style: [styles2.footer, { borderTopColor: accentColor }], fixed: true, children: [
|
|
108
|
+
/* @__PURE__ */ jsxs2(View2, { style: styles2.footerLeft, children: [
|
|
109
|
+
/* @__PURE__ */ jsx2(Text2, { style: [styles2.footerText, { color: muted }], children: agencyName }),
|
|
110
|
+
contactParts.length > 0 && /* @__PURE__ */ jsx2(Text2, { style: [styles2.footerText, { color: muted }], children: contactParts.join(" \xB7 ") })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsxs2(View2, { style: styles2.footerRight, children: [
|
|
113
|
+
dateStamp && /* @__PURE__ */ jsx2(Text2, { style: [styles2.footerText, { color: muted }], children: dateStamp }),
|
|
114
|
+
/* @__PURE__ */ jsx2(
|
|
115
|
+
Text2,
|
|
116
|
+
{
|
|
117
|
+
style: [styles2.footerText, { color: muted }],
|
|
118
|
+
render: ({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`,
|
|
119
|
+
fixed: true
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] })
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/react-pdf/line-card-header.tsx
|
|
127
|
+
import { Image as Image2, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "@react-pdf/renderer";
|
|
128
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
129
|
+
var styles3 = StyleSheet3.create({
|
|
130
|
+
row: {
|
|
131
|
+
flexDirection: "row",
|
|
132
|
+
alignItems: "center",
|
|
133
|
+
justifyContent: "space-between",
|
|
134
|
+
paddingHorizontal: 44,
|
|
135
|
+
paddingTop: 22,
|
|
136
|
+
paddingBottom: 16
|
|
137
|
+
},
|
|
138
|
+
logo: { height: 64, width: 180, objectFit: "contain" },
|
|
139
|
+
fallbackName: { fontSize: 20, fontFamily: "Helvetica-Bold", letterSpacing: 0.3 },
|
|
140
|
+
contactBlock: { alignItems: "flex-end" },
|
|
141
|
+
contactLine: { fontSize: 8.5, lineHeight: 1.4 },
|
|
142
|
+
contactPhone: { fontSize: 9.5, fontFamily: "Helvetica-Bold", marginTop: 2 },
|
|
143
|
+
stripe: { height: 3, marginHorizontal: 44 }
|
|
144
|
+
});
|
|
145
|
+
function LineCardHeader({
|
|
146
|
+
theme,
|
|
147
|
+
logoUrl,
|
|
148
|
+
agencyName,
|
|
149
|
+
agencyPhone,
|
|
150
|
+
agencyEmail,
|
|
151
|
+
agencyWebsite
|
|
152
|
+
}) {
|
|
153
|
+
const { accentColor, bandColor, bandTextColor } = theme;
|
|
154
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
155
|
+
/* @__PURE__ */ jsxs3(View3, { style: [styles3.row, { backgroundColor: bandColor }], children: [
|
|
156
|
+
logoUrl ? /* @__PURE__ */ jsx3(Image2, { src: logoUrl, style: styles3.logo }) : /* @__PURE__ */ jsx3(Text3, { style: [styles3.fallbackName, { color: bandTextColor }], children: agencyName }),
|
|
157
|
+
/* @__PURE__ */ jsxs3(View3, { style: styles3.contactBlock, children: [
|
|
158
|
+
/* @__PURE__ */ jsx3(Text3, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.8 }], children: agencyName }),
|
|
159
|
+
agencyPhone && /* @__PURE__ */ jsx3(Text3, { style: [styles3.contactPhone, { color: bandTextColor }], children: agencyPhone }),
|
|
160
|
+
agencyEmail && /* @__PURE__ */ jsx3(Text3, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.7 }], children: agencyEmail }),
|
|
161
|
+
agencyWebsite && /* @__PURE__ */ jsx3(Text3, { style: [styles3.contactLine, { color: bandTextColor, opacity: 0.7 }], children: agencyWebsite })
|
|
162
|
+
] })
|
|
163
|
+
] }),
|
|
164
|
+
/* @__PURE__ */ jsx3(View3, { style: [styles3.stripe, { backgroundColor: accentColor }] })
|
|
165
|
+
] });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/brand.ts
|
|
169
|
+
function resolveBandThemeMode(brand) {
|
|
170
|
+
if (brand.pdfHeaderTheme === "dark") return "dark";
|
|
171
|
+
if (brand.pdfHeaderTheme === "light") return "light";
|
|
172
|
+
if (brand.logoDarkUrl) return "dark";
|
|
173
|
+
const candidate = (brand.logoWordmarkUrl ?? brand.logoUrl ?? brand.logoMarkUrl ?? "").toLowerCase();
|
|
174
|
+
if (candidate.includes("white") || candidate.includes("-on-dark") || candidate.includes("_dark")) return "dark";
|
|
175
|
+
return "light";
|
|
176
|
+
}
|
|
177
|
+
function darkenHex(hex, amount) {
|
|
178
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
179
|
+
if (!m) return hex;
|
|
180
|
+
const n = Number.parseInt(m[1], 16);
|
|
181
|
+
let r = n >> 16 & 255;
|
|
182
|
+
let g = n >> 8 & 255;
|
|
183
|
+
let b = n & 255;
|
|
184
|
+
r = Math.max(0, Math.round(r * (1 - amount)));
|
|
185
|
+
g = Math.max(0, Math.round(g * (1 - amount)));
|
|
186
|
+
b = Math.max(0, Math.round(b * (1 - amount)));
|
|
187
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/react-pdf/theme.ts
|
|
191
|
+
function resolveBookendTheme(brand) {
|
|
192
|
+
const themeVars = brand.themeVars ?? {};
|
|
193
|
+
const themeMode = resolveBandThemeMode(brand);
|
|
194
|
+
let bandColor;
|
|
195
|
+
if (themeMode === "dark") {
|
|
196
|
+
bandColor = themeVars["--sidebar-bg"] ?? themeVars["--header-bg"] ?? (brand.primaryColor ? darkenHex(brand.primaryColor, 0.45) : "#1e3a5f");
|
|
197
|
+
} else {
|
|
198
|
+
bandColor = "#fbfaf6";
|
|
199
|
+
}
|
|
200
|
+
const accentColor = brand.primaryColor ?? themeVars["--accent"] ?? "#1e3a5f";
|
|
201
|
+
const creamColor = "#faf9f4";
|
|
202
|
+
const bandTextColor = themeMode === "dark" ? "#ffffff" : "#111111";
|
|
203
|
+
return { bandColor, accentColor, creamColor, bandTextColor, themeMode };
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
BookendFooter,
|
|
207
|
+
BookendHeader,
|
|
208
|
+
LineCardHeader,
|
|
209
|
+
resolveBookendTheme
|
|
210
|
+
};
|
|
211
|
+
//# sourceMappingURL=react-pdf.js.map
|