-
Notifications
You must be signed in to change notification settings - Fork 40
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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>
);
}
src/PrismicTable.tsx
Outdated
components, | ||
fallback, | ||
}: PrismicTableProps) { | ||
const serializer = createDefaultTableSerializer(components); |
There was a problem hiding this comment.
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>,
};
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@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]>
@angeloashmore These have been merged. Just waiting now for table to be published and the tests to pass. Thanks for your help on this 🙂 |
Resolves: DT-2553
Description
This PR adds a PrismicTable component and tests.
Checklist
Preview
N/A
How to QA 1
Footnotes
Please use these labels when submitting a review:
⚠️ #issue: Strongly suggest a change.
❓ #ask: Ask a question.
💡 #idea: Suggest an idea.
🎉 #nice: Share a compliment. ↩