Skip to content

Commit

Permalink
Feat: add <Table.Cell> component (#683)
Browse files Browse the repository at this point in the history
* chore: table header flag

* feat: add table header preview

* chore: table cell node and props

* chore: table cell normalizer

* chore: preview table with table cell rows

* chore: table sub node cell

* chore: table cell normalizer as text

* chore: format files

* fix: add table as node

* feat: add table.cell to pages

* feat: support table.cell props for render

* chore: table.cell props for preview

* chore: format files

* feat: add table cell margin support

* feat: table cell margin preview

* chore: format files

* style: improve table sample

* chore: fix comments
  • Loading branch information
juanesquintero authored Aug 21, 2023
1 parent daefa9d commit 65def15
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 56 deletions.
18 changes: 14 additions & 4 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,23 @@ const code = `ReactPPTX.render(
<Slide>
<Table
rows={[
["foo", <Text style={{ align: "right", backgroundColor: '#404040' }}>bar</Text>],
[<Table.Cell
colSpan={2}
style={{
align: "center",
backgroundColor: '#115599',
color: 'white'
}}>
Title
</Table.Cell>],
["foo", <Table.Cell style={{ align: "right", backgroundColor: '#404040' }}>bar</Table.Cell>],
[
<Text style={{ verticalAlign: "bottom" }}>
<Table.Cell style={{ verticalAlign: "bottom" }}>
what about a{" "}
<Text.Link url="https://www.youtube.com/watch?v=6IqKEeRS90A">
link
</Text.Link>
</Text>,
</Table.Cell>,
"xyz",
],
]}
Expand All @@ -186,8 +195,9 @@ const code = `ReactPPTX.render(
y: "10%",
w: "80%",
h: "80%",
borderWidth: 10,
borderWidth: 4,
borderColor: "#ff0000",
margin: 20,
}}
/>
</Slide>
Expand Down
35 changes: 28 additions & 7 deletions src/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export enum NodeTypes {
SLIDE = "slide",
MASTER_SLIDE = "master-slide",
IMAGE = "image",
TABLE = "table",
TABLE_CELL = "table-cell",
PRESENTATION = "presentation",
}

Expand Down Expand Up @@ -145,20 +145,39 @@ export const isShape = (
return el.type === NodeTypes.SHAPE;
};

export type TableCellProps = TextProps & {
colSpan?: number;
rowSpan?: number;
};
export const TableCell: React.FC<TableCellProps> =
NodeTypes.TABLE_CELL as unknown as React.FC;
export const isTableCell = (
el: React.ReactElement
): el is React.ReactElement<TableCellProps> => {
return el.type === NodeTypes.TABLE_CELL;
};

export type TableProps = VisualBaseProps & {
rows: Array<Array<string | React.ReactElement<TextProps>>>;
rows: Array<Array<string | React.ReactElement<TableCellProps>>>;
style?: {
borderWidth?: number;
borderColor?: string;
width?: number;
margin?: number;
};
};
export const Table: React.FC<TableProps> =
NodeTypes.TABLE as unknown as React.FC;

const TableFn: React.FC<TableProps> = () => null;
TableFn.prototype.isPptxTableElement = true;
TableFn.prototype.Cell = TableCell;
export const Table = Object.assign(TableFn, {
Cell: TableCell,
});
(Table.prototype as any).isPptxTableElement = true;
export const isTable = (
el: React.ReactElement
): el is React.ReactElement<TableProps> => {
return el.type === NodeTypes.TABLE;
): el is React.FunctionComponentElement<TableProps> => {
return el.type instanceof Function && el.type.prototype?.isPptxTableElement;
};

