Tag: Block

Adding a Custom Welcome Guide to the WordPress Block Editor

I am creating a WordPress plugin and there is a slight learning curve when it comes to using it. I’d like to give users a primer on how to use the plugin, but I want to avoid diverting users to documentation on the plugin’s website since that takes them out of the experience.

What would be great is for users to immediately start using the plugin once it’s installed but have access to helpful tips while they are actively using it. There’s no native feature for something like this in WordPress but we can make something because WordPress is super flexible like that.

So here’s the idea. We’re going to bake documentation directly into the plugin and make it easily accessible in the block editor. This way, users get to use the plugin right away while having answers to common questions directly where they’re working. 

My plugin operates through several Custom Post Types (CPT). What we’re going to build is essentially a popup modal that users get when they go to these CPTs. 

The WordPress block editor is built in React, which utilizes components that can be customized to and reused for different situations.  That is the case with what we’re making — let’s call it the <Guide> component — which behaves like a modal, but is composed of several pages that the user can paginate through.

WordPress itself has a <Guide> component that displays a welcome guide when opening the block editor for the first time:

Screenshot showing a modal on top of the WordPress block editor welcoming users to the editor for the first time.
WordPress displays a modal with instructions for using the block editor when a user loads the editor for the first time.

The guide is a container filled with content that’s broken up into individual pages. In other words, it’s pretty much what we want. That means we don’t have to re-invent the wheel with this project; we can reuse this same concept for our own plugin.

Let’s do exactly that. 

What we want to achieve

Before we get to the solution, let’s talk about the end goal.

The design satisfies the requirements of the plugin, which is a GraphQL server for WordPress. The plugin offers a variety of CPTs that are edited through custom blocks which, in turn, are defined through templates. There’s a grand total of two blocks: one called “GraphiQL client” to input the GraphQL query, and one called “Persisted query options” to customize the behavior of the execution.

Since creating a query for GraphQL is not a trivial task, I decided to add the guide component to the editor screen for that CPT. It’s available in the Document settings as a panel called “Welcome Guide.”

Screenshot showing the WordPress editor with the document settings panel open in the right column. a welcome guide tab is highlighted in the settings.

Crack that panel open and the user gets a link. That link is what will trigger the modal.

Close-up screenshot of the welcome guide tab opened, revealing a link that says "Open Guide: Creating Persisted Queries."

For the modal itself, I decided to display a tutorial video on using the CPT on the first page, and then describe in detail all the options available in the CPT on subsequent pages.

Screenshot showing the custom modal open in the block editor and containing an embedded video on how to use the plugin.

I believe this layout is an effective way to show documentation to the user. It is out of the way, but still conveniently close to the action. Sure, we can use a different design or even place the modal trigger somewhere else using a different component instead of repurposing <Guide>, but this is perfectly good.

Planning the implementation

The implementation comprises the following steps:

  1. Scaffolding a new script to register the custom sidebar panel
  2. Displaying the custom sidebar panel on the editor for our Custom Post Type only
  3. Creating the guide
  4. Adding content to the guide

Let’s start!

Step 1: Scaffolding the script

Starting in WordPress 5.4, we can use a component called <PluginDocumentSettingPanel> to add a panel on the editor’s Document settings like this:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost;   const PluginDocumentSettingPanelDemo = () => (   <PluginDocumentSettingPanel     name="custom-panel"     title="Custom Panel"     className="custom-panel"   >     Custom Panel Contents   </PluginDocumentSettingPanel> ); registerPlugin( 'plugin-document-setting-panel-demo', {   render: PluginDocumentSettingPanelDemo,   icon: 'palmtree', } );

If you’re experienced with the block editor and already know how to execute this code, then you can skip ahead. I’ve been coding with the block editor for less than three months, and using React/npm/webpack is a new world for me — this plugin is my first project using them! I’ve found that the docs in the Gutenberg repo are not always adequate for beginners like me, and sometimes the documentation is missing altogether, so I’ve had to dig into the source code to find answers.

When the documentation for the component indicates to use that piece of code above, I don’t know what to do next, because <PluginDocumentSettingPanel> is not a block and I am unable to scaffold a new block or add the code there. Plus, we’re working with JSX, which means we need to have a JavaScript build step to compile the code.

I did, however, find the equivalent ES5 code:

var el = wp.element.createElement; var __ = wp.i18n.__; var registerPlugin = wp.plugins.registerPlugin; var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; 
 function MyDocumentSettingPlugin() {   return el(     PluginDocumentSettingPanel,     {       className: 'my-document-setting-plugin',       title: 'My Panel',     },     __( 'My Document Setting Panel' )   ); } 
 registerPlugin( 'my-document-setting-plugin', {   render: MyDocumentSettingPlugin } );

ES5 code does not need be compiled, so we can load it like any other script in WordPress. But I don’t want to use that. I want the full, modern experience of ESNext and JSX.

So my thinking goes like this: I can’t use the block scaffolding tools since it’s not a block, and I don’t know how to compile the script (I’m certainly not going to set-up webpack all by myself). That means I’m stuck.

But wait! The only difference between a block and a regular script is just how they are registered in WordPress. A block is registered like this:

wp_register_script($ blockScriptName, $ blockScriptURL, $ dependencies, $ version); register_block_type('my-namespace/my-block', [   'editor_script' => $ blockScriptName, ]);

And a regular script is registered like this:

wp_register_script($ scriptName, $ scriptURL, $ dependencies, $ version); wp_enqueue_script($ scriptName);

We can use any of the block scaffolding tools to modify things then register a regular script instead of a block, which gains us access to the webpack configuration to compile the ESNext code. Those available tools are:

I chose to use the @wordpress/create-block package because it is maintained by the team developing Gutenberg.

To scaffold the block, we execute this in the command line:

npm init @wordpress/block

After completing all the prompts for information — including the block’s name, title and description — the tool will generate a single-block plugin, with an entry PHP file containing code similar to this:

/**  * Registers all block assets so that they can be enqueued through the block editor  * in the corresponding context.  *  * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/  */ function my_namespace_my_block_block_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-namespace/my-block" block first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-namespace-my-block-block-editor',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   ); 
   $ editor_css = 'editor.css';   wp_register_style(     'my-namespace-my-block-block-editor',     plugins_url( $ editor_css, __FILE__ ),     array(),     filemtime( "$ dir/$ editor_css" )   ); 
   $ style_css = 'style.css';   wp_register_style(     'my-namespace-my-block-block',     plugins_url( $ style_css, __FILE__ ),     array(),     filemtime( "$ dir/$ style_css" )   ); 
   register_block_type( 'my-namespace/my-block', array(     'editor_script' => 'my-namespace-my-block-block-editor',     'editor_style'  => 'my-namespace-my-block-block-editor',     'style'         => 'my-namespace-my-block-block',   ) ); } add_action( 'init', 'my_namespace_my_block_block_init' );

We can copy this code into the plugin, and modify it appropriately, converting the block into a regular script. (Note that I’m also removing the CSS files along the way, but could keep them, if needed.)

function my_script_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-script" script first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-script',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   );   wp_enqueue_script(     'my-script'   ); } add_action( 'init', 'my_script_init' );

Let’s copy the package.json file over:

{   "name": "my-block",   "version": "0.1.0",   "description": "This is my block",   "author": "The WordPress Contributors",   "license": "GPL-2.0-or-later",   "main": "build/index.js",   "scripts": {     "build": "wp-scripts build",     "format:js": "wp-scripts format-js",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "start": "wp-scripts start",     "packages-update": "wp-scripts packages-update"   },   "devDependencies": {     "@wordpress/scripts": "^9.1.0"   } }

Now, we can replace the contents of file src/index.js with the ESNext code from above to register the <PluginDocumentSettingPanel> component. Upon running npm start (or npm run build for production) the code will be compiled into build/index.js.

There is a last problem to solve: the <PluginDocumentSettingPanel> component is not statically imported, but instead obtained from wp.editPost, and since wp is a global variable loaded by WordPress on runtime, this dependency is not present in index.asset.php (which is auto-generated during build). We must manually add a dependency to the wp-edit-post script when registering the script to make sure it loads before ours:

$ dependencies = array_merge(   $ script_asset['dependencies'],   [     'wp-edit-post',   ] ); wp_register_script(   'my-script',   plugins_url( $ index_js, __FILE__ ),   $ dependencies,   $ script_asset['version'] );

Now the script setup is ready!

The plugin can be updated with Gutenberg’s relentless development cycles. Run npm run packages-update to update the npm dependencies (and, consequently, the webpack configuration, which is defined on package "@wordpress/scripts") to their latest supported versions.

At this point, you might be wondering how I knew to add a dependency to the "wp-edit-post" script before our script. Well, I had to dig into Gutenberg’s source code. The documentation for <PluginDocumentSettingPanel> is somewhat incomplete, which is a perfect example of how Gutenberg’s documentation is lacking in certain places.

While digging in code and browsing documentation, I discovered a few enlightening things. For example, there are two ways to code our scripts: using either the ES5 or the ESNext syntax. ES5 doesn’t require a build process, and it references instances of code from the runtime environment, most likely through the global wp variable. For instance, the code to create an icon goes like this:

