Skip to content

Instantly share code, notes, and snippets.

@Evavic44
Created July 7, 2024 00:05
Show Gist options
  • Save Evavic44/3a1ce40fd670a37c301fd9e5d10602b4 to your computer and use it in GitHub Desktop.
Save Evavic44/3a1ce40fd670a37c301fd9e5d10602b4 to your computer and use it in GitHub Desktop.

Custom Sanity Table Widget in PortableText using the @sanity/table plugin

install and add @sanity/table to plugins array in the sanity.config.ts file.

Sanity Table Preview

// TableWidget.tsx
import { TablePreview } from "@sanity/table";
import { PreviewProps } from "sanity";

interface Table {
  rows?: TableRow[];
  title?: string;
}

interface TableValueProps {
  table?: Table;
  caption?: string;
}

export function TableWidget(props: TableValueProps & PreviewProps) {
  const { table, caption, title, ...rest } = props;
  const tablePreviewProps = { ...rest, rows: table?.rows || [] };

  return (
    <>
      <div className="px-3">
        <em className="not-italic text-sm font-semibold">
          {caption ?? "Untitled Table"}
        </em>
      </div>
      <TablePreview {...tablePreviewProps} description={caption} />
    </>
  );
}

Table schema

Include table schema to schemas array

// schemas/table.ts
import { defineField, defineType } from "sanity";
import { TableWidget } from "@/app/components/widgets/TableWidget";
import { LuTable } from "react-icons/lu";

export const table = defineType({
  name: "customTable",
  title: "Table",
  type: "object",
  icon: LuTable,
  fields: [
    defineField({
      name: "table",
      title: "Table",
      type: "table",
    }),
    defineField({
      name: "caption",
      type: "string",
      title: "Caption",
      description: "Provide an accessible description for this table",
    }),
  ],
  preview: {
    select: {
      table: "table",
      caption: "caption",
    },
  },
  components: {
    preview: TableWidget,
  },
});

Block Content

import { defineArrayMember, defineType } from "sanity";

export default defineType({
  name: "blockContent",
  title: "Body",
  type: "array",
  description: "Write your content here",
  of: [
    defineArrayMember({
      title: "Block",
      type: "block",
      styles: [
        { title: "Normal", value: "normal" },
        { title: "H2", value: "h2" },
        { title: "H3", value: "h3" },
        { title: "H4", value: "h4" },
        { title: "Quote", value: "blockquote" },
      ],
      marks: {
        decorators: [
          { title: "Strong", value: "strong" },
          { title: "Emphasis", value: "em" },
          { title: "Code", value: "code" },
        ],
      },
    }),
    defineArrayMember({
      type: "customTable", // Add table to block content
    }),
  ],
});

React Table Component

// Table.tsx
import { TableValueProps } from "@/types";

export default function Table({ value }: { value: TableValueProps }) {
  const { caption, table } = value;
  const tableContent = table?.rows;

  if (!tableContent || tableContent.length < 1) {
    return <p>Table Data Missing</p>;
  }

  const [tableHeading, ...tableBody] = tableContent.map((t) => t.cells);

  if (!tableHeading || tableBody.length < 1) {
    return <p>Table Data must have at least one cell.</p>;
  }

  return (
    <table className="border dark:border-zinc-800 border-zinc-200 w-full text-base my-4">
      {caption && (
        <caption className="text-lg font-incognito font-medium my-1">
          {caption}
        </caption>
      )}
      <thead className="bg-zinc-50 dark:bg-[#141414] border-b dark:border-zinc-800 border-zinc-200 text-left">
        <tr className="divide-x divide-zinc-200 dark:divide-zinc-800">
          {tableHeading.map((heading) => (
            <th
              key={heading}
              scope="col"
              className="font-medium text-lg font-incognito px-3 py-2"
            >
              {heading}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {tableBody.map((row, index) => (
          <tr
            key={index}
            className="divide-x divide-zinc-200 dark:divide-zinc-800 border dark:border-zinc-800 border-zinc-200"
          >
            {row.map((cell) => (
              <td key={cell} className="px-3 py-2">
                {cell}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

PortableText Block

import { PortableTextComponents } from "@portabletext/react";

interface Table {
  rows?: TableRow[];
  title?: string;
}

interface TableValueProps {
  table?: Table;
  caption?: string;
}

export const CustomPortableText: PortableTextComponents = {
  types: {
    customTable: ({ value }: { value: TableValueProps }) => (
      <Table value={value} />
    ),
  },
}

Result in Studio and UI

Studio Preview React Component
studio preview studio preview
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment