Print System Patterns
Portal-based rendering patterns for creating new print views.
Overview
On Book Pro uses a portal-based print system that renders React components into a hidden print container, then triggers the browser's print dialog.
Architecture
┌─────────────────┐
│ Print Button │
│ (in toolbar) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ PrintPortal │──────► Hidden container in DOM
└────────┬────────┘
│
▼
┌─────────────────┐
│ Print Template │──────► React component with print styles
└────────┬────────┘
│
▼
┌─────────────────┐
│ window.print() │
└─────────────────┘Creating a Print View
Step 1: Create the Print Template
tsx
// src/features/print/components/MyReport.tsx
import { PrintHeader, PrintFooter, PrintTable } from '@/features/print/components';
interface MyReportProps {
data: MyData[];
projectName: string;
}
export const MyReport = ({ data, projectName }: MyReportProps) => (
<div className="print-container">
<PrintHeader title="My Report" projectName={projectName} />
<PrintTable>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{data.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.value}</td>
</tr>
))}
</tbody>
</PrintTable>
<PrintFooter />
</div>
);Step 2: Use the Print Portal Hook
tsx
import { usePrintPortal } from '@/features/print/usePrintPortal';
import { MyReport } from './MyReport';
export const MyFeatureTab = () => {
const { printContent, PrintPortal } = usePrintPortal();
const data = useAppStore((s) => s.myData);
const projectName = useAppStore((s) => s.meta.projectName);
const handlePrint = () => {
printContent(<MyReport data={data} projectName={projectName} />);
};
return (
<>
<Button leftIcon="print" onClick={handlePrint}>
Print Report
</Button>
<PrintPortal />
</>
);
};Tool Action Bar Integration (Recommended)
For tools that need print functionality, register the print action in the global toolbar:
tsx
// In your feature's main tab component
import { useToolActionBar } from '@/features/layout/hooks';
export const MyFeatureTab = () => {
const { printContent } = usePrintPortal();
// Register print action with global toolbar
useToolActionBar({
primaryAction: {
label: 'Print',
icon: 'print',
onClick: () => printContent(<MyReport {...props} />),
},
});
return (
// ... main content (no print button needed here)
);
};Print Styles
Use Tailwind's print utilities and custom print CSS:
css
/* Print-specific styles */
@media print {
/* Hide non-print elements */
.no-print {
display: none !important;
}
/* Force page breaks */
.page-break {
page-break-before: always;
}
/* Avoid breaking inside elements */
.avoid-break {
page-break-inside: avoid;
}
/* Full-width tables */
.print-table {
width: 100%;
border-collapse: collapse;
}
.print-table th,
.print-table td {
border: 1px solid #000;
padding: 4px 8px;
}
}Page Break Handling
For multi-page reports, control page breaks:
tsx
export const MultiPageReport = ({ sections }: Props) => (
<div className="print-container">
{sections.map((section, index) => (
<div
key={section.id}
className={index > 0 ? 'page-break' : ''}
>
<h2>{section.title}</h2>
<div className="avoid-break">
{/* Section content */}
</div>
</div>
))}
</div>
);Print Primitives
Use these reusable components for consistent print styling:
PrintHeader
tsx
<PrintHeader
title="Costume Bible"
projectName="Hamlet"
subtitle="Fall 2026 Production"
/>PrintFooter
tsx
<PrintFooter
showDate={true}
showPageNumbers={true}
/>PrintTable
tsx
<PrintTable striped>
<thead>...</thead>
<tbody>...</tbody>
</PrintTable>Existing Print Templates
| Report | Location | Description |
|---|---|---|
| Run Sheet | RunSheetPrint.tsx | Full production run sheet |
| Costume Bible | CostumeBible.tsx | Complete costume breakdown |
| Quick Change Plot | QuickChangePlot.tsx | Rapid costume changes |
| Budget Summary | BudgetReport.tsx | Financial overview |
| Rehearsal Schedule | SchedulePrint.tsx | Calendar export |
Best Practices
- Test at different paper sizes — Check A4 and Letter
- Use semantic tables — Better print rendering
- Avoid absolute positioning — Breaks across pages
- Include print date — Use
PrintFooterwithshowDate - Hide interactive elements — Use
no-printclass
Last updated: January 2026