megclaypool
9/25/2019 - 1:44 PM

Custom Gutenberg Blocks

File structure:

|-- radicati
    |-- dist
    |-- lib
        |-- filters
            |-- gutenberg_blacklist.js
            |-- gutenberg_blacklist.php
        |--gutenberg
            |-- acf_gutenberg_blocks
            |   |-- simple-button
            |   |    |-- simple-button.css
            |   |    |-- simple-button.js
            |   |    |-- simple-button.php
            |   |    |-- simple-button.scss
            |   |    |-- simple-button.svg
            |-- block_custom-styles.js
            |-- block_custom-styles.php
            |-- block_registration.php
        

For each block that you create in the block_registration.php file, you can enqueue styles and scripts that only load when the block is shown (front or backend). A gulp watch task compiles the scss files individually to css.

You can also set up a custom icon for your block. You can either use a dashicons string, or an svg stored as a string. For my Button block, I'm storing the contents of the svg file as a string in the 'icon' property using file_get_contents(). (I found it easier to edit the svg when I could view the file in the browser, and then I decided to keep that functionality by importing the file contents.)

Blocks can have alternate styles (added and removed in block-custom-styles.js) that are applied as classes around the block. Stylesheets then use those classes to display alternate styles.

...

require_once("lib/gutenberg/acf_gutenberg_blocks/block_registration.php");
require_once("lib/gutenberg/block_custom-style-options.php");

// This is Jason's code that automatically sets the 'href' property of our button ACF group based on the other fields
require_once("lib/filters/update_button_href.php");

...
<?php

/**
 * Register Blocks
 * @link https://www.billerickson.net/building-gutenberg-block-acf/#register-block
 *
 * @reference: https://developer.wordpress.org/resource/dashicons/
 */


// TODO:
// Get Jason's help adding code to autogenerate the proper ACF fields so this is independent of the database!

// Currently:
// In ACF GUI, created field group: Gutenberg Block -- Button
// Set to appear when block type = Button
// Created group field: button, with subfields button.content, button.type, button.local_page, and button.external_url


// We've switched to a more modular approach.
// This loads the register_item functions for each 
// custom block that we want to use.
require_once("accordion-item/block.php");
require_once("simple-button/simple-button.php");

add_action('acf/init', 'radicati_register_blocks');

// Then we have call each block's function
// Note that all the block settings are in the
// individual block file, rather than here
function radicati_register_blocks() {
  if (function_exists('acf_register_block_type')) {
    register_accordion_item();
    register_simple_button_item();
  }
}

<?php

/**
 * Gutenberg scripts and styles
 * @link https://www.billerickson.net/block-styles-in-gutenberg/
 */
function radicati_gutenberg_scripts()
{
  wp_enqueue_script(
    'radicati-editor',
    get_stylesheet_directory_uri() . '/lib/acf_gutenberg_blocks/block_custom-styles.js',
    array('wp-blocks', 'wp-dom'),
    filemtime(get_stylesheet_directory() . '/lib/acf_gutenberg_blocks/block_custom-styles.js'),
    true
  );
}
add_action('enqueue_block_editor_assets', 'radicati_gutenberg_scripts');
wp.domReady(() => {
  // always set a default style!
  wp.blocks.registerBlockStyle("acf/radicati-simple-button", {
    name: "default",
    label: "Default",
    isDefault: true
  });

  wp.blocks.registerBlockStyle("acf/radicati-simple-button", {
    name: "button--white",
    label: "White"
  });
  
  // add more blocks here
});

Add the following task to your gulpfile.js to individually compile each block's scss file back to its folder:

gulp.task('gutenberg-blocks-css', function() {
  return (
    gulp
      .src("./lib/acf_gutenberg_blocks/**/*.scss")
      // .pipe(sassGlob())
      .pipe(
        sass({
          outputStyle: "compressed",
          includePaths: [
            "./patterns/_patterns"
          ]
        }).on("error", sass.logError)
      )
      .pipe(autoprefixer())
      // compile each css file to the same directory its scss file is in!
      .pipe(gulp.dest("./lib/acf_gutenberg_blocks/"))
      .pipe(browserSync.stream({ match: "**/*.css" }))
  );
})

Note: the includePaths: ["./patterns/_patterns"] bit is making sure that you can @import stylesheets from the pattern library (such as _variables.scss!)


