Puck Visual Editor

repository·main·Indexed 11 days ago

https://github.com/puckeditor/puck

An open-source modular visual editor for React.js that enables developers to build custom drag-and-drop experiences using their own React components. It consists of a configuration system, the <Puck> editor component, and a <Render> component for displaying published content. Includes a scaffolding CLI via create-puck-app and specialized plugins for Contentful integration, emotion cache injection, and WCAG 2 heading analysis.

Tokens
153.7K
Snippets
527
Records
655
Agent score
95%

What's inside Puck

  1. What is Puck?

    main

    Puck is a modular, open-source visual editor for React.js designed to build custom drag-and-drop experiences using your own application and React components.

    Key characteristics:

    • React-based: As a React component, it is compatible with all React environments, including Next.js.
    • Data Ownership: You own your data with no vendor lock-in.
    • MIT Licensed: Suitable for both internal tools and commercial applications.
  2. Use Core Puck components

    main

    Puck provides three primary core components for different integration needs:

    • <Puck />: The main component used to render the full Puck editor.
    • <Render />: Used to render a Data object for a specific Config. This is typically used in your application's frontend to display the content created in the editor.
    • <DropZone />: Used to define droppable regions (zones) inside your own custom components, enabling nested component structures within the editor.
  3. Overview of Puck features

    main

    Puck provides a wide range of features for customizing the editing experience and integrating with existing systems:

    • Component Configuration: Integrate your own components by providing render functions and mapping fields to props.
    • Root Configuration: Customize the root component wrapping Puck components.
    • Layouts: Create multi-column layouts using nested components and advanced CSS.
    • Organization: Group components in the sidebar using Categories.
    • Dynamic Logic: Use Dynamic Props to set props after user input (including read-only fields) and Dynamic Fields to adjust fields based on user input.
    • Data Integration: Load content from third-party CMS or other External Data Sources.
    • Modern React Support: Opt-in support for React Server Components.
    • Maintenance: Use Data Migration tools to handle breaking Puck releases or prop changes.
    • Previewing: Use Viewports to simulate different screen sizes in a same-origin iframe.
    • Permissions: Use the Feature Toggling API to enable or disable features like duplication or deletion.
  4. Core concepts of Puck

    main

    Puck is a visual editor for React composed of three main parts:

    1. Config: Registers the components and editable fields available to users.
    2. The Editor (<Puck>): The UI component used to build pages. It accepts a config, initial data (JSON), and provides an onPublish callback to save changes.
    3. The Renderer (<Render>): The component used to display the published pages to end-users. It requires the same config and the page data used during editing.
    // 1. Config
    const config = {
      components: {
        HeadingBlock: {
          fields: {
            title: { type: "text" },
          },
          render: ({ title }) => <h1>{title}</h1>,
        },
      },
    };
    
    // 2. The Editor
    <Puck
      config={config}
      data={data}
      onPublish={(data) => {
        // Save data to your database
      }}
    />
    
    // 3. The Renderer
    <Render
      config={config}
      data={data}
    />
  5. Implement Hybrid Authoring with external data

    main

    Hybrid authoring allows users to either edit fields manually or populate them automatically from an external source.

    To achieve this:

    1. Define an external field to select the source data.
    2. Define standard fields (e.g., type: "text") for manual editing.
    3. Use resolveData to map properties from the external data to the standard fields.
    4. Use the readOnly property in the resolveData return object to lock the standard fields when external data is present, preventing accidental overrides.
    const config = {
      components: {
        Example: {
          fields: {
            data: {
              type: "external",
              // ... fetchList and getItemSummary
            },
            title: {
              type: "text",
            },
          },
          resolveData: async ({ props }, { changed }) => {
            // If no external data is selected, allow manual editing of the title
            if (!props.data) return { props, readOnly: { title: false } };
    
            // If data changed, sync the title from the external source and lock the field
            if (!changed.data) return { props };
    
            return {
              props: {
                title: props.data.title,
              },
              readOnly: { title: true },
            };
          },
          render: ({ title }) => <b>{title}</b>,
        },
      },
    };
  6. What are Fields in Puck?

    main
    In Puck, a Field represents a user input interface displayed within the Puck editor. Fields allow editors to modify the data associated with components. Puck provides several built-in field types (such as Text, Number, Select, and RichText) as well as mechanisms to create Custom fields or External fields that pull data from third-party APIs.
  7. How the 'other' category works

    main

    Any components that are not explicitly assigned to a category will automatically be grouped into a special other category. This category is visible by default. You can customize its appearance (such as its title) using the same configuration options available to standard categories.

    const config = {
      categories: {
        typography: {
          components: ["HeadingBlock", "ParagraphBlock"],
        },
        other: {
          title: "Other components",
        },
      },
      // ...
    };
  8. Use Field Transforms to modify field rendering

    main

    Puck provides the FieldTransforms API to modify how field props are rendered within the editor. This is useful for implementing custom rendering behavior, such as wrapping text in specific HTML elements or enabling inline editing.

    Important: Field transforms only apply to components rendered within the <Puck> editor component. They are not applied when using the <Render> component.

    const fieldTransforms = {
      text: ({ value }) => <div>Value: {value}</div>, // Wrap all text field props in divs
    };
    
    const Example = () => <Puck fieldTransforms={fieldTransforms} />;
  9. Core concepts of Puck AI: Plugin and Cloud Client

    main

    Puck AI enables AI-driven page generation through two components:

    1. AI Plugin: A browser-side plugin that renders a chat interface within the Puck editor and communicates with your server.
    2. Cloud Client: A server-side component that connects your server to the Puck cloud. It typically uses the puckHandler API to receive chat messages from the plugin, forward them to the Puck cloud, and stream responses back.

    Puck AI Modes:

    • Assembly mode: Generates pages using only the components defined in your config.
    • Design mode: Can generate entirely new components on the fly to satisfy prompts.
    // Client-side: Adding the AI plugin to the editor
    const aiPlugin = createAiPlugin();
    
    function Editor() {
      return <Puck plugins={[aiPlugin]} config={config} data={data} />;
    }
    
    // Server-side: Using the Cloud Client via puckHandler
    // This is typically used in loaders or actions
    export function loader(args: LoaderFunctionArgs) {
      return puckHandler(args.request, options);
    }
  10. Configure collisionAxis for <DropZone>

    main

    The collisionAxis prop configures which axis Puck uses for overlap collision detection.

    Options:

    • x: Detect collisions based on x-axis overlap.
    • y: Detect collisions based on y-axis overlap.
    • dynamic: Automatically choose an axis based on the direction of travel.

    Default behavior based on parent CSS layout:

    • grid: dynamic
    • flex (row): x
    • inline / inline-block: x
    • Everything else: y
    const config = {
      components: {
        Example: {
          render: () => {
            return (
              <div>
                <DropZone zone="my-content" collisionAxis="dynamic" />
              </div>
            );
          },
        },
      },
    };