import * as React from 'react'; import { Document, Page, View, Text, StyleSheet, Font, Link } from '@react-pdf/renderer'; // Standard fonts like Helvetica are built-in to PDF and don't require registration // unless we want to use specific TTF files. Using built-in Helvetica for maximum stability. // ─── Brand Tokens (matching datasheet) ────────────────────────────────── const C = { navy: '#001a4d', navyDeep: '#000d26', accent: '#82ed20', white: '#FFFFFF', offWhite: '#f8f9fa', gray100: '#f3f4f6', gray200: '#e5e7eb', gray300: '#d1d5db', gray400: '#9ca3af', gray600: '#4b5563', gray900: '#111827', }; const MARGIN = 72; const styles = StyleSheet.create({ page: { color: C.gray900, lineHeight: 1.5, backgroundColor: C.white, paddingTop: 0, paddingBottom: 100, fontFamily: 'Helvetica', }, // Hero-style header hero: { backgroundColor: C.white, paddingTop: 24, paddingBottom: 20, paddingHorizontal: MARGIN, marginBottom: 20, position: 'relative', borderBottomWidth: 1, borderBottomColor: C.gray200, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, logoText: { fontSize: 24, fontWeight: 700, color: C.navyDeep, letterSpacing: 1, textTransform: 'uppercase', }, docTitle: { fontSize: 10, fontWeight: 700, color: C.navy, letterSpacing: 2, textTransform: 'uppercase', }, productHero: { marginTop: 0, }, pageTitle: { fontSize: 24, fontWeight: 700, color: C.navyDeep, marginBottom: 0, textTransform: 'uppercase', letterSpacing: -0.5, }, accentBar: { width: 30, height: 3, backgroundColor: C.accent, marginTop: 8, borderRadius: 1.5, }, // Content Area content: { paddingHorizontal: MARGIN, }, // Lexical Elements paragraph: { fontSize: 10, color: C.gray600, lineHeight: 1.7, marginBottom: 12, }, heading1: { fontSize: 16, fontWeight: 700, color: C.navyDeep, marginTop: 20, marginBottom: 10, textTransform: 'uppercase', letterSpacing: 0.5, }, heading2: { fontSize: 12, fontWeight: 700, color: C.navyDeep, marginTop: 16, marginBottom: 8, }, heading3: { fontSize: 10, fontWeight: 700, color: C.navyDeep, marginTop: 12, marginBottom: 6, }, list: { marginBottom: 12, marginLeft: 8, }, listItem: { flexDirection: 'row', marginBottom: 4, }, listItemBullet: { width: 12, fontSize: 10, color: C.accent, fontWeight: 700, }, listItemContent: { flex: 1, fontSize: 10, color: C.gray600, lineHeight: 1.7, }, link: { color: C.accent, textDecoration: 'none', }, textBold: { fontWeight: 700, fontFamily: 'Helvetica-Bold', color: C.navyDeep, }, textItalic: { fontStyle: 'italic', }, // Footer — matches brochure style footer: { position: 'absolute', bottom: 40, left: MARGIN, right: MARGIN, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingTop: 24, borderTopWidth: 1, borderTopColor: C.gray200, }, footerText: { fontSize: 8, color: C.gray400, fontWeight: 500, textTransform: 'uppercase', letterSpacing: 1, }, footerBrand: { fontSize: 10, fontWeight: 700, color: C.navyDeep, textTransform: 'uppercase', letterSpacing: 1, }, }); // ─── Lexical to React-PDF Renderer ──────────────────────────────── const renderLexicalNode = (node: any, idx: number): React.ReactNode => { if (!node) return null; switch (node.type) { case 'text': { const format = node.format || 0; const isBold = (format & 1) !== 0; const isItalic = (format & 2) !== 0; let elementStyle: any = {}; if (isBold) elementStyle = { ...elementStyle, ...styles.textBold }; if (isItalic) elementStyle = { ...elementStyle, ...styles.textItalic }; return ( {node.text} ); } case 'paragraph': { return ( {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))} ); } case 'heading': { let hStyle = styles.heading3; if (node.tag === 'h1') hStyle = styles.heading1; if (node.tag === 'h2') hStyle = styles.heading2; return ( {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))} ); } case 'list': { return ( {node.children?.map((child: any, i: number) => { if (child.type === 'listitem') { return ( {node.listType === 'number' ? `${i + 1}.` : '•'} {child.children?.map((c: any, ci: number) => renderLexicalNode(c, ci))} ); } return renderLexicalNode(child, i); })} ); } case 'link': { const href = node.fields?.url || node.url || '#'; return ( {node.children?.map((child: any, i: number) => renderLexicalNode(child, i))} ); } case 'linebreak': { return {'\n'}; } // Ignore payload blocks recursively to avoid crashing case 'block': return null; default: if (node.children) { return ( {node.children.map((child: any, i: number) => renderLexicalNode(child, i))} ); } return null; } }; interface PDFPageProps { page: any; locale?: string; } export const PDFPage: React.FC = ({ page, locale = 'de' }) => { const dateStr = new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', { year: 'numeric', month: 'long', day: 'numeric', }); return ( {/* Hero Header */} KLZ {locale === 'en' ? 'Document' : 'Dokument'} {page.title} {page.content?.root?.children?.map((node: any, i: number) => renderLexicalNode(node, i), )} {/* Minimal footer */} KLZ CABLES {dateStr} ); };