style: align PDF Page component with KLZ brand Design System
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Failing after 1m15s
Build & Deploy / 🏗️ Build (push) Has been skipped
Nightly QA / 🔗 Links & Deps (push) Successful in 3m32s
Nightly QA / 🎭 Lighthouse (push) Successful in 5m5s
Nightly QA / ♿ Accessibility (push) Successful in 5m28s
Nightly QA / 🔍 Static Analysis (push) Failing after 6m0s
Nightly QA / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-03-05 22:56:53 +01:00
parent d9334f558d
commit 6f80e72c1d

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Document, Page, View, Text, Image, StyleSheet, Font, Link } from '@react-pdf/renderer';
import { Document, Page, View, Text, StyleSheet, Font, Link } from '@react-pdf/renderer';
// Register fonts (using system fonts for now, can be customized)
Font.register({
@@ -10,120 +10,120 @@ Font.register({
],
});
// ─── Brand Tokens ────────────────────────────────────────
// ─── Brand Tokens (matching datasheet) ──────────────────────────────────
const C = {
navy: '#001a4d',
navyDeep: '#000d26',
green: '#4da612',
greenLight: '#e8f5d8',
white: '#FFFFFF',
offWhite: '#f8f9fa',
gray100: '#f3f4f6',
gray200: '#e5e7eb',
gray300: '#d1d5db',
gray400: '#9ca3af',
gray600: '#4b5563',
gray900: '#111827',
};
const MARGIN = 56;
const HEADER_H = 52;
const FOOTER_H = 48;
const BODY_TOP = HEADER_H + 20;
const styles = StyleSheet.create({
page: {
color: C.gray900,
lineHeight: 1.5,
backgroundColor: C.white,
paddingTop: BODY_TOP,
paddingBottom: FOOTER_H + 40,
paddingHorizontal: MARGIN,
paddingTop: 0,
paddingBottom: 80,
fontFamily: 'Helvetica',
},
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: HEADER_H,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-end',
// Hero-style header
hero: {
backgroundColor: C.white,
paddingTop: 24,
paddingBottom: 0,
paddingHorizontal: MARGIN,
paddingBottom: 12,
marginBottom: 20,
position: 'relative',
borderBottomWidth: 0,
},
logoText: {
fontSize: 16,
fontWeight: 700,
color: C.navy,
},
docTitleLabel: {
fontSize: 7,
fontWeight: 700,
color: C.gray400,
letterSpacing: 1.2,
textTransform: 'uppercase',
},
footer: {
position: 'absolute',
bottom: 20,
left: MARGIN,
right: MARGIN,
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderTopWidth: 0.5,
borderTopColor: C.gray200,
paddingTop: 8,
marginBottom: 16,
},
footerText: {
fontSize: 7,
color: C.gray400,
letterSpacing: 0.8,
logoText: {
fontSize: 22,
fontWeight: 700,
color: C.navyDeep,
letterSpacing: 2,
textTransform: 'uppercase',
},
docTitle: {
fontSize: 8,
fontWeight: 700,
color: C.green,
letterSpacing: 2,
textTransform: 'uppercase',
},
// Content Area
content: {
paddingHorizontal: MARGIN,
},
pageTitle: {
fontSize: 24,
fontWeight: 700,
color: C.navyDeep,
marginBottom: 16,
marginBottom: 8,
marginTop: 10,
textTransform: 'uppercase',
letterSpacing: -0.5,
},
accentBar: {
width: 40,
height: 3,
width: 30,
height: 2,
backgroundColor: C.green,
marginBottom: 24,
marginBottom: 20,
borderRadius: 1,
},
// Lexical Elements
paragraph: {
fontSize: 10,
color: C.gray600,
lineHeight: 1.6,
lineHeight: 1.7,
marginBottom: 12,
},
heading1: {
fontSize: 18,
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,
},
heading2: {
fontSize: 14,
fontWeight: 700,
color: C.navyDeep,
marginTop: 14,
marginBottom: 6,
},
heading3: {
fontSize: 12,
fontSize: 10,
fontWeight: 700,
color: C.navyDeep,
marginTop: 12,
marginBottom: 4,
marginBottom: 6,
},
list: {
marginBottom: 12,
@@ -137,12 +137,13 @@ const styles = StyleSheet.create({
width: 12,
fontSize: 10,
color: C.green,
fontWeight: 700,
},
listItemContent: {
flex: 1,
fontSize: 10,
color: C.gray600,
lineHeight: 1.6,
lineHeight: 1.7,
},
link: {
color: C.green,
@@ -154,7 +155,37 @@ const styles = StyleSheet.create({
color: C.navyDeep,
},
textItalic: {
fontStyle: 'italic', // Not actually working without proper font set, but we will fallback
fontStyle: 'italic',
},
// Footer — matches brochure style
footer: {
position: 'absolute',
bottom: 28,
left: MARGIN,
right: MARGIN,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 12,
borderTopWidth: 2,
borderTopColor: C.green,
},
footerText: {
fontSize: 7,
color: C.gray400,
fontWeight: 400,
textTransform: 'uppercase',
letterSpacing: 0.8,
},
footerBrand: {
fontSize: 9,
fontWeight: 700,
color: C.navyDeep,
textTransform: 'uppercase',
letterSpacing: 1.5,
},
});
@@ -232,11 +263,10 @@ const renderLexicalNode = (node: any, idx: number): React.ReactNode => {
}
case 'linebreak': {
// React-PDF doesn't handle `<br/>`, but a newline char usually works inside `<Text>`.
return <Text key={idx}>{'\n'}</Text>;
}
// Ignore payload blocks recursively to avoid crashing, as pages should mainly use rich text
// Ignore payload blocks recursively to avoid crashing
case 'block':
return null;
@@ -267,20 +297,30 @@ export const PDFPage: React.FC<PDFPageProps> = ({ page, locale = 'de' }) => {
return (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.header} fixed>
<Text style={styles.logoText}>KLZ</Text>
<Text style={styles.docTitleLabel}>{locale === 'en' ? 'Document' : 'Dokument'}</Text>
{/* Hero Header */}
<View style={styles.hero} fixed>
<View style={styles.header}>
<View>
<Text style={styles.logoText}>KLZ</Text>
</View>
<Text style={styles.docTitle}>{locale === 'en' ? 'Document' : 'Dokument'}</Text>
</View>
</View>
<Text style={styles.pageTitle}>{page.title}</Text>
<View style={styles.accentBar} />
<View style={styles.content}>
<Text style={styles.pageTitle}>{page.title}</Text>
<View style={styles.accentBar} />
<View>
{page.content?.root?.children?.map((node: any, i: number) => renderLexicalNode(node, i))}
<View>
{page.content?.root?.children?.map((node: any, i: number) =>
renderLexicalNode(node, i),
)}
</View>
</View>
{/* Minimal footer */}
<View style={styles.footer} fixed>
<Text style={styles.footerText}>KLZ CABLES</Text>
<Text style={styles.footerBrand}>KLZ CABLES</Text>
<Text style={styles.footerText}>{dateStr}</Text>
</View>
</Page>