Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PrismicTable component #219

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

levimykel
Copy link
Contributor

Resolves: DT-2553

Description

This PR adds a PrismicTable component and tests.

Checklist

  • A comprehensive Linear ticket, providing sufficient context and details to facilitate the review of the PR, is linked to the PR.
  • If my changes require tests, I added them.
  • If my changes affect backward compatibility, it has been discussed.
  • If my changes require an update to the CONTRIBUTING.md guide, I updated it.

Preview

N/A

How to QA 1

Footnotes

  1. Please use these labels when submitting a review:
    ❓ #ask: Ask a question.
    💡 #idea: Suggest an idea.
    ⚠️ #issue: Strongly suggest a change.
    🎉 #nice: Share a compliment.

Copy link
Member

@angeloashmore angeloashmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome to see this addition! I left some suggestions in the code review. In addition, I have some notes that aren't specific to lines of code:

Handing tables in rich text

Have we considered how tables in rich text content will be structured? The current rich text serialization process relies on providing node and children to its components (e.g. heading1's component receives the heading1 node via the node prop). I don't think we'll be handling table data in the same way, yet we want to expose customizing its subcomponents in the same way as other node types.

I ask because if we design how rich text will hold tables now, we can update the @prismicio/client rich text types and helpers to support tables. We can then use those types in @prismicio/react without piecing a custom version together within the React package.

Simplifying the implementation

I had some trouble following how the code was laid out, I think mainly because it uses a decent amount of abstraction via components.

Here's another version that you could use as inspiration. It separates components and tableComponents, but that design could be avoided if we address the rich text note I left above.

Full disclosure: I haven't tested it, so it might not output the correct HTML.

View code block
import { type ReactNode } from "react";
import { type TableField, isFilled } from "@prismicio/client";

import { PrismicRichText, type JSXMapSerializer } from "./PrismicRichText";

// Perhaps these could be exported from `@prismicio/client`.
// Not sure, would need to think about it further.
type TableFieldHead = NonNullable<TableField<"filled">["head"]>;
type TableFieldBody = TableField<"filled">["body"];
type TableFieldRow =
	| TableFieldHead["rows"][number]
	| TableFieldBody["rows"][number];
type TableFieldCell = TableFieldRow["cells"][number];
type TableFieldHeaderCell = Extract<TableFieldCell, { type: "header" }>;
type TableFieldDataCell = Extract<TableFieldCell, { type: "data" }>;

// This type could be removed if we support tables properly in `@prismicio/client`'s rich text types.
type TableComponents = {
	table: (props: {
		table: TableField<"filled">;
		children: ReactNode;
	}) => ReactNode;
	thead: (props: { head: TableFieldHead; children: ReactNode }) => ReactNode;
	tbody: (props: { body: TableFieldBody; children: ReactNode }) => ReactNode;
	tr: (props: { row: TableFieldRow; children: ReactNode }) => ReactNode;
	th: (props: { cell: TableFieldHeaderCell; children: ReactNode }) => ReactNode;
	td: (props: { cell: TableFieldDataCell; children: ReactNode }) => ReactNode;
};

const defaultTableComponents: TableComponents = {
	table: ({ children }) => <table>{children}</table>,
	thead: ({ children }) => <thead>{children}</thead>,
	tbody: ({ children }) => <tbody>{children}</tbody>,
	tr: ({ children }) => <tr>{children}</tr>,
	th: ({ children }) => <th>{children}</th>,
	td: ({ children }) => <td>{children}</td>,
};

type PrismicTableProps = {
	field: TableField | null | undefined;
	components?: JSXMapSerializer;
	tableComponents?: TableComponents;
	fallback?: ReactNode;
};

export function PrismicTable(props: PrismicTableProps) {
	const { field, components, tableComponents, fallback = null } = props;

	if (!isFilled.table(field)) {
		return fallback;
	}

	const {
		table: Table,
		thead: Thead,
		tbody: Tbody,
	} = { ...defaultTableComponents, ...tableComponents };

	return (
		<Table table={field}>
			{field.head ? (
				<Thead head={field.head}>
					{field.body.rows.map((row) => (
						<TableRow
							key={JSON.stringify(row)}
							row={row}
							components={components}
							tableComponents={tableComponents}
						/>
					))}
				</Thead>
			) : null}
			<Tbody body={field.body}>
				{field.body.rows.map((row) => (
					<TableRow
						key={JSON.stringify(row)}
						row={row}
						components={components}
						tableComponents={tableComponents}
					/>
				))}
			</Tbody>
		</Table>
	);
}

type TableRowProps = {
	row: TableFieldRow;
	components?: JSXMapSerializer;
	tableComponents?: TableComponents;
};

function TableRow(props: TableRowProps) {
	const { row, components, tableComponents } = props;

	const {
		tr: Tr,
		th: Th,
		td: Td,
	} = { ...defaultTableComponents, ...tableComponents };

	return (
		<Tr row={row} key={JSON.stringify(row)}>
			{row.cells.map((cell) =>
				cell.type === "header" ? (
					<Th cell={cell} key={JSON.stringify(cell)}>
						<PrismicRichText field={cell.content} components={components} />
					</Th>
				) : (
					<Td cell={cell} key={JSON.stringify(cell)}>
						<PrismicRichText field={cell.content} components={components} />
					</Td>
				),
			)}
		</Tr>
	);
}

components,
fallback,
}: PrismicTableProps) {
const serializer = createDefaultTableSerializer(components);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ #ask: I found createDefaultTableSerializer kind of confusing. Is it doing something more than this?

const serializer = { ...defaultComponents, ...components };

Where defaultComponents is defined like this:

const defaultComponents = {
	table: ({ children }) => <table>{children}</table>,
	thead: ({ children }) => <thead>{children}</thead>,
	tbody: ({ children }) => <tbody>{children}</tbody>,
	tr: ({ children }) => <tr>{children}</tr>,
	th: ({ children }) => <th>{children}</th>,
	td: ({ children }) => <td>{children}</td>,
};

Copy link
Contributor Author

@levimykel levimykel Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to combine the components and tableComponents into a single object for simplicity. But if you think the difference between these will be clear to our users, then we can separate them out as you've suggested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separate components and tableComponents props were used in the above example because we don't have a straightforward way to combine them right now. However, it should actually be a single components prop to avoid confusion, per the design in the PRD.

We want to move everyone to an object-based components prop rather than the function-based version. With that in mind, we could decide to only support the object syntax in this new component. Doing so would simplify how we merge rich text and table components.

@angeloashmore
Copy link
Member

angeloashmore commented Feb 19, 2025

@levimykel I opened two related PRs:

With those changes, I think we are good to go.

If possible, I'd prefer holding off on merging and publishing until tests pass using a production version of Prismic.

* feat: remove `tableComponents` prop

* chore: update prismic-client dep

* fix: combine tableComponents and components in tests

---------

Co-authored-by: Levi Mykel Gable <[email protected]>
@levimykel
Copy link
Contributor Author

@levimykel I opened two related PRs:

With those changes, I think we are good to go.

If possible, I'd prefer holding off on merging and publishing until tests pass using a production version of Prismic.

@angeloashmore These have been merged. Just waiting now for table to be published and the tests to pass. Thanks for your help on this 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants