Как создать кастомный Gutenberg-блок
Полгода назад передо мной стояла задача — создать уникальный блок для WordPress-сайта клиента, который бы отображал карточки товаров с анимацией при наведении. Стандартные блоки Gutenberg не давали нужной гибкости, а плагины добавляли лишний вес. Так началось моё погружение в разработку кастомных блоков, о котором хочу рассказать подробно.
Почему я выбрал именно этот путь
Многие разработчики боятся Gutenberg из-за React и сложности сборки. Признаюсь, первые пару дней я тоже путался в терминологии — attributes, InspectorControls, RichText. Но когда разобрался в логике, процесс создания блоков стал приносить удовольствие.
Подготовка окружения
Первое, что нужно сделать — установить Node.js и инициализировать проект с помощью официального инструмента от WordPress:
npx @wordpress/create-block product-card-block
cd product-card-block
npm start
Эта команда создаёт полноценную структуру плагина с настроенным webpack, babel и всеми необходимыми зависимостями. Раньше я настраивал сборку вручную, но @wordpress/scripts экономит часы времени.
Структура моего блока
После генерации я получил такую структуру файлов:
product-card-block/
├── src/
│ ├── block.json
│ ├── edit.js
│ ├── save.js
│ ├── index.js
│ ├── style.scss
│ └── editor.scss
├── build/
└── product-card-block.php
Настройка block.json
Этот файл — метаданные блока. Именно здесь я определяю атрибуты, поддержку и категорию:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "myplugin/product-card",
"title": "Карточка товара",
"category": "widgets",
"icon": "cart",
"description": "Блок для отображения карточки товара с анимацией",
"keywords": ["товар", "карточка", "продукт"],
"version": "1.0.0",
"textdomain": "product-card-block",
"attributes": {
"title": {
"type": "string",
"source": "html",
"selector": "h3"
},
"price": {
"type": "string",
"default": ""
},
"imageUrl": {
"type": "string",
"default": ""
},
"imageId": {
"type": "number"
},
"buttonText": {
"type": "string",
"default": "Купить"
}
},
"supports": {
"html": false,
"align": ["wide", "full"]
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
Логика редактирования
Это самый интересный файл, где я собрал интерфейс редактирования блока edit.js:
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
RichText,
MediaUpload,
MediaUploadCheck,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, TextControl, Button } from '@wordpress/components';
export default function Edit({ attributes, setAttributes }) {
const { title, price, imageUrl, imageId, buttonText } = attributes;
const blockProps = useBlockProps({
className: 'product-card-block',
});
const onSelectImage = (media) => {
setAttributes({
imageUrl: media.url,
imageId: media.id,
});
};
return (
<>
<InspectorControls>
<PanelBody title={__('Настройки товара', 'product-card-block')}>
<TextControl
label={__('Цена', 'product-card-block')}
value={price}
onChange={(value) => setAttributes({ price: value })}
/>
<TextControl
label={__('Текст кнопки', 'product-card-block')}
value={buttonText}
onChange={(value) => setAttributes({ buttonText: value })}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<MediaUploadCheck>
<MediaUpload
onSelect={onSelectImage}
allowedTypes={['image']}
value={imageId}
render={({ open }) => (
<div className="product-card-image" onClick={open}>
{imageUrl ? (
<img src={imageUrl} alt={title} />
) : (
<Button variant="secondary">
{__('Загрузить изображение', 'product-card-block')}
</Button>
)}
</div>
)}
/>
</MediaUploadCheck>
<RichText