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>
43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
import { StyleSheet, Text, View } from '@react-pdf/renderer';
|
|
|
|
import { PDF_TOKENS } from './tokens';
|
|
|
|
export type BadgeTone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger';
|
|
|
|
const toneStyles: Record<BadgeTone, { background: string; foreground: string }> = {
|
|
neutral: { background: PDF_TOKENS.colors.border, foreground: PDF_TOKENS.colors.text },
|
|
accent: { background: PDF_TOKENS.colors.accentBlue, foreground: '#ffffff' },
|
|
success: { background: PDF_TOKENS.colors.success, foreground: '#ffffff' },
|
|
warning: { background: PDF_TOKENS.colors.warning, foreground: '#ffffff' },
|
|
danger: { background: PDF_TOKENS.colors.danger, foreground: '#ffffff' },
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
pill: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 3,
|
|
borderRadius: 999,
|
|
alignSelf: 'flex-start',
|
|
},
|
|
label: {
|
|
fontFamily: PDF_TOKENS.fonts.sansBold,
|
|
fontSize: PDF_TOKENS.sizes.caption,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
});
|
|
|
|
export interface BadgeProps {
|
|
text: string;
|
|
tone?: BadgeTone;
|
|
}
|
|
|
|
export function Badge({ text, tone = 'neutral' }: BadgeProps) {
|
|
const t = toneStyles[tone];
|
|
return (
|
|
<View style={[styles.pill, { backgroundColor: t.background }]}>
|
|
<Text style={[styles.label, { color: t.foreground }]}>{text}</Text>
|
|
</View>
|
|
);
|
|
}
|