Skip to content

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 />
        </>
    );
};

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)
    );
};

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>
);

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

ReportLocationDescription
Run SheetRunSheetPrint.tsxFull production run sheet
Costume BibleCostumeBible.tsxComplete costume breakdown
Quick Change PlotQuickChangePlot.tsxRapid costume changes
Budget SummaryBudgetReport.tsxFinancial overview
Rehearsal ScheduleSchedulePrint.tsxCalendar export

Best Practices

  1. Test at different paper sizes — Check A4 and Letter
  2. Use semantic tables — Better print rendering
  3. Avoid absolute positioning — Breaks across pages
  4. Include print date — Use PrintFooter with showDate
  5. Hide interactive elements — Use no-print class

Last updated: January 2026