export type LineProps = {
Expand Down Expand Up @@ -216,4 +235,6 @@ export const Presentation: React.FC<PresentationProps> =
NodeTypes.PRESENTATION as unknown as React.FC;

export const isReactPPTXComponent = (node: React.ReactElement): boolean =>
Object.values(NodeTypes).includes(node.type as NodeTypes) || isText(node);
Object.values(NodeTypes).includes(node.type as NodeTypes) ||
isText(node) ||
isTable(node);
65 changes: 46 additions & 19 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isLine,
isShape,
isTable,
isTableCell,
isText,
isTextBullet,
isTextLink,
Expand Down Expand Up @@ -114,13 +115,19 @@ export type InternalShape = ObjectBase & {
borderWidth: number | null;
};
};
export type InternalTableStyle = {
borderColor: HexColor | null;
borderWidth: number | null;
margin: number | null;
};
export type InternalTableCell = InternalText & {
colSpan?: number;
rowSpan?: number;
};
export type InternalTable = ObjectBase & {
kind: "table";
rows: Array<Array<InternalText>>;
style: {
borderColor: HexColor | null;
borderWidth: number | null;
};
rows: Array<Array<InternalTableCell>>;
style: InternalTableStyle;
};
export type InternalLine = {
kind: "line";
Expand All @@ -139,6 +146,7 @@ export type InternalSlideObject =
| InternalImage
| InternalShape
| InternalTable
| InternalTableCell
| InternalLine;

export type InternalImageSrc =
Expand Down Expand Up @@ -181,6 +189,7 @@ export type InternalPresentation = {
export const normalizeHexColor = (colorString: string): HexColor => {
return Color(colorString).hex().substring(1).toUpperCase();
};

export const normalizeHexOrComplexColor = (
colorString: string
): HexColor | ComplexColor => {
Expand Down Expand Up @@ -298,7 +307,28 @@ export const normalizeText = (t: TextChild): InternalTextPart[] => {
}
};

const normalizeTextType = (
node: React.ReactElement,
normalizedCoordinates: Record<string, `${number}%` | number>
) => {
const style = node.props.style;
return {
text:
node.props.children !== undefined
? normalizeText(node.props.children)
: [],
style: {
...style,
...normalizedCoordinates,
color: style.color ? normalizeHexColor(style.color) : null,
fontFace: style.fontFace ?? DEFAULT_FONT_FACE,
fontSize: style.fontSize ?? DEFAULT_FONT_SIZE,
},
};
};

const PERCENTAGE_REGEXP = /^\d+%$/;

export const normalizeCoordinate = (
x: string | number | null | undefined,
_default: number
Expand Down Expand Up @@ -348,20 +378,16 @@ const normalizeSlideObject = (
};

if (isText(node)) {
const style = node.props.style;
return {
kind: "text",
text:
node.props.children !== undefined
? normalizeText(node.props.children)
: [],
style: {
...style,
...normalizedCoordinates,
color: style.color ? normalizeHexColor(style.color) : null,
fontFace: style.fontFace ?? DEFAULT_FONT_FACE,
fontSize: style.fontSize ?? DEFAULT_FONT_SIZE,
},
...normalizeTextType(node, normalizedCoordinates),
};
} else if (isTableCell(node)) {
return {
kind: "text",
...normalizeTextType(node, normalizedCoordinates),
colSpan: node.props.colSpan,
rowSpan: node.props.rowSpan,
};
} else if (isImage(node)) {
return {
Expand Down Expand Up @@ -392,7 +418,7 @@ const normalizeSlideObject = (
},
};
} else if (isTable(node)) {
const normalized: InternalText[][] = node.props.rows.map((row) =>
const normalized: InternalTableCell[][] = node.props.rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return {
Expand All @@ -401,7 +427,7 @@ const normalizeSlideObject = (
style: { x: 0, y: 0, w: 0, h: 0, color: null },
};
} else {
return normalizeSlideObject(cell) as InternalText;
return normalizeSlideObject(cell) as InternalTableCell;
}
})
);
Expand All @@ -414,6 +440,7 @@ const normalizeSlideObject = (
? normalizeHexColor(node.props.style.borderColor)
: null,
borderWidth: node.props.style.borderWidth ?? null,
margin: node.props.style.margin ?? null,
},
};
} else {
Expand Down
25 changes: 17 additions & 8 deletions src/preview/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
InternalShape,
InternalSlide,
InternalSlideObject,
InternalTableCell,
InternalTableStyle,
InternalTextPart,
InternalTextPartBaseStyle,
normalizeJsx,
Expand All @@ -26,6 +28,13 @@ const normalizedColorToCSS = (color: HexColor | ComplexColor) => {
}
};

