0
110

SHARE

Componentes Reutilizáveis e Campos Compostos no Gutenberg

Componentes Reutilizáveis e Atributos Compostos em Blocos Gutenberg

Aprenda como criar componentes React reutilizáveis dentro de blocos Gutenberg e trabalhar com atributos compostos como botão com texto e link. Organização e escalabilidade no desenvolvimento de plugins.
Este post é a parte 5 de 20 da Série WordPress Extremo: Gutenberg + React + Blocos Customizados

Fala dev! No dia 12 da nossa trilha WordPress Extremo, a parada ficou séria: vamos montar um bloco chamado Card, com:

  • imagem destacada
  • título
  • descrição
  • botão com texto + link

Tudo isso com:

Componentes React reutilizáveis
Atributos compostos (objeto)
✅ Estrutura modular e segura
✅ Registro correto no PHP
✅ Webpack com build organizado em /dist


✅ O que você vai aprender

  • Como criar e registrar blocos personalizados com @wordpress/scripts
  • Como separar partes do bloco em componentes (estilo profissional)
  • Como usar TextareaControl, MediaUpload, InspectorControls
  • Como configurar corretamente webpack.config.js para não apagar arquivos
  • Como registrar os blocos via PHP usando register_block_type() e wp_register_script()

📁 Estrutura do Bloco card

/blocks/card/
├── block.json
├── index.js
├── edit.js
├── save.js
├── style.css
├── editor.css
└── components/
    ├── ImageUploader.js
    └── ButtonLink.js

/dist/
├── card.js
├── card.asset.php

📄 Arquivos do Bloco


📌 block.json

{
  "apiVersion": 2,
  "name": "wp24h/card",
  "title": "Card Personalizado WP24H",
  "category": "widgets",
  "icon": "format-image",
  "description": "Um card com imagem, título, descrição e botão. Com componentes React e atributos compostos.",
  "editorScript": "file:../../dist/card.js",
  "style": "file:./style.css",
  "editorStyle": "file:./editor.css",
  "attributes": {
    "imagem": { "type": "string", "default": "" },
    "titulo": { "type": "string", "default": "Título do card" },
    "descricao": { "type": "string", "default": "Descrição do conteúdo do card." },
    "botao": {
      "type": "object",
      "default": {
        "texto": "Saiba mais",
        "url": "#"
      }
    }
  },
  "supports": {
    "align": true,
    "html": false
  },
  "keywords": ["card", "wp24h", "imagem", "botão"]
}

📌 index.js

import { registerBlockType } from '@wordpress/blocks';
import edit from './edit';
import save from './save';
import metadata from './block.json';

registerBlockType(metadata.name, {
  ...metadata,
  edit,
  save,
});

📌 edit.js

import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, TextareaControl } from '@wordpress/components';
import ImageUploader from './components/ImageUploader';
import ButtonLink from './components/ButtonLink';

export default function Edit({ attributes, setAttributes }) {
  const { imagem, titulo, descricao, botao } = attributes;

  return (
    <>
      <InspectorControls>
        <PanelBody title="Conteúdo do Card">
          <TextControl
            label="Título"
            value={titulo}
            onChange={(val) => setAttributes({ titulo: val })}
          />
          <TextareaControl
            label="Descrição"
            value={descricao}
            onChange={(val) => setAttributes({ descricao: val })}
          />
          <ButtonLink
            texto={botao.texto}
            url={botao.url}
            onChange={(novo) => setAttributes({ botao: novo })}
          />
        </PanelBody>
      </InspectorControls>

      <div {...useBlockProps()} className="wp-block-wp24h-card">
        <ImageUploader imagem={imagem} onChange={(val) => setAttributes({ imagem: val })} />
        <h3>{titulo}</h3>
        <p>{descricao}</p>
        <a href={botao.url} className="btn-link">{botao.texto}</a>
      </div>
    </>
  );
}

📌 save.js

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

