import { View } from "@react-pdf/renderer"; import { Children, type ReactNode } from "react"; import { colsDetailed, FILL, pinned, type ColSpec } from "../engine/columns"; import { AvailableWidthProvider, useAvailableWidth } from "./AvailableWidth"; import { pdfFlags, type PdfFlagsInput } from "./pdfProps"; export interface RowProps extends PdfFlagsInput { /** Copied into the layout tree (audits find rows by it). */ id?: string; /** One spec per child, in order. */ widths: ColSpec[]; gap?: number; /** Stack the children as a column when the row is narrower than this. */ stackBelow?: number; /** Cross-axis alignment of the cells; never flex-end around multi-line text. */ align?: "flex-start" | "center" | "stretch"; /** Overrides the width from AvailableWidth. */ width?: number; style?: Record; children?: ReactNode; } /** * A flex row, pinned to its total width (a row inside a flex-start parent would otherwise shrink to its content and leave its fill cell empty), whose children each sit in a cell of explicit width: fixed and fractional cells are pinned * (width = minWidth = maxWidth) and the fill cell uses {flexGrow: 1, flexBasis: 0}. Descendants read the * cell's content width through useAvailableWidth(). When a column falls under its `min` (or the row is * narrower than `stackBelow`), the children are stacked at full width instead. */ export function Row({ id, widths, gap = 0, stackBelow, align = "flex-start", width, style, children, fixed, wrap, minPresenceAhead }: RowProps) { const inherited = useAvailableWidth(); const total = width ?? inherited; const kids = Children.toArray(children); if (kids.length !== widths.length) throw new Error(`Row: ${kids.length} children for ${widths.length} column specs`); const { widths: w, ok } = colsDetailed(total, widths, gap); const stacked = !ok || (stackBelow !== undefined && total < stackBelow); const flags = { ...(id !== undefined ? { id } : {}), ...pdfFlags({ fixed, wrap, minPresenceAhead }) }; if (stacked) { return ( {kids.map((child, i) => ( 0 ? { marginTop: gap } : {}) } as never}> {child} ))} ); } return ( {kids.map((child, i) => { const isFill = "fill" in widths[i]; const box = isFill ? FILL : pinned(w[i]); return ( 0 && gap ? { marginLeft: gap } : {}) } as never} > {child} ); })} ); } export interface CellProps { /** Padding in pt: one number for all sides, or per side. */ padding?: number | { top?: number; right?: number; bottom?: number; left?: number }; borderLeft?: number; borderRight?: number; style?: Record; children?: ReactNode; } /** * A padded/bordered box inside a Row column. It fills the column's width and republishes the content * width (column width minus its own padding and border) to its descendants. */ export function Cell({ padding = 0, borderLeft = 0, borderRight = 0, style, children }: CellProps) { const full = useAvailableWidth(); const p = typeof padding === "number" ? { top: padding, right: padding, bottom: padding, left: padding } : padding; const inner = Math.max(0, full - (p.left ?? 0) - (p.right ?? 0) - borderLeft - borderRight); return ( {children} ); }