Skip to content

Commit ee7dee3

Browse files
authored
fix(table,list): fixed-width columns collapse to zero & list rows overlap on wrapped text (#110, #103) (#112)
1 parent 14d1d5c commit ee7dee3

11 files changed

Lines changed: 143 additions & 21 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'pdfx-cli': minor
3+
---
4+
5+
Fix `DataTable`/`Table` fixed-width columns and `PdfList` overlapping rows on wrapped text.
6+
7+
**DataTable / Table — fixed-width columns collapsing to zero (#110)**
8+
9+
Setting a `width` prop on a `TableCell` or `DataTable` column had no effect — the column always rendered at zero width. Root cause: `flex: 0` in the `cellFixed` style is shorthand for `flexGrow:0 + flexShrink:0 + flexBasis:0`. The `flexBasis:0` overrode the explicit `width` in Yoga layout. Fixed by replacing `flex: 0` with the individual `flexGrow: 0` / `flexShrink: 0` properties so `width` is respected.
10+
11+
All accepted formats now work: numbers (pt), percentage strings (`"25%"`), and pixel strings (`"50px"`).
12+
13+
**PdfList — overlapping rows when item text wraps to multiple lines (#103)**
14+
15+
`PdfList` with any variant could render overlapping rows when item text wrapped to 2+ lines. Root cause: `flex: 1` on `Text` nodes sets `flexBasis:0`, causing Yoga to under-estimate multi-line text height — the next row was laid out too high. Fixed by moving `flex: 1` off `Text` and onto a wrapping `View` (`itemTextWrap`) across all variants: `bullet`, `numbered`, `checklist`, `icon`, and `multi-level`.
16+
17+
**Existing installs:** re-run `pdfx add table`, `pdfx add data-table`, and `pdfx add list` to pick up these fixes.

apps/www/public/r/list.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

apps/www/public/r/table.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
{
1414
"path": "components/pdfx/table/pdfx-table.styles.ts",
15-
"content": "import { StyleSheet } from '@react-pdf/renderer';\nimport { usePdfxTheme } from '../lib/pdfx-theme-context';\ntype PdfxTheme = ReturnType<typeof usePdfxTheme>;\n\n/**\n * Creates all table styles derived from the active theme.\n * @param t - The resolved PdfxTheme instance.\n */\nexport function createTableStyles(t: PdfxTheme) {\n const { spacing, borderRadius, fontWeights, typography } = t.primitives;\n const borderColor = t.colors.border;\n\n const hairline = 0.5;\n const rule = 1;\n const thick = 1.5;\n\n const cellPadV = spacing[2] - 2;\n const cellPadH = spacing[2] + 2;\n const cellPadVCompact = spacing[0.5];\n const cellPadHCompact = spacing[2];\n\n const rowDivider = {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid' as const,\n };\n\n return StyleSheet.create({\n table: {\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n marginBottom: t.spacing.componentGap,\n },\n\n tableGrid: {\n borderWidth: thick,\n borderColor: borderColor,\n borderStyle: 'solid',\n borderTopLeftRadius: borderRadius.md,\n borderTopRightRadius: borderRadius.md,\n borderBottomLeftRadius: borderRadius.md,\n borderBottomRightRadius: borderRadius.md,\n overflow: 'hidden' as const,\n },\n\n tableLine: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableMinimal: {\n paddingVertical: spacing[2],\n },\n\n tableStriped: {\n borderTopWidth: hairline,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableCompact: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableBordered: {\n borderWidth: rule,\n borderColor: borderColor,\n borderStyle: 'solid',\n borderTopLeftRadius: borderRadius.sm,\n borderTopRightRadius: borderRadius.sm,\n borderBottomLeftRadius: borderRadius.sm,\n borderBottomRightRadius: borderRadius.sm,\n overflow: 'hidden' as const,\n },\n\n tablePrimaryHeader: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n row: {\n flexDirection: 'row',\n display: 'flex',\n },\n\n rowGrid: rowDivider,\n rowLine: rowDivider,\n rowMinimal: rowDivider,\n rowStriped: {},\n rowCompact: rowDivider,\n rowBordered: rowDivider,\n rowPrimaryHeader: rowDivider,\n\n rowHeaderGrid: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderLine: {\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderMinimal: {\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderStriped: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderCompact: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderBordered: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderPrimaryHeader: {\n backgroundColor: t.colors.primary,\n },\n\n rowFooter: {\n borderTopWidth: rule,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n },\n rowFooterStriped: {\n borderTopWidth: rule,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n backgroundColor: t.colors.muted,\n },\n\n rowStripe: {\n backgroundColor: t.colors.muted,\n },\n\n cell: {\n flex: 1,\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n justifyContent: 'center',\n },\n cellFixed: {\n flex: 0,\n flexGrow: 0,\n flexShrink: 0,\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n justifyContent: 'center',\n },\n\n cellMinimal: {\n paddingVertical: spacing[1] + 1,\n paddingHorizontal: spacing[2] - 2,\n },\n cellStriped: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n cellCompact: {\n paddingVertical: cellPadVCompact,\n paddingHorizontal: cellPadHCompact,\n },\n cellBordered: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n cellPrimaryHeader: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n\n cellGridBorder: {\n borderRightWidth: hairline,\n borderRightColor: borderColor,\n borderRightStyle: 'solid',\n },\n cellBorderedBorder: {\n borderRightWidth: hairline,\n borderRightColor: borderColor,\n borderRightStyle: 'solid',\n },\n\n cellText: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n },\n\n cellTextHeaderGrid: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderLine: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderMinimal: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.mutedForeground,\n fontWeight: fontWeights.medium,\n },\n cellTextHeaderStriped: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderCompact: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n textTransform: 'uppercase',\n letterSpacing: 0.6,\n },\n cellTextHeaderBordered: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.bold,\n },\n cellTextHeaderPrimaryHeader: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.primaryForeground,\n fontWeight: fontWeights.semibold,\n textTransform: 'uppercase',\n letterSpacing: 0.6,\n },\n\n cellTextFooter: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n\n cellTextCompact: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.foreground,\n },\n });\n}\n",
15+
"content": "import { StyleSheet } from '@react-pdf/renderer';\nimport { usePdfxTheme } from '../lib/pdfx-theme-context';\ntype PdfxTheme = ReturnType<typeof usePdfxTheme>;\n\n/**\n * Creates all table styles derived from the active theme.\n * @param t - The resolved PdfxTheme instance.\n */\nexport function createTableStyles(t: PdfxTheme) {\n const { spacing, borderRadius, fontWeights, typography } = t.primitives;\n const borderColor = t.colors.border;\n\n const hairline = 0.5;\n const rule = 1;\n const thick = 1.5;\n\n const cellPadV = spacing[2] - 2;\n const cellPadH = spacing[2] + 2;\n const cellPadVCompact = spacing[0.5];\n const cellPadHCompact = spacing[2];\n\n const rowDivider = {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid' as const,\n };\n\n return StyleSheet.create({\n table: {\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n marginBottom: t.spacing.componentGap,\n },\n\n tableGrid: {\n borderWidth: thick,\n borderColor: borderColor,\n borderStyle: 'solid',\n borderTopLeftRadius: borderRadius.md,\n borderTopRightRadius: borderRadius.md,\n borderBottomLeftRadius: borderRadius.md,\n borderBottomRightRadius: borderRadius.md,\n overflow: 'hidden' as const,\n },\n\n tableLine: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableMinimal: {\n paddingVertical: spacing[2],\n },\n\n tableStriped: {\n borderTopWidth: hairline,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableCompact: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n tableBordered: {\n borderWidth: rule,\n borderColor: borderColor,\n borderStyle: 'solid',\n borderTopLeftRadius: borderRadius.sm,\n borderTopRightRadius: borderRadius.sm,\n borderBottomLeftRadius: borderRadius.sm,\n borderBottomRightRadius: borderRadius.sm,\n overflow: 'hidden' as const,\n },\n\n tablePrimaryHeader: {\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n\n row: {\n flexDirection: 'row',\n display: 'flex',\n },\n\n rowGrid: rowDivider,\n rowLine: rowDivider,\n rowMinimal: rowDivider,\n rowStriped: {},\n rowCompact: rowDivider,\n rowBordered: rowDivider,\n rowPrimaryHeader: rowDivider,\n\n rowHeaderGrid: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderLine: {\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderMinimal: {\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderStriped: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderCompact: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: rule,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderBordered: {\n backgroundColor: t.colors.muted,\n borderBottomWidth: hairline,\n borderBottomColor: borderColor,\n borderBottomStyle: 'solid',\n },\n rowHeaderPrimaryHeader: {\n backgroundColor: t.colors.primary,\n },\n\n rowFooter: {\n borderTopWidth: rule,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n },\n rowFooterStriped: {\n borderTopWidth: rule,\n borderTopColor: borderColor,\n borderTopStyle: 'solid',\n backgroundColor: t.colors.muted,\n },\n\n rowStripe: {\n backgroundColor: t.colors.muted,\n },\n\n cell: {\n flex: 1,\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n justifyContent: 'center',\n },\n cellFixed: {\n flexGrow: 0,\n flexShrink: 0,\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n justifyContent: 'center',\n },\n\n cellMinimal: {\n paddingVertical: spacing[1] + 1,\n paddingHorizontal: spacing[2] - 2,\n },\n cellStriped: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n cellCompact: {\n paddingVertical: cellPadVCompact,\n paddingHorizontal: cellPadHCompact,\n },\n cellBordered: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n cellPrimaryHeader: {\n paddingVertical: cellPadV,\n paddingHorizontal: cellPadH,\n },\n\n cellGridBorder: {\n borderRightWidth: hairline,\n borderRightColor: borderColor,\n borderRightStyle: 'solid',\n },\n cellBorderedBorder: {\n borderRightWidth: hairline,\n borderRightColor: borderColor,\n borderRightStyle: 'solid',\n },\n\n cellText: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n },\n\n cellTextHeaderGrid: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderLine: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderMinimal: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.mutedForeground,\n fontWeight: fontWeights.medium,\n },\n cellTextHeaderStriped: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n cellTextHeaderCompact: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n textTransform: 'uppercase',\n letterSpacing: 0.6,\n },\n cellTextHeaderBordered: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.bold,\n },\n cellTextHeaderPrimaryHeader: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.primaryForeground,\n fontWeight: fontWeights.semibold,\n textTransform: 'uppercase',\n letterSpacing: 0.6,\n },\n\n cellTextFooter: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: t.typography.body.fontSize,\n lineHeight: 1.2,\n color: t.colors.foreground,\n fontWeight: fontWeights.semibold,\n },\n\n cellTextCompact: {\n fontFamily: t.typography.body.fontFamily,\n fontSize: typography.xs,\n lineHeight: 1.2,\n color: t.colors.foreground,\n },\n });\n}\n",
1616
"type": "registry:component"
1717
},
1818
{

apps/www/src/constants/data-table.constant.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export const dataTableProps = [
9898
name: 'Column.width',
9999
type: 'string | number',
100100
defaultValue: '-',
101-
description: 'Column width. Use % for proportional (e.g. "25%") or number for fixed pts.',
101+
description:
102+
'Fixed column width. Accepts a number (pt) or any CSS-like string ("50px", "20%"). Fixed-width columns do not grow; remaining space is distributed among flex columns.',
102103
},
103104
{
104105
name: 'Column.render',

apps/www/src/constants/table.constant.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export const tableProps = [
9494
name: 'TableCell.width',
9595
type: 'string | number',
9696
defaultValue: '-',
97-
description: "Column width ('50%' or 200). Omit for equal flex.",
97+
description:
98+
"Fixed column width — number (pt) or string ('50px', '25%'). Fixed-width cells do not grow or shrink; omit to distribute space equally with flex.",
9899
},
99100
// DataTable props
100101
{

apps/www/src/registry/components/list/list.styles.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,26 @@ export function createListStyles(t: PdfxTheme) {
106106
fontWeight: fontWeights.bold,
107107
},
108108
itemText: {
109-
flex: 1,
109+
// No flex here — Text nodes with flex shorthand get flexBasis:0, which causes
110+
// Yoga to under-measure multi-line text height → overlapping rows.
111+
// flex:1 must live on the wrapping View, not on the Text itself.
110112
fontFamily: t.typography.body.fontFamily,
111113
fontSize: t.typography.body.fontSize,
112114
lineHeight: t.typography.body.lineHeight,
113115
color: t.colors.foreground,
114116
},
115117
itemTextSub: {
116-
flex: 1,
118+
// Same: no flex — see itemText comment.
117119
fontFamily: t.typography.body.fontFamily,
118120
fontSize: t.typography.body.fontSize - 0.5,
119121
lineHeight: t.typography.body.lineHeight,
120122
color: t.colors.mutedForeground,
121123
},
124+
itemTextWrap: {
125+
// Companion to itemText / itemTextSub. Apply this to the View that wraps
126+
// the Text so flex expansion happens at the View level, not the Text level.
127+
flex: 1,
128+
},
122129
itemTextBold: {
123130
fontWeight: fontWeights.semibold,
124131
},

0 commit comments

Comments
 (0)