Уникальный класс для блока Gutenberg

При разработке блока Gutenberg иногда бывает, что необходимо задать уникальные css-стили блока, как это сделать без костылей.

Простой пример, как задать уникальный класс и стилизовать блок Gutenberg

Для начала заполним наш block.json, я для примера напишу самый базовую структуру.

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "ourslug/ourblock",
  "title": "Our block",
  "attributes": {
    "blockId": {
      "type": "string"
    },
    "colors": {
      "type":"object",
      "default": {
        "button": "#454545",
        "headline": "#595959"
      }
    }
  },
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}

Тут в атрибуты мы добавили colors, который является объектом и содержит значения по умолчанию. Как видно, стилизовать мы будем кнопку и заголовок. Это все в рамках примера. Ну и ключевой наш атрибут blockId именно в него мы будем заносить уникальную часть класса.

Далее переходим к edit.js

import {
  useBlockProps, // свойства блока
  InspectorControls, // панель управления
  useSetting // настройки темы
} from '@wordpress/block-editor';
import {
  PanelBody, // панель управления
  ColorPalette // палитра цветов
} from '@wordpress/components';
import {
  useEffect // эффекты
} from '@wordpress/element';

export default function Edit({ attributes, setAttributes, clientId }) { // функция редактора
  const { colors, blockId } = attributes; // атрибуты блока
  const themeColors = useSetting('color.palette') || []; // цвета из настроек темы

  // устанавливаем блок id
  useEffect(() => {
    if (!blockId) { // если блок id не установлен
      setAttributes({ blockId: clientId }); // устанавливаем блок id
    }
  }, [clientId, blockId]);

  // возвращаем JSX
  return (
    <>
      {/* панель управления */}
      <InspectorControls>
        <PanelBody title="Colors">
          <h2>Button Color</h2>
          <ColorPalette
            enableAlpha={true}
            colors={themeColors} // цвета из настроек темы
            value={colors.button} // установленный цвет кнопки
            onChange={(val) => {
              // устанавливаем цвет кнопки
              setAttributes({
                colors: {
                  ...colors,
                  button: val
                }
              });
            }}
          />
          <hr />
          <h2>Headline Color</h2>
          <ColorPalette
            enableAlpha={true}
            colors={themeColors} // цвета из настроек темы
            value={colors.headline} // установленный цвет заголовка
            onChange={(val) => {
              // устанавливаем цвет заголовка
              setAttributes({
                colors: {
                  ...colors,
                  headline: val
                }
              });
            }}
          />
        </PanelBody>
      </InspectorControls>

      {/* стили */}
      <style
        dangerouslySetInnerHTML={{
          __html: `
            .our-block-${blockId} h3 {
              color: ${colors.headline};
            }

            .our-block-${blockId} button {
              background-color: ${colors.button};
            }
          `,
        }}
      />

      {/* блок */}
      <div {...useBlockProps({ className: `our-block-${blockId}` })}>
        <h3>Заголовок</h3>
        <button>Кнопка</button>
      </div>
    </>
  );
}

В целом, если вы уже разрабатывали блок для Gutenberg или что-либо под ReactJS, то синтаксис вам должен быть понятен. Но есть несколько моментов на которых я хотел бы остановиться:

  • На 14 строчке из Edit мы получаем помимо привычных attributes и setAttributes еще и clientId — именно он дает нам возможность создать уникальный класс для блока и чуть позднее мы обязательно должны занести его в наш атрибут blockId
  • На 16 строчке мы получаем палитру цветов из настроек темы, это нужно, если мы в своем элементе выбора цвета хотим использовать предустановки
  • На 19 строчке используя UseEffect мы проверяем пустой ли наш атрибут blockId и если он пустой, то заносим в него clientId, который мы получили выше
  • А далее уже пишем панель управления с выбором цвета, уникальный <style></style> и непосредственно наш простенький блок
  • В InspectorControls и PanelBody я использую компонент ColorPalette для выбора цвета. В него я передаю цвета из настроек темы (themeColors) и пишу обработку изменения цвета
  • Далее в теге style я использую специальный атрибут dangerouslySetInnerHTML для того, чтобы написать свой CSS
  • Ну а в нашем блоке я добавляю в useBlockProps() наш уникальный класс { className: `our-block-${blockId}` }, чтобы все изменения применялись

Но edit.js это только часть редактора Gutenberg, там у нас теперь всё редактируется и сохраняется по красоте, но на самом сайте у нас пока выводится примерно ничего. Вывод содержимого на сайте мы будет писать в save.js (спойлер: там тоже самое только меньше)

import {
  useBlockProps,
} from '@wordpress/block-editor';

export default function save({ attributes}) {
  const { colors, blockId } = attributes; // атрибуты блока

  // возвращаем JSX
  return (
    <>
      {/* стили */}
      <style
        dangerouslySetInnerHTML={{
          __html: `
            .our-block-${blockId} h3 {
              color: ${colors.headline};
            }

            .our-block-${blockId} button {
              background-color: ${colors.button};
            }
          `,
        }}
      />

      {/* блок */}
      <div {...useBlockProps.save({ className: `our-block-${blockId}` })}>
        <h3>Заголовок</h3>
        <button>Кнопка</button>
      </div>
    </>
  );
}

Как видно, в save.js мы не делаем никаких обработок, а только используем сохраненные атрибуты.

Вывод

Выше я написал простую реализацию редактирования стилей блока. Ключевой момент тут — это использование clientId, но конкретно в этом примере этот путь был необязателен.

Можно было просто написать инлайн-стиль к необходимому элементу. Но иногда бывает так, что нужно стилизовать псевдоэлементы, типа ::before или ::after, к которым так просто не подступиться 😅

И если мы пошли все-таки путем использования clientId, то лучше не использовать <style dangerouslySetInnerHTML>. Да, это работает, но не является лучим решением.

Лучше написать стили в style.scss, а в качестве значений параметров использовать css-переменные, например var(--our-block-headline-color) и var(--our-block-button-color). А сами эти переменные передать в useBlockProps

<div
  {...useBlockProps({
    style: {
      '--our-block-headline-color': colors.headline,
      '--our-block-button-color': colors.button
    }
  })}
>

Но в данном варианте нам и задавать уникальный класс смысла нет, а это была основная цель данной заметки 😅