export default function save({ attributes }) {
  const { imagem, titulo, descricao, botao } = attributes;

  return (
    <div {...useBlockProps.save()} className="wp-block-wp24h-card">
      {imagem && <img src={imagem} alt="" />}
      <h3>{titulo}</h3>
      <p>{descricao}</p>
      <a href={botao.url} className="btn-link">{botao.texto}</a>
    </div>
  );
}

📌 components/ImageUploader.js

import { MediaUpload } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';

export default function ImageUploader({ imagem, onChange }) {
  return (
    <div className="image-uploader">
      {imagem && <img src={imagem} alt="Imagem do card" style={{ maxWidth: '100%' }} />}
      <MediaUpload
        onSelect={(media) => onChange(media.url)}
        allowedTypes={['image']}
        render={({ open }) => (
          <Button onClick={open} variant="secondary">
            {imagem ? 'Trocar imagem' : 'Selecionar imagem'}
          </Button>
        )}
      />
    </div>
  );
}

import { TextControl } from '@wordpress/components';

export default function ButtonLink({ texto, url, onChange }) {
  return (
    <div className="button-config">
      <TextControl
        label="Texto do botão"
        value={texto}
        onChange={(val) => onChange({ texto: val, url })}
      />
      <TextControl
        label="Link do botão"
        value={url}
        onChange={(val) => onChange({ texto, url: val })}
      />
    </div>
  );
}

📌 style.css

.wp-block-wp24h-card {
  padding: 1.5rem;
  background-color: #ffffff;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  max-width: 480px;
  margin: 1rem auto;
  font-family: sans-serif;
  text-align: center;
}

.wp-block-wp24h-card img {
  max-width: 100%;
  border-radius: 6px;
  margin-bottom: 1rem;
}

.wp-block-wp24h-card h3 {
  font-size: 1.4rem;
  margin: 0.5rem 0;
  color: #222;
}

.wp-block-wp24h-card p {
  font-size: 1rem;
  color: #444;
  margin-bottom: 1rem;
}

.wp-block-wp24h-card .btn-link {
  display: inline-block;
  background: #007cba;
  color: #fff;
  padding: 0.6rem 1rem;
  text-decoration: none;
  border-radius: 4px;
  transition: background 0.3s ease;
}

.wp-block-wp24h-card .btn-link:hover {
  background: #005a8d;
}

📌 editor.css

.wp-block-wp24h-card {
  outline: 1px dashed #bbb;
  background-color: #f9f9f9;
}

.image-uploader img {
  max-width: 100%;
  margin-bottom: 1rem;
  border-radius: 6px;
}

✅ Registro via PHP (organizado no Init.php)

namespace WpArquiteturaExtrema\Hooks;

class Init {
  public function register() {
    add_action('init', [$this, 'init_plugin']);
    add_action('init', [$this, 'register_blocks']);
  }

  public function init_plugin() {
    // lógica do plugin
  }

  public function register_blocks() {
    $blocks = ['card', 'hello'];

    foreach ($blocks as $block) {
      $asset_path = __DIR__ . "/../../dist/{$block}.asset.php";
      if (!file_exists($asset_path)) continue;

      $asset = include $asset_path;

      wp_register_script(
        "wp24h-{$block}",
        plugins_url("dist/{$block}.js", dirname(__DIR__, 2)),
        $asset['dependencies'],
        $asset['version']
      );

      register_block_type(__DIR__ . "/../../blocks/{$block}", [
        'editor_script' => "wp24h-{$block}"
      ]);
    }
  }
}


⚙️ Sobre o Webpack e o Build Seguro

Quando usamos @wordpress/scripts, é o Webpack quem cuida da compilação dos arquivos JS do bloco.
O problema? Se você não configurar direito, ele pode:

  • Apagar suas pastas (blocks/card)
  • Ignorar arquivos (se você buildar antes de criá-los)
  • Ou gerar JS que o WordPress não entende

