feat(pdf): brand kit foundation for @react-pdf/renderer
Phase 1 / commit 1 of 14 — installs deps and lays down the brand-kit
primitives used by every internal-only PDF. No callers wired yet.
Adds:
@react-pdf/renderer 4.5.1 one engine for internal exports
unpdf 1.6.2 reserved for berth-PDF parser tier-2
react-image-crop 11.0.10 admin logo crop UI (commit 2)
svgo 4.0.1 SVG sanitization on logo upload (commit 2)
brand-kit/
tokens.ts single source of truth for colors/fonts/spacing
logo.ts resolvePortLogo() — cached, soft-fallback
DocumentShell <Document><Page> + fixed Header + fixed Footer
Header dark band, logo slot (letterboxed) + text fallback
Footer page N of M + generated-at + confidential tag
Section heading + bottom border
KeyValueGrid 2-col (default) or stacked label/value
DataTable zebra rows + sticky header + totals row + empty state
Badge 5 tone pills
charts/
BarChart pure SVG, 4-tick y-axis, optional value labels
LineChart pure SVG, line + markers + grid
PieChart pure SVG, donut-or-pie + side legend
FunnelChart pure SVG, slope-cut slices for pipeline stages
render.ts renderToBuffer + renderToStream wrappers, typed
svg-primitives.tsx <SvgLabel> wraps react-pdf SVG <Text> to bridge
missing TS declarations for fontSize/fontFamily
Smoke test renders a kitchen-sink Document including every primitive
and every chart, plus an empty-data path. 1293+4 vitest tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
50
src/lib/pdf/brand-kit/svg-primitives.tsx
Normal file
50
src/lib/pdf/brand-kit/svg-primitives.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Text as PdfText } from '@react-pdf/renderer';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { PDF_TOKENS } from './tokens';
|
||||
|
||||
/**
|
||||
* Brand-kit wrapper around @react-pdf/renderer's SVG `<Text>`. Exists because
|
||||
* the renderer accepts `fontSize`/`fontFamily`/`fontWeight` as presentation
|
||||
* attributes at runtime but the published types only declare the SVG-spec
|
||||
* subset. Wrapping here keeps the casts isolated to one file.
|
||||
*/
|
||||
export interface SvgLabelProps {
|
||||
x: number;
|
||||
y: number;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: string | number;
|
||||
fill?: string;
|
||||
textAnchor?: 'start' | 'middle' | 'end';
|
||||
transform?: string;
|
||||
opacity?: number | string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function SvgLabel({
|
||||
x,
|
||||
y,
|
||||
fontSize = PDF_TOKENS.sizes.caption,
|
||||
fontFamily = PDF_TOKENS.fonts.sans,
|
||||
fontWeight,
|
||||
fill = PDF_TOKENS.colors.text,
|
||||
textAnchor = 'start',
|
||||
transform,
|
||||
opacity,
|
||||
children,
|
||||
}: SvgLabelProps) {
|
||||
// Runtime accepts these as presentation attrs; types omit them. Cast scoped here.
|
||||
const props = {
|
||||
x,
|
||||
y,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
fill,
|
||||
textAnchor,
|
||||
transform,
|
||||
opacity,
|
||||
} as unknown as { x: number; y: number; fill: string };
|
||||
return <PdfText {...props}>{children}</PdfText>;
|
||||
}
|
||||
Reference in New Issue
Block a user