0
34

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 11 de 18 da Série WordPress Extremo

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<< Ícones, Imagens e Classes Personalizadas no GutenbergConectando Blocos com a REST API do WordPress >>

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
Segurança Profunda em Plugins WordPress

Segurança Profunda em Plugins WordPress

Como Tratar Erros e Exceptions em Plugins WordPress do Jeito Certo

Tratamento de Erros e Uso Inteligente de Exceptions em Plugins WordPress

Como Implementar um Sistema de Logs Profissional em Plugins WordPress

Como Gerenciar Logs e Erros Internos em Plugins WordPress (Do Jeito Certo)

Como Criar um Bloco WordPress com Filtro por Múltiplas Categorias

Bloco de Posts com Filtro por Múltiplas Categorias

Como Filtrar Posts por Categoria em um Bloco Gutenberg

Filtrando Posts por Categoria no Editor do Bloco

Como Criar um Bloco Dinâmico no WordPress com Renderização PHP

Bloco Dinâmico com Renderização no Servidor

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!