To the watch task, add:

  gulp.watch("./lib/acf_gutenberg_blocks/**/*.php", ["browserSyncReload"]);
  gulp.watch("./lib/acf_gutenberg_blocks/**/*.scss", ["gutenberg-blocks-css"]);

The browserSyncReload task should already be defined, but I'll include it below just in case:

gulp.task("browserSyncReload", [], function(done) {
  browserSync.reload();
  done();
});
// Obviously, we wouldn't bother to enqueue a "hello world" script, but this is a demo :)
window.onload = function() {
  console.log("hello world!");
};
<?php

/**
 * Radicati Simple Button block
 *
 * Note that the $block variable is built-in to
 * ACF Gutenberg blocks
 **/


/**
 * The built-in block alignment controls are saved
 * in $block['align'], though we've elected not to
 * use them
 *
 * Custom block styles are stored in
 * $block['classname'], as are manually
 * applied styles.
 */


function register_simple_button_item() {

  $settings = [
    'name' => 'radicati-simple-button',
    'title' => __('Button', 'radicati'),
    'render_callback' => 'render_simple_button_item_test',
    'category' => 'layout',
    'icon' => file_get_contents(get_stylesheet_directory_uri() . '/lib/gutenberg/acf_gutenberg_blocks/simple-button/simple-button.svg'),
    'enqueue_style' => get_template_directory_uri() . '/lib/gutenberg/acf_gutenberg_blocks/simple-button/simple-button.css',
    'enqueue_script' => get_template_directory_uri() . '/lib/acf_gutenberg_blocks/simple-button/simple-button.js',
    'mode' => 'auto',
    'keywords' => [
      'link',
      'local',
      'url',
      'button'
    ],
    'supports' => [
      'align' => false,
      'mode' => false
    ]
  ];
  acf_register_block_type($settings);
}

function render_simple_button_item_test($block, $content = '', $is_preview = false, $post_id = 0) {

  // Set up baseClass for BEM :)
  $baseClass = 'button';

  // start with the base class :)
  $blockClasses = $baseClass;
  $blockMods = ['simple-block'];

  $link = get_field('button_link');
  $content = get_field('button_label');
  $color = get_field('button_color');
  $alignment = get_field('button_alignment');


  // This is for supporting the default Gutenberg block alignment, which we've decided to abandon in favor of a field
  // if (!empty($block['align'])) {
  //   $blockMods[] = 'align-' . $block['align'];
  // }

  // Add the button alignment to the modifier list
  if(!empty($alignment)) {
    $blockMods[] = 'align-' . $alignment;
    $wrapperAlignment = 'align-' . $alignment;
  }

  // Add the color to the modifier list
  if (!empty($color)) {
    $blockMods[] = $color;
  }

  // add the modifiers to the button classes
  foreach ($blockMods as $mod) {
    $blockClasses .= ' ' . $baseClass . '--' . $mod;
  }

  // add in any extra classes from the gutenberg advanced tab
  if (!empty($block['className'])) {
    $blockClasses .= ' ' . $block['className'];
  }

  echo ('<div class="simple-button__wrapper simple-button__wrapper--' . $wrapperAlignment . '">');
  echo ('<a href="' . $link . '" class="' . $blockClasses . '">' . $content . '</a>');
  echo ('</div>');

}
// by using includePaths in our gulpfile, we can import our site-wide variables file, which is at the root of the directory I passed to includePaths
@import "variables";

.simple-button--wrapper {
  a.simple-button--button {
    @include rem('border-radius', 4px);
    @include rem('letter-spacing', 1.94px);
    display: inline-block;
    @include rem('font-size', 16px);
    font-weight: 900;
    @include rem('margin', 21px 5px 21px 0);
    @include rem('padding', 10px 26px 11px);
    text-transform: uppercase;


    @include button($color--terracotta);

    &:hover,
    &:focus {
      border-color: $color--terracotta;
      color: $color--terracotta;
      text-decoration: none;
    }
  }

  &.is-style-button--white {
    .simple-button--button {
      @include button($color--white, $color--black);
      border-color: $color--black;

      &:hover {
        background-color: $color--black;
        border-color: $color--black;
      }

      &.button--transparent:hover {
        color: black;
      }
    }
  }
}
Rectangle with rounded corners text