Classic now renders through buildRenderModel, computeFrame, the template and renderCore, with S.No and HSN/SAC columns, GST slots only through checked Slot components, taxable value, reverse-charge line, cancelled tag, continuation band and Page n of N. Letter absorbs its height deficit in elastic gaps. A harness runs 15 fixtures on A4 and Letter plus an item sweep and asserts zero audit errors, repeated headers, no split blocks, rupee extraction and deterministic output. The legacy InvoiceDocument is removed.
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
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<string, unknown>;
|
|
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 (
|
|
<View style={{ flexDirection: "column", alignItems: "flex-start", ...pinned(total), ...style } as never} {...flags}>
|
|
{kids.map((child, i) => (
|
|
<View key={i} style={{ ...pinned(total), alignItems: "flex-start", ...(gap && i > 0 ? { marginTop: gap } : {}) } as never}>
|
|
<AvailableWidthProvider width={total}>{child}</AvailableWidthProvider>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={{ flexDirection: "row", alignItems: align, ...pinned(total), ...style } as never} {...flags}>
|
|
{kids.map((child, i) => {
|
|
const isFill = "fill" in widths[i];
|
|
const box = isFill ? FILL : pinned(w[i]);
|
|
return (
|
|
<View
|
|
key={i}
|
|
style={{ ...box, alignItems: "flex-start", ...(i > 0 && gap ? { marginLeft: gap } : {}) } as never}
|
|
>
|
|
<AvailableWidthProvider width={w[i]}>{child}</AvailableWidthProvider>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
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<string, unknown>;
|
|
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 (
|
|
<View
|
|
style={
|
|
{
|
|
...pinned(full),
|
|
alignItems: "flex-start",
|
|
paddingTop: p.top ?? 0,
|
|
paddingRight: p.right ?? 0,
|
|
paddingBottom: p.bottom ?? 0,
|
|
paddingLeft: p.left ?? 0,
|
|
...(borderLeft ? { borderLeftWidth: borderLeft } : {}),
|
|
...(borderRight ? { borderRightWidth: borderRight } : {}),
|
|
...style,
|
|
} as never
|
|
}
|
|
>
|
|
<AvailableWidthProvider width={inner}>{children}</AvailableWidthProvider>
|
|
</View>
|
|
);
|
|
}
|