👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Custom PageBuilder Input Component - Preview Blocks & Sections

By studiodrimmel

Wanna use Sanity to build a PageBuilder? Show previews of your blocks/sections!

PageBuilderInput.tsx

"use client";

import { ArrayOfObjectsInputProps, BooleanSchemaType, FileSchemaType, NumberSchemaType, ObjectSchemaType, ReferenceSchemaType, StringSchemaType } from "sanity";
import {
  Grid,
  Stack,
  Button,
  Dialog,
  Box,
  Card,
  Heading,
} from "@sanity/ui";
import { useCallback, useState } from "react";
import { AddIcon } from "@sanity/icons";
import { randomKey } from "@sanity/util/content";
import React from "react";

type Schema = BooleanSchemaType | FileSchemaType | NumberSchemaType | ObjectSchemaType | StringSchemaType | ReferenceSchemaType;

const PageBuilderInput = (props: ArrayOfObjectsInputProps) => {
  const { onInsert } = props;
  const [open, setOpen] = useState(false);
  const onClose = useCallback(() => setOpen(false), []);
  const onOpen = useCallback(() => setOpen(true), []);

  const onSelectItem = useCallback((schema: Schema) => {
    const key = randomKey(12);
    onInsert({
      items: [
        {
          _type: schema.name,
          _key: key,
        } as any,
      ],
      position: "after",
      referenceItem: -1,
      open: true,
    });
    onClose();
  }, []);

  return (
    <>
      <Stack space={3}>
        {props.renderDefault({
          ...props,
          arrayFunctions: () => {
            return (
              <Button
                onClick={onOpen}
                icon={AddIcon}
                mode="ghost"
                text="Add item"
              />
            );
          },
        })}
      </Stack>

      {open && (
        <Dialog
          header="Select a section"
          id="dialog-example"
          width={4}
          onClose={onClose}
          zOffset={1000}
        >
          <Box padding={1}>
            <Grid autoCols={'auto'} columns={[1, 2, 2, 3, 4]} autoFlow={'row dense'} gap={[3]} padding={4}>
              {props.schemaType.of.map((schema, index) => {
                return (
                  <PreviewCard
                  key={index}
                  schema={schema}
                  onClick={() => onSelectItem(schema)} />
                );
              })}
            </Grid>
          </Box>
        </Dialog>
      )}
    </>
  );
}

type PreviewProps = {
  onClick: React.MouseEventHandler<HTMLDivElement> | undefined,
  schema: Schema
}

function PreviewCard(props: PreviewProps) {
  const { onClick, schema } = props;
  return (
    <Card
    role="button"
    shadow={1}
    padding={3}
    onClick={onClick}
    style={{ cursor: "pointer" }}
  >
    <Stack padding={2} space={[3]}>
      <Heading as="h5" size={1}>
        {schema.title}
      </Heading>
      <div
        style={{
          height: "150px",
        }}
      >
        <img
          style={{
            width: "100%",
            height: "100%",
            objectFit: "cover",
          }}
          src={`/sanity/${schema.name}.png`}
          alt={schema.title}
          onError={(i: any) =>
            (i.target.style.display = "none")
          }
        />
      </div>
    </Stack>
  </Card>
  )
}

export default PageBuilderInput;

PageBuilder Array

defineField({
  title: "Pagebuilder",
  name: "sections",
  type: "array",
  group: 'pagebuilder',
  of: [
    { type: homeHeroSection },
    ...
  ],
  components: {
    input: PageBuilderInput,
  }
}),

Do you want to use Sanity.io by creating some sort of Page Builder? But are you stuck with the simply array input and selecting a name like: "Block text with image on the right side"?

Create a more beautiful block/section selector with preview images!

How to use.

  1. First of all, you will need to copy the PageBuilderInput.tsx into your Sanity.io project files.
  2. Create a folder in your project which will contain all of your preview images (one per 'Block') and name your images after your schemaType. e.g. if your schemaType is introWithText your image filename is introWithText.png.
  3. Change the image src to your desired folder src={`/sanity/${schema.name}.png`}
  4. Use the component in your page builder array!

Contributor