var moreIcon = wp.element.createElement( 'svg' );

ESNext relies on webpack to resolve all dependencies, which enables us to import static components. For instance, the code to create an icon would be:

import { more } from '@wordpress/icons';

This applies pretty much everywhere. However, that’s not the case for the <PluginDocumentSettingPanel> component, which references the runtime environment for ESNext:

const { PluginDocumentSettingPanel } = wp.editPost;

That’s why we have to add a dependency to the “wp-edit-post” script. That’s where the wp.editPost variable is defined.

If <PluginDocumentSettingPanel> could be directly imported, then the dependency to “wp-edit-post” would be automatically handled by the block editor through the Dependency Extraction Webpack Plugin. This plugin builds the bridge from static to runtime by creating a index.asset.php file containing all the dependencies for the runtime environment scripts, which are obtained by replacing "@wordpress/" from the package name with "wp-". Hence, the "@wordpress/edit-post" package  becomes the "wp-edit-post" runtime script. That’s how I figured out which script to add the dependency.

Step 2: Blacklisting the custom sidebar panel on all other CPTs 

The panel will display documentation for a specific CPT, so it must be registered only to that CPT. That means we need to blacklist it from appearing on any other post types.

Ryan Welcher (who created the <PluginDocumentSettingPanel> component) describes this process when registering the panel:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost const { withSelect } = wp.data; 
 const MyCustomSideBarPanel = ( { postType } ) => { 
   if ( 'post-type-name' !== postType ) {     return null;   } 
   return(     <PluginDocumentSettingPanel       name="my-custom-panel"       title="My Custom Panel"     >       Hello, World!     </PluginDocumentSettingPanel>   ); } 
 const CustomSideBarPanelwithSelect = withSelect( select => {   return {     postType: select( 'core/editor' ).getCurrentPostType(),   }; } )( MyCustomSideBarPanel); 
 
 registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

He also suggests an alternative solution, using useSelect instead of withSelect.

That said, I’m not totally convinced by this solution, because the JavaScript file must still be loaded, even if it isn’t needed, forcing the website to take a performance hit. Doesn’t it make more sense to not register the JavaScript file than it does to run JavaScript just to disable JavaScript?

I have created a PHP solution. I’ll admit that it feels a bit hacky, but it works well. First, we find out which post type is related to the object being created or edited:

function get_editing_post_type(): ?string {   if (!is_admin()) {     return null;   } 
   global $ pagenow;   $ typenow = '';   if ( 'post-new.php' === $ pagenow ) {     if ( isset( $ _REQUEST['post_type'] ) && post_type_exists( $ _REQUEST['post_type'] ) ) {       $ typenow = $ _REQUEST['post_type'];     };   } elseif ( 'post.php' === $ pagenow ) {     if ( isset( $ _GET['post'] ) && isset( $ _POST['post_ID'] ) && (int) $ _GET['post'] !== (int) $ _POST['post_ID'] ) {       // Do nothing     } elseif ( isset( $ _GET['post'] ) ) {       $ post_id = (int) $ _GET['post'];     } elseif ( isset( $ _POST['post_ID'] ) ) {       $ post_id = (int) $ _POST['post_ID'];     }     if ( $ post_id ) {       $ post = get_post( $ post_id );       $ typenow = $ post->post_type;     }   }   return $ typenow; }

Then, ,we register the script only if it matches our CPT:

add_action('init', 'maybe_register_script'); function maybe_register_script() {   // Check if this is the intended custom post type   if (get_editing_post_type() != 'my-custom-post-type') {     return;   } 
   // Only then register the block   wp_register_script(...);   wp_enqueue_script(...); }

Check out this post for a deeper dive on how this works.

Step 3: Creating the custom guide

I designed the functionality for my plugin’s guide based on the WordPress <Guide> component. I didn’t realize I’d be doing that at first, so here’s how I was able to figure that out.

  1. Search the source code to see how it was done there.
  2. Explore the catalogue of all available components in Gutenberg’s Storybook.

First, I copied content from the block editor modal and did a basic search. The results pointed me to this file. From there I discovered the component is called <Guide> and could simply copy and paste its code to my plugin as a base for my own guide.

Then I looked for the component’s documentation. I browsed the @wordpress/components package (which, as you may have guessed, is where components are implemented) and found the component’s README file. That gave me all the information I needed to implement my own custom guide component.

I also explored the catalogue of all the available components in Gutenberg’s Storybook (which actually shows that these components can be used outside the context of WordPress). Clicking on all of them, I finally discovered <Guide>. The storybook provides the source code for several examples (or stories). It’s a handy resource for understanding how to customize a component through props.

At this point, I knew <Guide> would make a solid base for my component. There is one missing element, though: how to trigger the guide on click. I had to rack my brain for this one!

This is a button with a listener that opens the modal on click:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import MyGuide from './guide'; 
 const MyGuideWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button onClick={ () => setOpen( true ) }>         { __('Open Guide: “Creating Persisted Queries”') }       </Button>       { isOpen && (         <MyGuide            { ...props }           onFinish={ () => setOpen( false ) }         />       ) }     </>   ); }; export default MyGuideWithButton;

Even though the block editor tries to hide it, we are operating within React. Until now, we’ve been dealing with JSX and components. But now we need the useState hook, which is specific to React.

I’d say that having a good grasp of React is required if you want to master the WordPress block editor. There is no way around it.

Step 4: Adding content to the guide

We’re almost there! Let’s create the <Guide> component, containing a <GuidePage> component for each page of content.

The content can use HTML, include other components, and whatnot. In this particular case, I have added three <GuidePage> instances for my CPT just using HTML. The first page includes a video tutorial and the next two pages contain detailed instructions.

import { Guide, GuidePage } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; 
 const MyGuide = ( props ) => {   return (     <Guide { ...props } >       <GuidePage>         <video width="640" height="400" controls>           <source src="https://d1c2lqfn9an7pb.cloudfront.net/presentations/graphql-api/videos/graphql-api-creating-persisted-query.mov" type="video/mp4" />           { __('Your browser does not support the video tag.') }         </video>         // etc.       </GuidePage>       <GuidePage>         // ...       </GuidePage>       <GuidePage>         // ...       </GuidePage>     </Guide>   ) } export default MyGuide;
imaged gif showing the mouse cursor clicking on the Open Guide link in the block editor's document settings, which opens the custom welcome guide containing a video with links to other pages in the modal.
Hey look, we have our own guide now!

Not bad! There are a few issues, though:

  • I couldn’t embed the video inside the <Guide> because clicking the play button closes the guide. I assume that’s because the <iframe> falls outside the boundaries of the guide. I wound up uploading the video file to S3 and serving with <video>.
  • The page transition in the guide is not very smooth. The block editor’s modal looks alright because all pages have a similar height, but the transition in this one is pretty abrupt.
  • The hover effect on buttons could be improved. Hopefully, the Gutenberg team needs to fix this for their own purposes, because my CSS aren’t there. It’s not that my skills are bad; they are nonexistent.

But I can live with these issues. Functionality-wise, I’ve achieved what I need the guide to do.

Bonus: Opening docs independently 

For our <Guide>, we created the content of each <GuidePage> component directly using HTML. However, if this HTML code is instead added through an autonomous component, then it can be reused for other user interactions.

For instance, the component <CacheControlDescription> displays a description concerning HTTP caching:

const CacheControlDescription = () => {   return (     <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or "no-store" if the max-age is 0</p>   ) } export default CacheControlDescription;

This component can be added inside a <GuidePage> as we did before, but also within a <Modal> component:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import CacheControlDescription from './cache-control-desc'; 
 const CacheControlModalWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button          icon="editor-help"         onClick={ () => setOpen( true ) }       />       { isOpen && (         <Modal            { ...props }           onRequestClose={ () => setOpen( false ) }         >           <CacheControlDescription />         </Modal>       ) }     </>   ); }; export default CacheControlModalWithButton;

To provide a good user experience, we can offer to show the documentation only when the user is interacting with the block. For that, we show or hide the button depending on the value of isSelected:

import { __ } from '@wordpress/i18n'; import CacheControlModalWithButton from './modal-with-btn'; 
 const CacheControlHeader = ( props ) => {   const { isSelected } = props;   return (     <>       { __('Cache-Control max-age') }       { isSelected && (         <CacheControlModalWithButton />       ) }     </>   ); } export default CacheControlHeader;

Finally, the <CacheControlHeader> component is added to the appropriate control.

Animated gif showing the option to view a guide displaying when a block is selected in the editor.

Tadaaaaaaaa 🎉

The WordPress block editor is quite a piece of software! I was able to accomplish things with it that I would have been unable to without it. Providing documentation to the user may not be the shiniest of examples or use cases, but it’s a very practical one and something that’s relevant for many other plugins. Want to use it for your own plugin? Go for it!

The post Adding a Custom Welcome Guide to the WordPress Block Editor appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,

Block Links: The Search for a Perfect Solution

I was reading this article by Chris where he talks about block links — you know, like wrapping an entire card element inside an anchor — being a bad idea. It’s bad accessibility because of how it affects screen readers. And it’s bad UX because it prevents simple user tasks, like selecting text.

But maybe there’s something else at play. Maybe it’s less an issue with the pattern than the implementation of it. That led me to believe that this is the time to write follow-up article to see if we can address some of the problems Chris pointed out.

Throughout this post, I’ll use the term “card” to describe a component using the block link pattern. Here’s what we mean by that.

Let’s see how we want our Card Components to work:

  1. The whole thing should be linked and clickable.
  2. It should be able to contain more than one link.
  3. Content should be semantic so assistive tech can understand it.
  4. The text should be selectable, like regular links.
  5. Things like right-click and keyboard shortcuts should work with it
  6. Its elements should be focusable when tabbing.

That’s a long list! And since we don’t have any standard card widget provided by the browser, we don’t have any standard guidelines to build it. 

Like most things on the web, there’s more than one way to make a card component. However, I haven’t found something that checks all the requirements we just covered. In this article, we will try to hit all of them. That’s what we’re going to do now!

Method 1: Wrap everything an <a>

This is the most common and the easiest way to make a linked card. Take the HTML for the card and wrap the entire thing in an anchor tag.

<a href="/">   <!-- Card markup --> </a>

Here’s what that gives us:

  1. It’s clickable.
  2. It works with right-click and keyboard shortcuts.

Well, not great. We still can’t:

  1. Put another link inside the card because the entire thing is a single link
  2. Use it with a screen reader — the content is not semantic, so assistive technology will announce everything inside the card, starting from the time stamp
  3. Select text

That’s enough 👎 that we probably shouldn’t use it. Let’s move onto the next technique.

Method 2: Just link what needs linking

This is a nice compromise that sacrifices a little UX for improved accessibility.

With this pattern we achieve most of our goals:

  1. We can put as many links as we want. 
  2. Content is semantic.
  3. We can select the text from Card.
  4. Right Click and keyboard shortcuts work.
  5. The focus is in proper order when tabbing.

But it is missing the main feature we want in a card: the whole thing should be clickable! Looks like we need to try some other way.

Method 3: The good ol’  ::before pseudo element

In this one, we add a ::before or ::after element, place it above the card with absolute positioning and stretch it over the entire width and height of the card so it’s clickable.

But now:

  1. We still can’t add more than one link because anything else that’s linked is under the pseudo element layer. We can try to put all the text above the pseudo element, but card link itself won’t work when clicking on top of the text.
  2. We still can’t select the text. Again, we could swap layers, but then we’re back to the clickable link issue all over again.

Let’s try to actually check all the boxes here in our final technique.

Method 4: Sprinkle JavaScript on the second method

Let’s build off the second method. Recall that’s what where we link up everything we want to be a link:

<article class="card">   <time datetime="2020-03-20">Mar 20, 2020</time>   <h2><a href="https://css-tricks.com/a-complete-guide-to-calc-in-css/" class="main-link">A Complete Guide to calc() in CSS</a></h2>   <p>     In this guide, let’s cover just about everything there is to know about this very useful function.   </p>   <a class="author-name" href="https://css-tricks.com/author/chriscoyier/" target="_blank">Chris Coyier</a>     <div class="tags">       <a class="tag" href="https://css-tricks.com/tag/calc/" >calc</a>     </div> </article>

So how do we make the whole card clickable? We could use JavaScript as a progressive enhancement to do that. We’ll start by adding a click event listener to the card and trigger the click on the main link when it is triggered.

const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') 
 card.addEventListener("click", handleClick) 
 function handleClick(event){   mainLink.click(); }

Temporarily, this introduces the problem that we can’t select the text, which we’ve been trying to fix this whole time. Here’s the trick: we’ll use the relatively less-known web API window.getSelection. From MDN:

The Window.getSelection() method returns a Selection object representing the range of text selected by the user or the current position of the caret.

Although, this method returns an Object, we can convert it to a string with toString().

const isTextSelected = window.getSelection().toString()

With one line and no complicated kung-fu tricks with event listeners, we know if the user has selected text. Let’s use that in our handleClick function.

const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') 
 card.addEventListener("click", handleClick) 
 function handleClick(event){   const isTextSelected = window.getSelection().toString();   if (!isTextSelected) {     mainLink.click();   } }

This way, the main link can be clicked when no text selected, and all it took was a few lines of JavaScript. This satisfies our requirements:

  1. The whole thing is linked and clickable.
  2. It is able to contain more than one link.
  3. This content is semantic so assistive tech can understand it.
  4. The text should be selectable, like regular links.
  5. Things like right-click and keyboard shortcuts should work with it
  6. Its elements should be focusable when tabbing.

Here’s the final demo with all the JavaScript code we have added:

I think we’ve done it! Now you know how to make a perfect clickable card component.

What about other patterns? For example, what if the card contains the excerpt of a blog post followed by a “Read More’ link? Where should that go? Does that become the “main” link? What about image?

For those questions and more, here’s some further reading on the topic:

The post Block Links: The Search for a Perfect Solution appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

WordPress Block Transforms

This has been the year of Gutenberg for us here at CSS-Tricks. In fact, that’s a goal we set at the end of last year. We’re much further along that I thought we’d be, authoring all new content in the block editor¹, enabling the block editor for all content now. That means when we open most old posts, we see all the content in the “Classic” block. It looks like this:

A post written on CSS-Tricks before we were using the block editor.

The entire contents of the post is in a single block, so not exactly making any use of the block editor. It’s still “visual,” like the block editor, but it’s more like the old visual editor using TinyMCE. I never used that as it kinda forcefully mangled HTML in a way I didn’t like.

This is the #1 thing I was worried about

Transforming a Classic block into new blocks is as trivial as selecting the Classic block and selecting the “Convert to Blocks” option.

Select the option and the one block becomes many blocks.

How does the block editor handle block-izing old content, when we tell it to do that from the “Convert to Blocks” option? What if it totally screws up content during the conversion? Will we ever be able to switch?

The answer: it does a pretty darn good job. But… there are still issues. Not “bugs” but situations where we have custom HTML in our old content and it doesn’t know what to do with it — let alone how to convert it into exactly the blocks we wish it would. There is a way!

Basic Block Transforms

That’s where this idea of “Block Transforms” comes in. All (well, most?) native blocks have “to” and “from” transformations. You’re probably already familiar with how it manifests in the UI. Like a paragraph can transform “to” a quote and vice versa. Here’s a super meta screenshot of this very paragraph:

Those transforms aren’t magic; they are very explicitly coded. When you register a block, you specify the transforms. Say you were registering your own custom code block. You’d want to make sure that you could transform it…

  • From and to the default built-in code block, and probably a handful of others that might be useful.
  • Back to the built-in code block.

Which might look like:

registerBlockType("my/code-block", {   title: __("My Code Block"),   ...   transforms: {     from: [       {         type: "block",         priority: 7,         blocks: ["core/code", "core/paragraph", "core/preformatted"],         transform: function (attributes) {           return createBlock("my/code-block", {             content: attributes.content,           });         },       },     ],     to: [       {         type: "block",         blocks: ["core/code"],         transform: ({ content }) => createBlock("core/code", { content }),       },     ],        ...

Those are transforms to and from other blocks. Fortunately, this is a pretty simple block where we’re just shuffling the content around. More complex blocks might need to pass around more data, but I haven’t had to deal with that yet.

The more magical stuff: Block Transforms from raw code

Here’s the moment of truth for old content:

The “Convert to Blocks” option.

In this situation, blocks are being created not from other blocks, but from raw code. Quite literally, the HTML is being looked at and choices are being made about what blocks to make from chunks of that HTML. This is where it’s amazing the block editor does such a good job with the choices, and also where things can go wrong and it can fail, make wrong block choices, or mangle content.

In our old content, a block of code (a super very important thing) in a post would look like this:

<pre rel="JavaScript"><code class="language-javascript" markup="tt">   let html = `<div>cool</div>`; </code></pre>

Sometimes the block conversion would do OK on those, turning it into a native code block. But there were a number of problems:

  1. I don’t want a native code block. I want that to be transformed into our own new code block (blogged about that here).
  2. I need some of the information in those attributes to inform settings on the new block, like what kind of code it is.
  3. The HTML in our old code blocks was not escaped and I need it to not choke on that.

I don’t have all the answers here, as this is an evolving process, but I do have some block transforms in place now that are working pretty well. Here’s what a “raw” transform (as opposed to a “block” transform) looks like:

registerBlockType("my/code-block", {   title: __("My Code Block"),   // ...   transforms: {     from: [       {         type: "block",         priority: 7,         // ...       },       {         type: "raw",         priority: 8,         isMatch: (node) =>           node.nodeName === "PRE" &&           node.children.length === 1 &&           node.firstChild.nodeName === "CODE",         transform: function (node) {           let pre = node;           let code = node.querySelector("code");            let codeType = "html";           if (pre.classList.contains("language-css")) {             codeType = "css";           }           if (pre.getAttribute("rel") === "CSS") {             codeType = "css";           }           if (pre.classList.contains("language-javascript")) {             codeType = "javascript";           }           if (code.classList.contains("language-javascript")) {             codeType = "javascript";           }           // ... other data wrangling...            return createBlock("csstricks/code-block", {             content: code.innerHTML,             codeType: codeType,           });         },       },     ],     to: [       // ...      ],        // ...  }

That isMatch function runs on every node in the HTML it finds, so this is the big opportunity to return true from that in the special situations you need to. Note in the code above that I’m specifically looking for HTML that looks like <pre ...><code ...>. When that matches, the transform runs, and I can return a createBlock call that passes in data and content I extract from the node with JavaScript.

Another example: Pasting a URL

“Raw” transforms don’t only happen when you “Convert to Blocks.” They happen when you paste content into the block editor too. You’ve probably experienced this before. Say you have copied some table markup from somewhere and paste it into the block editor -— it will probably paste as a table. A YouTube URL might paste into an embed. This kind of thing is why copy/pasting from Word documents and the like tend to work so well with the block editor.

Say you want some special behavior when a certain type of URL is pasted into the editor. This was the situation I was in with our custom CodePen Embed block. I wanted it so if you pasted a codepen.io URL, it would use this custom block, instead of the default embed.

This is a “from” transform that looks like this:

{   type: "raw",   priority: 8, // higher number to beat out default   isMatch: (node) =>     node.nodeName === "P" &&     node.innerText.startsWith("https://codepen.io/"),    transform: function (node) {     return createBlock("cp/codepen-gutenberg-embed-block", {       penURL: node.innerText,       penID: getPenID(node.innerText), // helper function     });   }, }

So…

Is it messy? A little. But it’s as powerful as you need it to be. If you have an old site with lots of bespoke HTML and shortcodes and stuff, then getting into block transforms is the only ticket out.

I’m glad I went to WPBlockTalk and caught K. Adam White’s talk on shortcodes because there was just one slide that clued me into that this was even possible. There is a little bit of documentation on it.

One thing I’d like to figure out is if it’s possible to run these transforms on all old content in the database. Seems a little scary, but also like it might be a good idea in some situations. Once I get my transformations really solid, I could see doing that so any old content ready-to-go in the block editor when opening it up. I just have no idea how to go about it.

I’m glad to be somewhat on top of this though, as I friggin love the block editor right now. It’s a pleasure to write in and build content with it. I like what Justin Tadlock said:

The block system is not going anywhere. WordPress has moved beyond the point where we should consider the block editor as a separate entity. It is an integral part of WordPress and will eventually touch more and more areas outside of the editing screen.

It’s here to stay. Embracing the block editor and bending it to our will is key.

  1. What are we calling it anyway? “Gutenberg” doesn’t seem right anymore. Feels like that will fade away, even though the development of it still happens in the Gutenberg plugin. I think I’ll just call it “the block editor” unless specifically referring to that plugin.

The post WordPress Block Transforms appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How to Use Block Variations in WordPress

WordPress 5.4 was released not so long ago and, along with other improvements and bug fixes, it introduced a feature called Block Variations. I had a chance to use it on one of my recent projects and am so pleasantly surprised with how smart this feature is. I actually think it hasn’t received the attention it deserves, which is why I decided to write this article.

What is a Block Variation?

Block Variations allow developers to define instances of existing blocks. An example that you’ll see below is a quote block. Perhaps your site has three variations of how to display a quote on your site. A Block Variation can be created for each one so that they are all styled differently. This sounds awfully familiar with how Block Styles, but the concept of variations goes a bit further than that, as we’ll see.

How are Block Variations different from Block Styles?

Fair question. Block variations appear in the inserter as separate blocks with unique names and (optionally) icons and can have pre-filled custom attributes, and inner blocks.

Block Styles are designed to alter the look of the block. In fact, a Block Style is a fancy way of adding a custom class to a block using the Block options in the post editor.

The difference is clear when you consider how each one is used in the post editor. Let’s say we register a new Block Style called “Fancy Quote.” We do that by extending the core “Quote” block like this example from the Block Editor Handbook:

wp.blocks.registerBlockStyle(   'core/quote',   {     name: 'fancy-quote',     label: 'Fancy Quote'   }, );

This adds a .is-style-fancy-quote class to the Quote block settings in the post editor.

Screenshot of the Block options in the WordPress post editor highlighting the options for a quote block. A "Fancy Quote" option is listed under Styles and the custom class name is in an Additional CSS Classes field.
We now have a Fancy Quote option in the Block options under “Styles” and the class for it filled in for us.

Even though it sort of sounds like it would do the same thing (which it technically can), a Block Variation can be used to pre-fill custom attributes (including custom classes) and inner blocks. They’re actually registered as separate blocks.

Let’s take a closer look at the API and what block variations can do.

Creating a Block Variation

The API for registering Block Variations is very similar that of the Block Style we just looked at:

wp.blocks.registerBlockVariation(   'core/quote',   {     name: 'fancy-quote',     title: 'Fancy Quote',   }, );

The registerBlockVariation function accepts the name of the block (in our case it is core/quote) and an object (or an array of objects) describing the variation(s).

The code above doesn’t do much by default, but it does add “Fancy Quote” to the list of available blocks.

Showing the Fancy Quote variation in the WordPress Block Inserter.
We now have two different “quote” blocks available to drop into the post.

To take full advantage of the variation. we need to provide more details in the object describing it. The list is covered in the Make WordPress post, but I’ll share it here and provide additional comments.

  • name – The unique and machine-readable name of the variation. Following the examples on Github and Make post it’s safe to assume that the best practice is to use kebab-case for naming variations.
  • title – A human-readable variation title. This is what appears under the icon in the Inserter.
  • description – A detailed variation description. Appears in the Inserter as well. If empty, the default block description will be used. (Optional)
  • icon – An icon for the variation. Can be a Dashicons slug, an SVG or an object. Follows the same declaration pattern as in registerBlockType. (Optional)
  • isDefault – Indicates whether the current variation is the default one. Defaults to false. In case of our example, if we set it to true, the Fancy Quote block will be the only Quote block available in the inserter. (Optional)
  • attributes – Values that override block attributes. These are block-specific. You can set the level for the Heading block or height for Spacer, for example.
  • innerBlocks – Initial configuration of nested blocks. Only applies to blocks that allow inner blocks in the first place, like Columns, Cover, or Group. We’ll cover this in one of the examples. (Optional)
  • example – Example provides structured data for the block preview. You can set it to undefined to disable the preview shown for the block type. This is the same as the example field in registerBlockType. (Optional) There’s more information available on this parameter.
  • scope – The list of scopes where the variation is applicable. When not provided, it assumes all available scopes. Available options are block and inserter. We’ll cover this in detail in one of the examples.

Many of you may wonder why we need this extra layer of abstraction. Let me try to answer that with a few use cases (one form my recent project).

Use case: Buttons with different widths

Let’s say you have a design system with two types of buttons: Fill and Outline.

Two buttons, one with a green fill and one with a green border. Both say Learn More.
Fill and Outline button styles in the design system

Lucky you, because these are the default styles for buttons in WordPress. No need to register any new styles or hack the editor. All you have to do is write some CSS for each style and call it a day. Life is good and everybody’s happy.

But then you look in the design spec again and notice that there is a little twist. The buttons come in three widths: Regular, Wide, and Full.

The same green buttons but with additional variations at two different widths for a totally of six buttons.
Fill and Outline button styles with different width variations

Dammit! You are a little upset because you now have two options:

  1. Write two extra classes for the new button sizes (say, .is-wide and .is-full), then teach the client to use the Advanced panel in the editor to add those classes and write a manual where you explain what each class does. Or…
  2. Register four(!) new styles that go in the Block options: Fill Wide, Fill Full, Outline Wide, and Outline Full.

Neither of those options are exactly elegant. (BTW, what is Fill Full exactly? Quite an unfortunate mouthful!)

There are two more options that I didn’t include in the list:

  • Filter the button block and add a custom width control to it
  • Build a custom block from scratch.

These obviously feel like heavy lifts for such a simple task.

Enter Block Variations! By adding just two variations, Full and Wide, we can keep things clean and simple:

wp.blocks.registerBlockVariation(   'core/buttons',   [     {       name: 'wide',       title: 'Wide Buttons',       attributes: {         className: 'is-wide'       },   },   {       name: 'full',       title: 'Full Buttons',       attributes: {         className: 'is-full'       },     }   ] );

This is the same as adding a custom class to the Buttons block, but in a neat and elegant way that can be dropped directly into a post from the Block Inserter:

Showing the Wide and Full button variations in the WordPress Block Inserter.
Button variations in the inserter

Life is good and everybody is happy again! So what did we learn from this example?

  • It shows that Block Variations are not designed to replace Block Styles. In fact, they can work pretty well together even if the variation just adds a class to a block.
  • It demos how to register multiple variations in a single declaration.

Use case: Repeating column layouts

Let’s say you are a designer and have a portfolio website with case studies. Each case study has an intro section with the name of the project, client information, and a description of your role on the project. It might look something like this:

Showing three columns, one that says Website Design, one that says Clients, and one says Role. Each one represents a column we want on the page.
The type of work (left), who it was for (center) and your role on it (right)

The problem is that it’s a bit tedious to build this part of the layout every time you create a new portfolio case study — especially because the Client and My Role headings never change. You are only editing the main title and two paragraphs.

With Block Variations, you can create a variation of a core Columns block called Project Intro that will have the columns, and inner blocks already defined. This example is a bit more involved, so we’ll build it out step-by-step.

Let’s start with registering the variation:

wp.blocks.registerBlockVariation(   'core/columns', {     name: 'project-intro',     title: 'Project Intro',     scope: ['inserter'],     innerBlocks: [       ['core/column'],       ['core/column'],       ['core/column'],     ],   } );

We are taking this example a bit further than the first one, so why not add a custom portfolio icon from the Dashicons library that’s baked right into WordPress? We do that with the icon property.

wp.blocks.registerBlockVariation(   'core/columns', {     name: 'project-intro',     title: 'Project Intro',     icon: 'portfolio',     scope: ['inserter'],     innerBlocks: [       ['core/column'],       ['core/column'],       ['core/column'],     ],   } );

This will make the block available in the block menu with our icon:

Variation with a custom icon in the Block Inserter.

The next important thing happens on where we add inner blocks:

wp.blocks.registerBlockVariation(   'core/columns', {     name: 'project-intro',     title: 'Project Intro',     icon: 'portfolio',     scope: ['inserter'],     innerBlocks: [       ['core/column'],       ['core/column'],       ['core/column'],     ],   } );

But this only gives us three empty columns. Let’s add starter content and inner blocks to each of them. We can use the same pattern we use to declare a block template in the InnerBlocks component. We can add an object with block attributes as a second element in the array describing the block, and an array of inner blocks as the third element.

The first column will look like this:

['core/column', {}, [   ['core/heading', { level: 2, placeholder: 'Project Title'} ], ]]

…and the complete block variation is like this:

wp.blocks.registerBlockVariation (   'core/columns', {     name: 'project-intro',     title: 'Project Intro',     icon: 'portfolio',     scope: ['inserter'],     innerBlocks: [       ['core/column', {}, [         ['core/heading', { level: 2, placeholder: 'Project Title' }],       ]],       ['core/column', {}, [         ['core/heading', { level: 3, content: 'Client' }],         ['core/paragraph', { placeholder: 'Enter client info' }],       ]],       ['core/column', {}, [         ['core/heading', { level: 3, content: 'My Role' }],         ['core/paragraph', { placeholder: 'Describe your role' }],       ]],     ],   } );

Cool, now we can insert the whole section with just one click. Okay, it’s a few clicks, but still faster than without using the variations.

So what did we learn from this example?

  • And demos how to use the inner blocks within the variation
  • It shows how to define a custom icon for a variation

Use case: Four-column layout

You already know that columns are a default block type, and that there are a handful of options for different types of columns. A four-column layout isn’t one of them, so we can build that. But this introduces a new concept as well: scoping in context of block variations.

Some core blocks, like Columns, already offer variations out of the box. You can choose one of them after you insert the block on the page:

Showing the columns block inserted with 5 different layout options to display up to 3 columns at varying widths.
Block-scoped variations

Let’s say you use a four-column layout on your website as often as you use two-column one. That’s unfortunate, because there is no shortcut button to create four-column layout. Creating one is a bit annoying because it takes extra clicks to get to the Columns control after the block is inserted:

Showing the slider control to change the number of columns in the Block settings.

So, what can you do to improve this workflow? Right, you can add a Block Variation that will create a four-column layout. The only difference this time, compared to previous examples, is that it makes much more sense to include this variation inside the block placeholder, next to all other column layouts.

That is exactly what the scope option is for. If you set it to [block], the variation will not appear in the Block Inserter but in the variations once the block has been inserted.

wp.blocks.registerBlockVariation(   'core/columns', {     name: 'four-columns',     title: 'Four columns; equal split',     icon: <svg ... />,     scope: ['block'], // Highlight     innerBlocks: [       ['core/column'],       ['core/column'],       ['core/column'],       ['core/column'],     ],   } );
Four-column layout variation scoped to the block.
Hey, now we have a four-column option!

Isn’t that sweet?!

I’ve omitted the full SVG code for the icon, but it’s available if you need it.

To sum up scope: If it isn’t declared, the variation will appear in the Block Inserter and inside the block placeholder — specifically for blocks that support block-scoped variations. 

If we were to remove the scope parameter from the example above, here’s how the variation would appear in the inserter:

Four-column block variation in the block inserter.
Keep in mind that the icon sizes for variations within the block and and the block icons size are different. The custom icon for columns was intended for the block scope, that’s why it looks a bit out-of-place in this example.

So what did we learn from this example?

  • It explains the difference between the block and inserter scope for the variation.
  • We learned how to use SVG for variation icon.

That’s it!

As you can see, Block Variations are pretty powerful for building a lot of things, from different variations of buttons to complete page layouts.

I’d like to wrap this up with a quick recap of different APIs for block customizations and when to use them:

  • Use Block Styles if you need to alter the appearance of the block and adding a CSS class is enough for that.
  • Use Block Variations if you need to specify the default attributes for the block and/or add inner blocks to it.
  • If that’s not enough and you need to change the markup of the block, you are probably looking into filtering the block or creating a new one from scratch.

If you’ve had a chance to play with Block Variation, let me know what you think of them in the comments!

Resources

The post How to Use Block Variations in WordPress appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Block Links Are a Pain (and Maybe Just a Bad Idea)

As we noted in our complete guide, you can put an <a href=""> link around whatever chunks of HTML you like. Let’s call that a “block link.” Like you are wanting to link up an entire “Card” of content because it makes a big clickable target.

<a href="/article/"> <!-- display: block; -->   <div class="card">     <h2>Card</h2>     <img src="..." alt="...">     <p>Content</p>   </div> </a>

On that, Adrian Roselli:

Perhaps the worst thing you can do for a block link is to wrap everything in the <a href>.

[…] for a screen reader user the entire string is read when tabbing through controls. In the following example, the first link contains the heading, image (without declaring it as an image), and block of text, taking about 25 seconds to read before announcing it as a link. When tabbing, you do not always know the control type until the accessible name is complete.

(The example is a pretty normal looking card with a header, image, and paragraph.)

So don’t do that.

The alternative is to let the link be “normal” like just the header.

<div class="card">   <h2><a href="/article/">Article</a></h2>   <img src="..." alt="...">   <p>Content</p> </div>

The extending the “clickable area” of the link to cover the entire area.

.card {   position: relative; } .card h2 a::after {   content: "";   position: absolute;   top: 0;   left: 0;   width: 100%;   height: 100%; }

That works for the clickable area and solves the penalty to screen reader users.

But there is another problem that hurts both of these solutions, and it’s text selection. You can’t just put your cursor somewhere in the card and select text normally. The click activates the link, waiting for you to mouseup while still on the link to trigger it. You don’t get the ability to select inner parts of the text as you would probably expect. It doesn’t prevent the ability to select the text at all, but it makes it awkward and annoying.

I’m not sure that is easily solveable. Perhaps there is some exotic JavaScript solution that can detect if you’ve started to select text and then not trigger a click, but if you click without dragging then it does go to the link. Something about that is a bit red-flaggy to me though.

All in all, I’d say block links are just a bad idea. But I’d love to be proven wrong and see a really good implementation that solves all these issues.

The post Block Links Are a Pain (and Maybe Just a Bad Idea) appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Recreating the CodePen Gutenberg Embed Block for Sanity.io

Chris recently put out a neat CodePen Embed Block for the Gutenberg editor in WordPress. It allows you to embed a Pen just by dropping in its URL. From there, you get access to control the size, theme, and the default tabs that render on initial load. Super neat!

Having a live preview of the embedded Pen while writing is so handy!

But it got me thinking: How difficult would it be to recreate it with Sanity Studio’s Portable Text editor? (Spoiler: Not that difficult). Since I already knew how to do it, it took me under seven minutes from start to finish. This tutorial takes you through how to get up and running with a studio, and how to add the schemas and the custom preview component for a CodePen embed.

That felt so cool that I want to teach you how to do it as well. Let’s dive right into it.

Getting Sanity Studio up and running locally

First, you’ll need to install Sanity Studio locally on your machine. In this tutorial we will be using the blog studio that you can initiate from the command line, but you can also check out the different starters on sanity.io/create. You should be able to tag along with one of those too.

This tutorial assumes that you have a bit of knowledge of JavaScript. It will use a bit of React, but only a small part. You should have installed node and npm if you haven’t already.

Oh, and you’ll want the Sanity CLI, which you can snag with the command line:

npm install --global @sanity/cli

Once the installation is done, you can initiate a new Sanity Studio with a new project by running the command sanity init. It will let you log in with your Google or GitHub account (or make a new account with an email/password). Give your project a name and follow the instructions. When given the options for a project template, choose the blog one:

? Select project template   Movie project (schema + sample data)   E-commerce (schema + sample data) ❯ Blog (schema)   Clean project with no predefined schemas

After completing the steps, change directory (cd) into the new project folder and open it in your favorite code editor. To start the developer server that will also hot reload your studio when you make changes, run sanity start. To stop this server, you press ctrl + C in most command line tools.

Adding the schemas for a CodePen embed

Schemas define which document types that are available in the Studio, and which input fields they have. These schemas are defined in JavaScript objects that you import into the schemas.js file, where they are exported as a function that the Studio translates into its UI. There’s a lot you can do with these schemas, but in this tutorial, we will keep it reasonably simple.

Start with adding a new file inside /yourproject/schemas called codepen.js. Then type in this code:

export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

Then you can go to /yourproject/schemas/schema.js and add the following two lines of code to it:

import createSchema from "part:@sanity/base/schema-creator"; import schemaTypes from "all:part:@sanity/base/schema-type";  import blockContent from "./blockContent"; import category from "./category"; import post from "./post"; import author from "./author"; import codepen from "/codepen.js"; // <= first import the object  export default createSchema({   name: "default",   types: schemaTypes.concat([     post,     author,     category,     blockContent,     codepen // <= add it to the schema types array   ]) });

So what did we just do? Well, we have now made this CodePen object available as a type in other schemas in the Studio. In other words, you can now add type: 'codepen' to get those fields anywhere else in the schema code where you add fields. Adding this type to the rich text field is also our next step. Hang on!

Adding the CodePen field to the rich text editor

Before diving into the code bit, let us take a step back and look at what is going on in terms of the data formats we operate with, and how WordPress and Sanity differ slightly.

While Gutenberg stores rich text as JSON in its runtime (which is great!), what developers end up dealing with is mostly this content as HTML and JSON objects inside of HTML comments.

Sanity stores and distributes rich text content as Portable Text, which developers then serializes in their frontends. That means that you get fine-grained control over how rich text content is rendered by letting you use custom components for your favorite framework, either it's ReactVueSvelte, or .NETPHP, or even Markdown.

In other words, you store your content as structured data in Sanity’s backend, and then decide how you want to use the data inside your frontend components. But enough exposition, let's get back to the code!

Open /schemas/blockContent.js and notice that it's of the type array. Yes, rich text is an array of different types, where one of them has to be of the block type (in which text paragraphs are stored). So the simplest way of making rich text is the following schema definition:

export default {   name: "body",   type: "array",   title: "Body",   of: [     {       type: "block"     }   ] };

Now, blockContent.js has a bunch of more stuff. You can see styles, lists, marks, and so on. All defining which properties should be available for the author. In the top array, there are two types block and image. We are going to add the third one, codepen:

export default {   title: "Block Content",   name: "blockContent",   type: "array",   of: [     {       type: "block"       // ...     },     {       type: "image",       options: { hotspot: true }     },     {       type: "codepen"     }   ] };

Save the file, and that's it! If you now run sanity start in your command line (assuming you haven't already), and open the Studio on https://localhost:3333, you should be able to find your new field in the rich text editor under the "post" type:

Sanity Studio with a CodePen button in the Rich Text editor.

If you try out the new button, you'll get a modal with the URL field that you defined in the previous section. Feel free to add the URL from a cool CodePen that you have found. We will use this one from the legendary Sara Drasner; it's pretty cool.

Just showing the URL value in the editor isn't especially inspiring, though. So let's go ahead and add the actual CodePen embed so we can interact with it directly in the editor!

Adding the CodePen embed as a preview

Open /yourproject/schemas/codepen.js again. Now we are going to make a small React component for our preview. Start by importing React in the top, and the boilerplate for the React component that we will turn into the embed:

import React from "react";  const CodePenPreview = ({ value }) => {   return <pre>{JSON.stringify(value, null, 2)}</pre>; };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

The JSON.stringify stuff is a temporary little way of outputting the incoming data in a readable manner. You could also use console.log(value), but who has time to open the developer console?

Now you must tell Sanity how to use this component for the preview. As well as which of the fields in the object it should select for the value in the preview component.

import React from "react";  const CodePenPreview = ({ value }) => {   return <pre>{JSON.stringify(value, null, 2)}</pre>; };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   preview: {     select: {       url: "url"     },     component: CodePenPreview   },   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     }   ] };

The editor should look something like this after you saved your changes:

Cool! Now we want to take the url value and somehow integrate it with a CodePen embed. The easiest way to go about this is to fit the markup for CodePen’s iFrame embed, and fit into our preview component in React.

The original iFrame element will look like this:

<iframe height="265" style="width: 100%;" scrolling="no" title="React Animated Page Transitions" src="https://codepen.io/sdras/embed/gWWQgb?height=265&theme-id=dark&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true">   See the Pen <a href='https://codepen.io/sdras/pen/gWWQgb'>React Animated Page Transitions</a> by Sarah Drasner   (<a href='https://codepen.io/sdras'>@sdras</a>) on <a href='https://codepen.io'>CodePen</a>. </iframe>

If we paste this snippet into our preview component, it will almost work. In order to make it JSX-compatible you'll have to some few changes to some of the HTML-attributes. Make sure that you change:

  • style="width: 100%;" to style={{width: "100%"}}
  • frameborder="no" to frameBorder="no"
  • allow-transparency="true" to allowTransparency
  • allow-fullscreen="true" to allowFullScreen

You can remove the content (links, etc.) inside of the iframe, because it isn't particularly useful inside the studio. What we should end up with is something like this:

import React from "react"; import Codepen from "react-codepen-embed";  const CodePenPreview = ({ value }) => {   return (     <iframe       height="265"       style={{ width: '100%' }}       scrolling="no"       title="React Animated Page Transitions"       src="https://codepen.io/sdras/embed/gWWQgb?height=370&theme-id=dark&default-tab=js,result"       frameBorder="no"       allowTransparency       allowFullScreen     />); };  // ...

When saved, we should be able to see the CodePen embed inside the rich text editor:

Notice that the iFrame has an embed URL with some parameters for how it should be displayed. Of course, we could've asked someone to dive into CodePen to obtain this URL, but it's probably better for to use the regular one. We'll take the effort to reassemble into what we need:

The last part is to take the URL from the field, and get the hash and user out of it.

We split the URL string on forward slashes into an array. Then we use array destructuring to assign the different array elements to a variable. Since we only need the user and the hash we leave the other positions empty. This method isn't bulletproof, as it assumed a specific format for the URL, but it works for this example. Then we reassemble the embedUrl by using template literals.

import React from "react";  const CodePenPreview = ({ value }) => {   const { url } = value;   const splitURL = url.split("/");   // [ 'https:', '', 'codepen.io', 'sdras', 'pen', 'gWWQgb' ]   const [, , , user, , hash] = splitURL;   const embedUrl = `https://codepen.io/$ {user}/embed/$ {hash}?height=370&theme-id=dark&default-tab=result`;   return (     <iframe       height="370"       style={{ width: '100%' }}       scrolling="no"       title="CodePen Embed"       src={embedUrl}       frameBorder="no"       allowTransparency       allowFullScreen     />   ); }; // ...

Save the changes and voilá; we're pretty much done with the custom CodePen block!

Taking it further

Now, you probably noticed that Chris had put more settings into his custom block. Nothing is stopping us from doing the same! If we look up the documentation for the React CodePen embed component that we installed, we'll find a table of properties that it can take. We can add these as fields in the schema definition. For example, if we wanted to add the themeId, we could do it as follows:

import React from "react"; import Codepen from "react-codepen-embed";  const CodePenPreview = ({ value }) => {   const { url, themeId = "dark" } = value; // <= add themeId here, default it to "dark"   const splitURL = url.split("/");   // [ 'https:', '', 'codepen.io', 'sdras', 'pen', 'gWWQgb' ]   const [, , , user, , hash] = splitURL;   const embedUrl = `https://codepen.io/$ {user}/embed/$ {hash}?height=370&theme-id=$ {themeId}&default-tab=result`; // <= add themeId here   return (     <iframe       height="370"       style={{ width: '100%' }}       scrolling="no"       title="CodePen Embed"       src={embedUrl}       frameBorder="no"       allowTransparency       allowFullScreen     />   ); };  export default {   name: "codepen",   type: "object",   title: "CodePen Embed",   preview: {     select: {       url: "url",       themeId: "themeId" // <= add themeId here     },     component: CodePenPreview   },   fields: [     {       name: "url",       type: "url",       title: "CodePen URL"     },     // Add the new field below     {       name: "themeId",       type: "string",       title: "Theme ID",       description: 'You can use "light" and "dark" also.'     }   ] };

Conclusion

We just looked at how schemas for Sanity Studio work, and learned how to make previews for custom components to boot! Hopefully, you now know enough to make pretty much any custom component with a preview using these same principles. If you do, I would love to know about it either on Twitter or in the comments.

The post Recreating the CodePen Gutenberg Embed Block for Sanity.io appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Web Component for a Code Block

We’ll get to that, but first, a long-winded introduction.

I’m still not in a confident place knowing a good time to use native web components. The templating isn’t particularly robust, so that doesn’t draw me in. There is no state management, and I like having standard ways of handling that. If I’m using another library for components anyway, seems like I would just stick with that. So, at the moment, my checklist is something like:

  • Not using any other JavaScript framework that has components
  • Templating needs aren’t particularly complex
  • Don’t need particularly performant re-rendering
  • Don’t need state management

I’m sure there is tooling that helps with these things and more (the devMode episode with some folks from Stencil was good), but if I’m going to get into tooling-land, I’d be extra tempted to go with a framework, and probably not framework plus another thing with a lot of overlap.

The reasons I am tempted to go with native web components are:

  • They are native. No downloads of frameworks.
  • The Shadow DOM is a true encapsulation in a way a framework can’t really do.
  • I get to build my own HTML element that I use in HTML, with my own API design.

It sorta seems like the sweet spot for native web components is design system components. You build out your own little API for the components in your system, and people can use them in a way that is a lot safer than just copy and paste this chunk of HTML. And I suppose if consumers of the system wanted to BYO framework, they could.

So you can use like <our-tabs active-tab="3"> rather than <div class="tabs"> ... <a href="#3" class="tab-is-active">. Refactoring the components certainly gets a lot easier as changes percolate everywhere.

I’ve used them here on CSS-Tricks for our <circle-text> component. It takes the radius as a parameter and the content via, uh, content, and outputs an <svg> that does the trick. It gave us a nice API for authoring that abstracted away the complexity.

So!

It occurred to me a “code block” might be a nice use-case for a web component.

  • The API would be nice for it, as you could have attributes control useful things, and the code itself as the content (which is a great fallback).
  • It doesn’t really need state.
  • Syntax highlighting is a big gnarly block of CSS, so it would be kinda cool to isolate that away in the Shadow DOM.
  • It could have useful functionality like a “click to copy” button that people might enjoy having.

Altogether, it might feel like a yeah, I could use this kinda component.

This probably isn’t really production ready (for one thing, it’s not on npm or anything yet), but here’s where I am so far:

Here’s a thought dump!

  • What do you do when a component depends on a third-party lib? The syntax highlighting here is done with Prism.js. To make it more isolated, I suppose you could copy and paste the whole lib in there somewhere, but that seems silly. Maybe you just document it?
  • Styling web components doesn’t feel like it has a great story yet, despite the fact that Shadow DOM is cool and useful.
  • Yanking in pre-formatted text to use in a template is super weird. I’m sure it’s possible to do without needing a <pre> tag inside the custom element, but it’s clearly much easier if you grab the content from the <pre>. Makes the API here just a smidge less friendly (because I’d prefer to use the <code-block> alone).
  • I wonder what a good practice is for passing along attributes that another library needs. Like is data-lang="CSS" OK to use (feels nicer), and then convert it to class="language-css" in the template because that’s what Prism wants? Or is it better practice to just pass along attributes as they are? (I went with the latter.)
  • People complain that there aren’t really “lifecycle methods” in native web components, but at least you have one: when the thing renders: connectedCallback. So, I suppose you should do all the manipulation of HTML and such before you do that final shadowRoot.appendChild(node);. I’m not doing that here, and instead am running Prism over the whole shadowRoot after it’s been appended. Just seemed to work that way. I imagine it’s probably better, and possible, to do it ahead of time rather than allow all the repainting caused by injecting spans and such.
  • The whole point of this is a nice API. Seems to me thing would be nicer if it was possible to drop un-escaped HTML in there to highlight and it could escape it for you. But that makes the fallback actually render that HTML which could be bad (or even theoretically insecure). What’s a good story for that? Maybe put the HTML in HTML comments and test if <!-- is the start of the content and handle that as a special situation?

Anyway, if you wanna fork it or do anything fancier with it, lemme know. Maybe we can eventually put it on npm or whatever. We’ll have to see how useful people think it could be.

The post Web Component for a Code Block appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Getting Acquainted With Svelte, the New Framework on the Block

For the last six years, Vue, Angular, and React have run the world of front-end component frameworks. Google and Facebook have their own sponsored frameworks, but they might leave a bitter taste for anyone who advocates for an open and unbiased web. Vue is another popular framework that has multiple sponsors, but isn’t run by a single corporation, which may be attractive to some folks.

There’s another player in the framework space that’s gaining attention and operates very much in the same spirit as Vue as far adopting an open MIT license: Svelte.

Svelte has been covered here on CSS-Tricks before, like Ollie Williams’ excellent overview of how it can be used to write more convenient, component-based CSS. This article is going to zoom out a bit and provide a little more context about Svelt, as well as how it differentiates itself from other frameworks, and how to implement it in your own projects.

What makes Svelte different?

I can confidently say that Svelte has been the easiest JavaScript component library to learn and start putting to use in a productive way.

— Jeff Delaney, from Svelte Realtime Todo List with Firebase

OK, so Svelte is a JavaScript component library. But so is React. And Angular. And Vue. What makes Svelte stand out from the bunch?

Svelte is trying to do a few things that are different from the rest:

  1. All the code is compiled ahead of time.
  2. There is no virtual DOM.
  3. CSS scoping is baked in.

Let’s break those down a bit because they significantly distinguish Svelte from other front-end frameworks.

All the code is compiled ahead of time.

Svelte is a compiler, meaning that the code in Svelte files gets converted from an easier-to-write hybrid language that mixes HTML, JavaScript, and CSS into lower-level optimized JavaScript, HTML, and CSS files.

This is very similar to the way C# gets compiled down to bytecode, or how Typescript compiles down to JavaScript. But where traditional compilers tend to go down to one language, Svelte mixes all three.

This makes writing code a lot more flexible, and benefits the client (web browser) as the computation is done when the application is built, not on every browser when the web app is visited.

There is no Virtual DOM.

A DOM (or Document Object Model) is an interface that defines the logical structure of a webpage. It takes HTML and converts it to a structure that can be manipulated and accessed. Chris has a classic post that thoroughly explains it.

The Virtual DOM extends the concept of a DOM by creating a “second” DOM in memory. Like the DOM, this is manipulated and accessed by traditional frameworks (e.g. Angular, Vue, and React). At build, this second “virtual” DOM gets consolidated with the actual DOM, allowing the UI to render.

And what about the Shadow DOM? Well, the Shadow DOM is technically part of the “real” DOM, just in the shadows. As such it is a great tool for isolating chunks of code that don’t leak into or conflict with other elements on the page — a little bit like (but at the same time almost nothing like) an iframe. The shadow DOM is sorta the crux for most component-based front-end frameworks because they leverage the siloed nature of the Shadow DOM to serve specific code to specific elements.

While that isn’t exactly a key selling point of Svelte, it is possible to work with the Shadow DOM experimentally. The Shadow DOM hasn’t really quite caught on in progressive web practices, which is a shame, and probably due to the confusion between drafts and lack of support from IE and Edge.

So, where am I going with all this? The difference between Svelte and other JavaScript frameworks is the lack of a Virtual DOM. That’s important because it contributes to faster apps — faster than frameworks using a Virtual DOM. Yes, the Virtual DOM can be super fast because it only updates parts of the DOM when needed, but as applications grow, the impact of a duplicate DOM stored in memory can have an overall negative impact on performance.

Svelte takes a different approach and does a lot of these heavy calculations at build time. All that heavy lifting in advance, which allows Svelte to surgically insert changes only where needed.

CSS scoping is baked in.

Svelte has built-in styling, which is essential in other modern frameworks. The different between CSS in Svelte and CSS in other frameworks is that Svelte takes the CSS from each component and spits it out to a separate CSS file on build.

A personal gripe I have with most CSS-in-JS approaches is that it seems like an over-engineered solution. Svelte’s approach keeps things lean, vanilla, and encapsulated — while keeping everything where it should be.

For those who love preprocessors, there are plugins, whether it for Sass, Less or Gulp. But since Svelte is still in its infancy, I would recommend using plain ol’ CSS with a minified CSS framework of your choice so you can utilize Svelte’s handy dandy component scoping. 

You could just as easily keep to your usual styling preferences and completely forgo Svelte’s CSS builder. However, I’d argue that is a massive shame, as Svelte’s solution has been extremely clean and enjoyable, at lease in my experience. But anyone who has to work with IE11 (😬) and even older browsers will know that normalizing styles is a must. This is a good place to stop and check out Ollie’s post because he dives much deeper into Svelte’s styling features and advantages.

How Svelte stacks up to other frameworks

We just looked at what how Svelte has a different approach for compiling, interacting with DOM and writing CSS. You might be wondering: how does Svelte compare to other popular frameworks?

There are plenty of comparisons already out there, but suffice to say that Svelte is pretty darn fast. But speed isn’t the only basis for comparison. Instead, let’s do a side-by-side that looks at a broader overview in a format much loved by the development community: a table!

Svelte Vue React Angular (2+)
What is it Compiler Framework Framework Framework
First Commit Nov. 16, 2016 Jul. 29, 2013 May 24, 2013 Sep. 18, 2014
Backing Open source Multiple Sponsors Facebook Google
Community¹ Small Large Massive Large
Satisfaction2 88% 87% 89% 38%

Svelte is in a strong position considering its late entrance and small community. Developer satisfaction is high, while the big three have been seeing recent declines. The Svelte community is small, but growing, and the code is open source which is a huge plus for the overall web community.

Let’s look at an example of using Svelte

I hope that I have convinced you that Svelte is worth at least a try. If so, let’s fire up the terminal and try a real-world examples of an everyday use case: implementing the Intersection Observer. If you’ve ever run a Lighthouse report, it may have been shouted at you for not using passive scroll events. That may be the most boring sentence I have written in my life, but it’s scores points for performance and isn’t overly complicated to do with the Intersection Observer in Svelte.

Let’s skip all the installation and setup stuff because we can avoid it with REPL, the online editor Svelte uses to demonstrate the framework on its site. The standard “Hello world” boilerplate is in there. Go ahead and download the ZIP file of the app, in the upper-right corner of the screen.

Now, unzip the file and cd into the folder from the terminal and run  npm -i to initialize the project. Once that’s done, do npm run build and you’ll get a copy of your lightweight miniature Svelte “Hello, world!” app.

Now we can get into the actual task of adding the IntersectionObserver.

First, we import the code that has already kindly been written by the Svelte team. It’s in the source code of the svelte.dev git repo (the inner cogs of which make for fascinating reading).

<script>   import { onMount } from 'svelte';   export let once = false;   export let top = 0;   export let bottom = 0;   export let left = 0;   export let right = 0;   let intersecting = false;   let container;    onMount(() => {     if (typeof IntersectionObserver !== 'undefined') {       const rootMargin = `$  {bottom}px $  {left}px $  {top}px $  {right}px`;       const observer = new IntersectionObserver(entries => {         intersecting = entries[0].isIntersecting;         if (intersecting && once) {           observer.unobserve(container);         }         }, {           rootMargin       });         observer.observe(container);         return () => observer.unobserve(container);   }    function handler() {     const bcr = container.getBoundingClientRect();     intersecting = (       (bcr.bottom + bottom) > 0 &&       (bcr.right + right) > 0 &&       (bcr.top - top) < window.innerHeight &&       (bcr.left - left) < window.innerWidth     );     if (intersecting && once) {       window.removeEventListener('scroll', handler);     }   }    window.addEventListener('scroll', handler);     return () => window.removeEventListener('scroll', handler);   }); </script>  <style>   div {     width: 100%;     height: 100%;   } </style>  <div bind:this={container}>   <slot {intersecting}></slot> </div>

Stick this in a file called IntersectionObserver.svelte in a src/components folder. Then, reference it from the main Svelte file: App.svelte.

import IntersectionObserver from "../components/IntersectionObserver.svelte";

Now that we have the Intersection Observer available as a component, we can wrap other elements with it.

<IntersectionObserver let:intersecting top={400}>  {#if intersecting}     <section>       This message will Show if it is intersecting     </section>   {:else}     <section>       This message won't Show if it is intersecting     </section>  {/if} </IntersectionObserver>

That’s really it! You can see how the Intersection Observer component allows us to use <IntersectionObserver>  like a wrapper and define where the intersection should trigger, which is 400 pixels from the top in this example. As a reminder, this is all being exported as vanilla JavaScript! Super performant, no funny business. We’re sandwiching JavaScript and HTML together which is cool because we can see what the Intersection Observer is directly affecting, leaving no ambiguity and without being penalized for performance.

The OnMount function is necessary to tell Svelte that this code needs to run within the browser, as the Intersection Observer can’t be figured out ahead of time.

We’ll need to add some styling so that we can experience the observer in action, and we can do that directly in your App.svelte file. This might look super familiar if you have worked with any of the other front-end frameworks:

<style>   .somesection {     display: flex;     align-items: center;     justify-content: center;     width: 100%;     height: 100vh;   }      .somesection.even{     background: #ccc;   }            .content{     text-align: center;     width: 350px;   } </style>

Finally, we can copy and paste our Intersection Observer element four times to create more intersections. That gives us a mini web app that reactively adds and removes content as it comes into view — perfect to use with media, like lazy-loading. Check out a demo of the final result and be sure to crack open DevTools to see the Intersection Observer

Some final thoughts

My personal recommendation is to give Svelte a try. We’ve only scratched the surface of the framework in this article, but having converted my personal website to Svelte, I can confidently say that it is a pleasure to work with. It is performant, has a brilliant VSCode linter, and best of all, is easy to use. It may be small and new on the block, but I have a keen feeling that it is the relief from bloated “Goliath” frameworks, the “David” that frontend-ers have been looking for.

So should you use Svelte in a real project? Comparing risk and reward definitely comes into play. The community is smaller than other frameworks, meaning you’re likely to find less support and fewer tutorials to guide your along. At the same time, Svelte is in its third generation, meaning most of the gremlins should have been driven away, leaving a lean and reliable framework.

As with anything new, common sense rules, try it out with something non-commercial, take it for a spin, and see how you go.

Is there anything else? Funny you should ask! There are two co-projects that live in the Svelte Ecosystem: Sapper and Native. Sapper is a framework that utilizes Svelte for building full web applications, including routing, service workers, and all the good stuff you need to get started. I have used it to rebuild my personal website, and so far, I am a fan. Svelte Native is the most experimental of the Svelte projects, a NativeScript mobile app builder that utilizes Svelte under the hood. I confess that is where my knowledge on the subject ends. Luckily, it has a website with further information.

What do you think? Have you given Svelte a try? Do you think it stacks up to other frameworks? Let’s discuss it in the comments!

  1. Based on a mix of Github Contributions, NPM Downloads and StackOverflow topics
  2. State of JS review 2019

The post Getting Acquainted With Svelte, the New Framework on the Block appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Jetpack Slideshow Block

One of the many (many) useful things that Jetpack does is give you extra-fancy custom blocks in the WordPress block (AKA Gutenberg) editor: a slideshow, business hours, contact info, GIF, Mailchimp, Map, Markdown, Pinterest, Star Rating, Recurring Payments Button, Repeat Visitor, Simple Payments Button, Tiled Gallery. All of those sound useful to me, especially to use within totally arbitrary content wherever I want them.

I was just writing a blog post in the block editor the other day about a recent trip I took with my wife and friends to Tulum, Mexico. Being a travel blog post, of course it had images in it. Jetpack does a ton for me for those images:

  • Hosts them on a CDN
  • Optimizes them
  • Generates responsive image syntax
  • Lazy Loads them

That’s… pretty awesome.

But it can do more. In some spots of the blog post, I went for a slideshow approach to save a little room on the page.

Which is entirely controlled as a fancy Gutenberg block:

I could have gone for a tiled gallery as well, which is a fancier version of the built-in gallery block.

That’s an awful lot of helping out for posting and dealing with images.

The post Jetpack Slideshow Block appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Link Underlines That Animate Into Block Backgrounds

It’s a cool little effect. The default link style has an underline (which is a good idea) and then on :hover you see the underline essentially thicken up turning into almost what it would have looked liked if you used a background-color on the link instead.

Here’s an example of the effect on the Superfriendly site:

A journey:

  • The Superfriendly site does it with box-shadow. Turning box-shadow: inset 0 -0.07em 0 #0078d6; into box-shadow: inset 0 -0.85em 0 #a2d5fe; with a transition. Andres Cuervo ported that idea to a Pen. (I forked it to fix the “start offset” idea that was broken-seeming to me on the original).
  • You might be tempted to draw the line with a pseudo-element that’s, say, absolutely positioned within the relatively positioned link. Then you animate its height or scaleY or something. Here’s that kind of idea. Your enemy here is going to be links that break onto new lines, which box-shadow seems to handle more elegantly.
  • Another idea would be using linear-gradient with hard color stops to kinda “fake” the drawing of a line that’s positioned to look like an underline. Then the gradient can be animated to cover the element on hover, probably by moving its background-position. Here’s that kind of idea and another example we wrote up a little while back. This handles line breaks nicer than the previous method.
  • The default text-decoration: underline; has a distinct advantage these days: text-decoration-skip-ink! It has become the default behavior for links to have the underlines deftly skip around the decenders in text, making for much nicer looking underlines than any of these techniques (also: borders) can pull off. There are properties that are somewhat new that you may not be aware of that give you more control over the underline that we have traditionally had, like text-decoration-color. But there is more, like thickness and offset, that make this effect possible! Miriam Suzanne has a demo of exactly this, which only works in Firefox Nightly at the moment, but should be making the rounds soon enough.

Summary: If you need to do this effect right now in the most browsers you can, the box-shadow technique is probably best. If it’s just an enhancement that can wait a bit, using text-decoration-thickness / text-decoration-offset / text-decoration-color with a transition is a better option for aesthetics, control, and being able to understand the code at first glance.

The post Link Underlines That Animate Into Block Backgrounds appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]