Skip to main content

Creating Block from Components

docs-source

You may wonder: what is the difference between components and blocks? Aren't they the same thing?

They are similar, but not the same. Components are, for lack of a better word, dumb. They aren't bothered with the context and they are reusable. This is the keyword in this whole ordeal. One component may be reused in different blocks. Also, the main difference is that the component is not registered in WordPress; its sole purpose is to provide reusable parts for your blocks.

Let's look at an example. For instance, we want to create a card block. This block consists of an image, a title, and a text.

Folder structure

First, we'll create a block in the src/blocks/custom/card folder. The folder structure will look like this

|card
| |components
| | |card-editor.js
| | |card-options.js
| |card-block.js
| |card.php
| |card-style.scss
| |manifest.json

The manifest.json will hold all the default attributes and data about the new block

Manifest

{
"blockName": "card",
"title": "Card",
"description" : "Card with custom settings.",
"category": "eightshift",
"icon": {
"src": "buddicons-buddypress-logo"
},
"keywords": [
"Service",
"Box",
"Card"
],
"hasWrapper": false,
"parent": [
"eightshift-boilerplate/cards-grid"
],
"attributes": {
"media": {
"type": "object",
"default": {
"id": 0,
"url": "",
"title": ""
},
"items": {
"type": "object"
},
"mediaAction": true
},
"heading": {
"type": "string"
},
"paragraph": {
"type": "string"
}
}
}

Components

Next, we want to set up the editor and options component for this block. So in the components folder, we'll add card-editor.js and card-options.js files.

card-editor.js

import React from 'react'; // eslint-disable-line no-unused-vars
import { RichText } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { ImageEditor } from '../../../components/image/components/image-editor';

export const CardEditor = (props) => {
const {
attributes: {
blockClass,
heading,
paragraph,
media,
},
actions: {
onChangeHeading,
onChangeParagraph,
},
} = props;

return (
<div className={blockClass}>
<div className={`${blockClass}__media`}>
<ImageEditor
blockClass={blockClass}
media={media}
/>
</div>
<div className={`${blockClass}__content`}>
<div className={`${blockClass}__heading`}>
<RichText
placeholder={__('Add Heading', 'eightshift-boilerplate')}
onChange={onChangeHeading}
value={heading}
/>
</div>
<div className={`${blockClass}__paragraph`}>
<RichText
placeholder={__('Add Paragraph', 'eightshift-boilerplate')}
onChange={onChangeParagraph}
value={paragraph}
/>
</div>
</div>
</div>
);
};

Notice how we've imported the image-editor component from the project's component folder. Note that in your project you'd just copy this component from the frontend-libs blocks (that is you'd copy the entire folder in your project as previously described).

Our card editor component will have an <ImageEditor /> component and two <RichText /> components (which come from the core editor).

card-options.js

import React from 'react'; // eslint-disable-line no-unused-vars
import { __ } from '@wordpress/i18n';
import { PanelBody } from '@wordpress/components';
import { ImageOptions } from '../../../components/image/components/image-options';

export const CardOptions = (props) => {
const {
attributes: {
media,
},
actions: {
onChangeMedia,
},
} = props;

return (
<PanelBody title={__('Card Details', 'eightshift-boilerplate')}>
<ImageOptions
media={media}
onChangeMedia={onChangeMedia}
/>
</PanelBody>
);
};

The options component (the one shown on the right side in the editor screen), will only contain the <ImageOptions /> so that we can upload image in our block.

Editor render

Back to the block, we need to create the edit method functionality and the view that we'll render on the front.

card-block.js

import React from 'react'; // eslint-disable-line no-unused-vars
import { Fragment } from '@wordpress/element';
import { InspectorControls } from '@wordpress/editor';
import { getActions } from 'EighshiftBlocksGetActions';
import manifest from './manifest.json';
import { CardEditor } from './components/card-editor';
import { CardOptions } from './components/card-options';
import './hooks';

export const Card = (props) => {

const {
attributes,
} = props;

const actions = getActions(props, manifest);

return (
<Fragment>
<InspectorControls>
<CardOptions
attributes={attributes}
actions={actions}
/>
</InspectorControls>
<CardEditor
attributes={attributes}
actions={actions}
/>
</Fragment>
);
};

Here we'll use our ready-made component (built out of other components - reusability), and wrap it in React Fragment. It is a pattern used to return multiple elements.

Another thing you'll note is the usage of <InspectorControls /> component. It is used to display settings of the block in the sidebar (in our case our options that contain the image upload component). Lastly, both our <CardEditor /> and <InspectorControls /> are wrapped in the <Fragment /> wrapper due to Reacts one top-level element rule. If your block doesn't have options, you can only have <CardEditor /> component in here.

PHP view

So all we need now is the PHP view.

card.php

<?php
/**
* Template for the Card Block.
*
* @since 1.0.0
* @package Eightshift_Boilerplate\Blocks.
*/

namespace Eightshift_Boilerplate\Blocks;

$heading = $attributes['heading'] ?? '';
$paragraph = $attributes['paragraph'] ?? '';
$media = $attributes['media'] ?? [];

$block_class = $attributes['blockClass'] ?? '';

?>

<div class="<?php echo esc_attr( $block_class ); ?>">

<?php if ( ! empty( $media ) ) { ?>
<div class="<?php echo esc_attr( "{$block_class}__media" ); ?>">
<?php $this->render_block_view(
'/components/image/image.php',
[
'blockClass' => $attributes['blockClass'] ?? '',
'media' => $media,
]
);
?>
</div>
<?php } ?>

<div class="<?php echo esc_attr( "{$block_class}__content" ); ?>">
<?php if ( ! empty( $heading ) ) { ?>
<div class="<?php echo esc_attr( "{$block_class}__heading" ); ?>">
<?php echo wp_kses_post( $heading ); ?>
</div>
<?php } ?>

<?php if ( ! empty( $paragraph ) ) { ?>
<div class="<?php echo esc_attr( "{$block_class}__paragraph" ); ?>">
<?php echo wp_kses_post( $paragraph ); ?>
</div>
<?php } ?>
</div>

</div>

Notice how we used

<?php $this->render_block_view(
'/components/image/image.php',
[
'blockClass' => $attributes['blockClass'] ?? '',
'media' => $media,
]
);
?>

To render out our image component. Again, we're showing the power of reusability here.

Styling

You can style them how you want, but we added some default styles

card-style.scss

$block-card: (
heading: (
mobile: (
font-size: 24px,
line-height: 1.33,
),
),
paragraph: (
mobile: (
font-size: 18px,
line-height: 1.5,
),
),
);

.block-card {
$this: &;

&__media {
margin-bottom: 25px;
}

&__img {
margin: 0 auto;
}

&__content {
text-align: center;
}

&__heading {
@include responsive($block-card, heading);
font-weight: bold;
}

&__paragraph {
@include responsive($block-card, paragraph);
margin-top: 15px;
}
}