measuredco / puck

The visual editor for React
https://puckeditor.com
MIT License
5.02k stars 274 forks source link

Rendering Puck components outside of DropZone #196

Open xaviemirmon opened 10 months ago

xaviemirmon commented 10 months ago

👋 Hi folks,

I am starting work on a project where I've got a requirement for nesting Puck components as children inside a draggable parent component. This is because I am building a design system that requires a set of fieldable primitives used inside a parent component. Ideally, this would work similarly to the DropZone hierarchy without any drag-and-drop functionality. A non-droppable DropZone 🤣. While it's possible to flatten the options that leads to a load of duplicated field definitions and a cluttered sidebar by the .

Example

For example, let's take the demo site and break the following two components into primitives.

Hero

Screenshot 2023-10-31 at 11 08 09

Text

Screenshot 2023-10-31 at 11 18 55

In the pictures above, TitleBlock and TextBlock both might have additional settings to a title field, such as Heading tag (h2, h3, h4, etc), font-size, weight, etc.

I'd expect it to behave similarly to DropZone so that when you select the child component it presents the fields in the sidebar. Screenshot 2023-10-31 at 17 26 48 Screenshot 2023-10-31 at 17 27 24

An option

One option is to add a new Component component that can behave similarly to DropZones now and renders your component and it's fields e.g.

import { Component } from "@measured/puck";

export const HeroBlock = () => {
  return <div>
    <div>
      <Component type="TitleBlock" />
      <Component type="TextBlock" />
      <Component type="ButtonsBlock" />
    </div>
    <div>
      <Component type="ImageBlock" />
    </div>
  </div> 
}

export const ButtonsBlock = () => {
  return <div>
    <Component type="ButtonBlock" />
    <Component type="ButtonBlock" />
  </div> 
}

This could be done by extending the current DropZone but disabling the Droppable functionality.

// pseudo-code
const Component = ({ type, props }) => {
  const { id } = props;
  const componentData = {
    props,
    type
  }

  // Inject into `data.zones`
  data.zones[`${rootDroppableId}:${id}`] = data.zones[`${rootDroppableId}:${id}`] || [componentData];

  return <DropZone zone={id} disabled />
}

Ideas?

What do other people think about this?

jperasmus commented 8 months ago

I like this idea of composable "templates" and agree that it would remove a lot of duplicate config setup.

I'll chime in with an alternative naming:

<Slot name="TitleBlock" />

instead of

<Component type="TitleBlock" />