const normalizeBorderToCSS = (style: InternalTableStyle) =>
`${style.borderWidth ?? 0}px solid ${
style.borderColor
? normalizedColorToCSS(style.borderColor)
: undefined ?? "transparent"
}`;

const SlideObjectShape = ({ shape }: { shape: InternalShape }) => {
const baseStyle = {
width: "100%",
Expand Down Expand Up @@ -363,17 +372,14 @@ const SlideObjectPreview = ({
style={{
width: "100%",
height: "100%",
border: `${object.style.borderWidth ?? 0}px solid ${
object.style.borderColor
? normalizedColorToCSS(object.style.borderColor)
: undefined ?? "transparent"
}`,
border: normalizeBorderToCSS(object.style),
borderCollapse: "collapse",
}}
>
<tbody>
{object.rows.map((row, i) => (
{object.rows.map((row: InternalTableCell[], i: number) => (
<tr key={i}>
{row.map((cell, i) => {
{row.map((cell: InternalTableCell) => {
return (
<td
key={i}
Expand All @@ -383,9 +389,12 @@ const SlideObjectPreview = ({
dimensions,
slideWidth
),
border: normalizeBorderToCSS(object.style),
textAlign: cell.style.align,
verticalAlign: cell.style.verticalAlign,
padding: object.style.margin ?? undefined,
}}
colSpan={cell.colSpan}
>
<TextPreview
parts={cell.text}
Expand All @@ -402,7 +411,7 @@ const SlideObjectPreview = ({
</tbody>
</table>
) : (
<SlideObjectShape shape={object} />
<SlideObjectShape shape={object as InternalShape} />
)}
</div>
);
Expand Down
41 changes: 23 additions & 18 deletions src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
InternalSlide,
InternalSlideObject,
InternalTextPart,
InternalTableCell,
normalizeJsx,
} from "./normalizer";

Expand Down Expand Up @@ -150,31 +151,35 @@ const renderSlideObject = async (
}
} else if (object.kind === "table") {
const style = object.style;

const mapped: PptxGenJs.TableRow[] = object.rows.map((row) =>
row.map((cell) => {
const { color, verticalAlign, backgroundColor, ...style } = cell.style;
// this is super weird, but works?
return {
text: renderTextParts(cell.text),
options: {
...style,
fill: backgroundColor
? normalizedColorToPptxgenShapeFill(backgroundColor)
: undefined,
color: color ?? undefined,
valign: verticalAlign,
breakLine: true,
},
};
})
const mapped: PptxGenJs.TableRow[] = object.rows.map(
(row: InternalTableCell[]) =>
row.map((cell: InternalTableCell) => {
const { color, verticalAlign, backgroundColor, ...style } =
cell.style;
// this is super weird, but works?
return {
text: renderTextParts(cell.text),
options: {
...style,
fill: backgroundColor
? normalizedColorToPptxgenShapeFill(backgroundColor)
: undefined,
color: color ?? undefined,
valign: verticalAlign ?? "middle",
breakLine: true,
colspan: cell.colSpan,
rowspan: cell.rowSpan,
},
};
})
);

slide.addTable(mapped, {
x,
y,
w,
h,
margin: style.margin ?? undefined,
border: {
type: "solid",
pt: style.borderWidth ?? undefined,
Expand Down

0 comments on commit 65def15

Please sign in to comment.