0
7

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 11 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 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
Adicionando Imagem Ícone e Classe CSS Personalizada em Bloco Gutenberg

Ícones, Imagens e Classes Personalizadas no Gutenberg

Atributos Visuais e Estilo Dinâmico com Gutenberg

Cor, Alinhamento e Estilo Dinâmico com Gutenberg + React

Como Estilizar Blocos Gutenberg com CSS e Classes Dinâmicas

Estilizando Blocos Gutenberg com CSS e Classes Dinâmicas

Criando Campos Dinâmicos e InspectorControls no Gutenberg

Campos Dinâmicos e InspectorControls no Gutenberg: Deixe Seu Bloco Interativo

Criando Blocos Personalizados com Gutenberg e React

Criando Blocos Personalizados com Gutenberg e React

Como Criar Comandos WP-CLI Personalizados para Plugins

Criando Comandos WP-CLI Personalizados para Plugins WordPress

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!