🧨 O erro mais comum

Muita gente (e até dev experiente) usa isso no webpack.config.js:

output: {
  path: path.resolve(__dirname, 'blocks'),
  filename: '[name]/build/[name].js'
}

Isso faz o Webpack sobrescrever (e até apagar) arquivos dentro da pasta blocks/ a cada build.


✅ A solução profissional

Separar o JS compilado em uma pasta externa: /dist

📄 Seu webpack.config.js deve ser assim:

const path = require('path');
const defaultConfig = require('@wordpress/scripts/config/webpack.config');

module.exports = {
  ...defaultConfig,
  entry: {
    'card': path.resolve(__dirname, 'blocks/card/index.js'),
    'hello': path.resolve(__dirname, 'blocks/hello/index.js')
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    clean: true
  }
};

🎯 Por que usar /dist?

✅ Evita apagar arquivos importantes (como seus .js, .json, .css) dentro de blocks/
✅ Centraliza todos os scripts prontos para uso
✅ Facilita deploy, versionamento e debug
✅ É o padrão em projetos reais (inclusive plugins no repositório oficial)


📌 Depois disso:

  • Certifique que no editorScript no block.json esteja assim:
"editorScript": "file:../../dist/card.js"
  • E rode:
npm run build

Ou para build automático enquanto edita:

npm run start

✅ Desse momo temos:

✔️ Bloco seguro, escalável e funcional
✔️ Tudo registrado corretamente
✔️ Componentes reaproveitáveis
✔️ Build confiável com Webpack moderno
✔️ Estrutura pronta pra produção real


🎯 Resultado final

✅ Bloco “Card” aparece no editor
✅ Totalmente funcional
✅ Seguro pra build
✅ Componentes organizados
✅ Setup profissional

Se você gostou desse conteúdo, deixe seu comentário abaixo.

Navegação<< Conectando Blocos com a REST API do WordPressÍcones, Imagens e Classes Personalizadas no Gutenberg >>

Não perca mais nenhuma atualização aqui!

Ative as Notificações!

Clique aqui e, em seguida, clique em Permitir na caixa que aparecerá na parte superior da janela, próximo à barra de endereços.

Torne-se um Assinante e Eleve seu Conhecimento do WordPress!

Acesso Exclusivo, Suporte Especializado e Muito Mais.

Se você está aproveitando nosso conteúdo gratuito, vai adorar os benefícios exclusivos que oferecemos aos nossos assinantes! Ao se tornar um assinante do WP24Horas, você terá acesso a:

Não perca a oportunidade de maximizar seu potencial no WordPress. Clique no botão abaixo para se tornar um assinante e leve suas habilidades ao próximo nível!

Não perca mais nenhuma atualização aqui!

Tabela de Conteúdo
PUBLICIDADE
Últimos Posts
Listagem e Detalhes de Posts WordPress com API REST no Next.js

Listagem de Posts e Roteamento Dinâmico no Next.js

Frontend com Next.js - Setup e Integração Inicial com WordPress

Frontend com Next.js: Setup e Integração Inicial

Configurando o WordPress como Backend Headless

Configurando o WordPress como Backend Headless

REST API do WordPress

REST API do WordPress: Visão Geral

Introdução ao WordPress Headless

Introdução ao WordPress Headless

Como Distribuir Seus Blocos Gutenberg em Plugins ou no GitHub

Distribuindo Seus Blocos: Plugins, Repositório e GitHub

Você precisa estar logado para ver esta informação.

Torne-se um Assinante e Eleve seu Conhecimento do WordPress!

Acesso Exclusivo, Suporte Especializado e Muito Mais.

Se você está aproveitando nosso conteúdo gratuito, vai adorar os benefícios exclusivos que oferecemos aos nossos assinantes! 

Não perca a oportunidade de maximizar seu potencial no WordPress. Clique no botão abaixo para se tornar um assinante e leve suas habilidades ao próximo nível!