Search code examples
reactjswordpresswordpress-gutenberg

How make a WP custom Gutemberg block with a drag and drop sorting?


Here’s a question for both WP and React developers. I want to develop a custom Gutemberg block using the official create-block library. For UX reasons, I would like this block to have a drag-and-drop sorting system, like this gif. But I haven’t found a solution. There’s this library https://github.com/lucprincen/gutenberg-sortable, but it’s obsolete and won’t compile.

Any ideas? Here’s a simplified code that you can improve.

import { registerBlockType } from "@wordpress/blocks";
import "./style.scss";
import metadata from "./block.json";
import { useBlockProps } from "@wordpress/block-editor";

registerBlockType(metadata.name, {
    attributes: {
        items: {
            type: "array",
            default: ["carrot", "cabbage", "zucchini"],
        },
    },

    edit: (props) => {
        const blockProps = useBlockProps();
        const {
            attributes: { items },
            setAttributes,
        } = props;

        const sortItems = (sortedItems) => {
            setAttributes({ items: sortedItems });
        };

        return (
            <div {...blockProps}>
                {items.map((item) => (
                    <div className="item">{item}</div>
                ))}
            </div>
        );
    },

    save: (props) => {
        const blockProps = useBlockProps.save();
        const {
            attributes: { items },
        } = props;

        return (
            <div {...blockProps}>
                {items.map((item) => (
                    <div className="item">{item}</div>
                ))}
            </div>
        );
    },
});

Solution

  • I've been thinking so hard and i find a solution :). The best thing to do is to use Gutenberg's native drag and drop. I've developed two custom blocks. A parent block and a child block. The parent block has an innerBlock which can only receive child blocks. Here's the simplified code.

    parent block.json

    {
        "$schema": "https://schemas.wp.org/trunk/block.json",
        "apiVersion": 2,
        "name": "domain/vegetables",
        "version": "0.1.0",
        "title": "Vegetables",
        "category": "text",
        "icon": "carrot",
        "description": "A sortable list of vegetables",
        "supports": {
            "html": false
        },
        "editorScript": "file:./index.js",
        "editorStyle": "file:./index.css",
        "style": "file:./style-index.css"
    }
    

    parent index.js

    import { registerBlockType } from "@wordpress/blocks";
    import metadata from "./block.json";
    import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";
    
    registerBlockType(metadata, {
        edit: () => {
            const blockProps = useBlockProps();
    
            return (
                <div {...blockProps}>
                    <InnerBlocks
                        allowedBlocks={["domain/vegetable"]}
                        template={[["domain/vegetable"]]}
                    />
                </div>
            );
        },
    
        save: (props) => {
            const blockProps = useBlockProps.save();
    
            return (
                <div {...blockProps}>
                    <InnerBlocks.Content />
                </div>
            );
        },
    });
    

    child block.json

    {
        "$schema": "https://schemas.wp.org/trunk/block.json",
        "apiVersion": 2,
        "name": "domain/vegetable",
        "version": "0.1.0",
        "title": "Vegetable",
        "category": "text",
        "icon": "carrot",
        "parent": ["domain/vegetables"],
        "description": "A vegetable",
        "supports": {
            "html": false
        },
        "attributes": {
            "name": {
                "type": "string"
            }
        },
        "editorScript": "file:./index.js",
        "editorStyle": "file:./index.css",
        "style": "file:./style-index.css"
    }
    

    child index.js

    import { registerBlockType } from "@wordpress/blocks";
    import metadata from "./block.json";
    import { useBlockProps, RichText } from "@wordpress/block-editor";
    
    registerBlockType(metadata, {
        edit: (props) => {
            const blockProps = useBlockProps();
            const {
                attributes: { name },
                setAttributes,
            } = props;
    
            return (
                <div {...blockProps}>
                    <RichText
                        {...blockProps}
                        tagName="p"
                        value={name}
                        allowedFormats={[]}
                        onChange={(name) => setAttributes({ name })}
                        placeholder={"A vegetable..."}
                    />
                </div>
            );
        },
    
        save: (props) => {
            const blockProps = useBlockProps.save();
            const {
                attributes: { name },
            } = props;
    
            return (
                <div {...blockProps}>
                    <p>{name}</p>
                </div>
            );
        },
    });