Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds tabs block behind block experiments flag #63689

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5bf4de5
Scaffolds tabs and tab blocks
creativecoder Jul 10, 2024
89c8035
WIP Adds title attribute for tab blocks
creativecoder Jul 17, 2024
f3074ba
WIP Show only active tab
creativecoder Jul 18, 2024
ccebbe2
Displays list of tabs and active tab content
creativecoder Jul 19, 2024
aa0a710
Removes button component from block and initial styles for tab labels
creativecoder Jul 22, 2024
4a42b99
Adds basic tab inserting and management
creativecoder Jul 23, 2024
c47f407
Correctly saves block markup for front-end
creativecoder Jul 26, 2024
9b30278
Adds proper ids to tabs, integrated with anchor attribute
creativecoder Jul 28, 2024
4fe919e
Attempts to add interactivity API to show tab on click
creativecoder Jul 29, 2024
d141de9
Adds icons to block settings
creativecoder Jul 29, 2024
590f3b2
Converts save functions to render a static version of the block
creativecoder Jul 30, 2024
fa1873b
Uses withoutInteractiveFormatting prop rather than allowedFormats for…
creativecoder Jul 30, 2024
0247925
Switches tab and tabs blocks to server side rendering
creativecoder Jul 31, 2024
04b2b49
Gets interactivity API loaded for tabs
creativecoder Aug 1, 2024
b42ca9c
Sets aria roles and labels in front-end markup
creativecoder Aug 1, 2024
e5bff1b
Adds accessibility features for tabs front-end
creativecoder Aug 3, 2024
58fe918
Enables tab label text selection in editor
creativecoder Aug 3, 2024
28c682c
Adds view.js null/undefined checks and small refactor to keyDown handler
creativecoder Aug 4, 2024
5f91330
Adds comments, simplifies, fixes linting
creativecoder Aug 5, 2024
2db172b
Clarifies and corrects comments
creativecoder Aug 5, 2024
cc304b5
Replace JS initTabs in favor of interativity directives and state get…
creativecoder Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,17 @@ Add white space between blocks and customize its height. ([Source](https://githu
- **Supports:** anchor, interactivity (clientNavigation), spacing (margin)
- **Attributes:** height, width

## Tab

Single tab within a tabs block. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tab))

- **Name:** core/tab
- **Experimental:** true
- **Category:** design
- **Parent:** core/tabs
- **Supports:** anchor, ~~html~~, ~~reusable~~
- **Attributes:** isActive, label, slug, tabIndex

## Table

Create structured content in rows and columns to display information. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/table))
Expand All @@ -906,6 +917,17 @@ Summarize your post with a list of headings. Add HTML anchors to Heading blocks
- **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** headings, onlyIncludeCurrentPage

## Tabs

Organize content into tabs. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tabs))

- **Name:** core/tabs
- **Experimental:** true
- **Category:** design
- **Allowed Blocks:** core/tab
- **Supports:** align (full, wide), color (text, ~~background~~), interactivity, spacing (margin, padding), ~~html~~
- **Attributes:** innerTabs

## Tag Cloud

A cloud of popular keywords, each sized by how often it appears. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tag-cloud))
Expand Down
2 changes: 2 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ function gutenberg_reregister_core_block_types() {
'site-logo.php' => 'core/site-logo',
'site-tagline.php' => 'core/site-tagline',
'site-title.php' => 'core/site-title',
'tab.php' => 'core/tab',
'tabs.php' => 'core/tabs',
'tag-cloud.php' => 'core/tag-cloud',
'template-part.php' => 'core/template-part',
'term-description.php' => 'core/term-description',
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
@import "./social-links/editor.scss";
@import "./spacer/editor.scss";
@import "./table/editor.scss";
@import "./tabs/editor.scss";
@import "./tag-cloud/editor.scss";
@import "./template-part/editor.scss";
@import "./text-columns/editor.scss";
Expand Down
8 changes: 8 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ import * as socialLinks from './social-links';
import * as spacer from './spacer';
import * as table from './table';
import * as tableOfContents from './table-of-contents';
import * as tab from './tab';
import * as tabs from './tabs';
import * as tagCloud from './tag-cloud';
import * as templatePart from './template-part';
import * as termDescription from './term-description';
Expand Down Expand Up @@ -232,6 +234,12 @@ const getAllBlocks = () => {
queryTitle,
postAuthorBiography,
];

if ( window.__experimentalEnableBlockExperiments ) {
blocks.push( tab );
blocks.push( tabs );
}

if ( window?.__experimentalEnableFormBlocks ) {
blocks.push( form );
blocks.push( formInput );
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@
@import "./site-title/style.scss";
@import "./social-links/style.scss";
@import "./spacer/style.scss";
@import "./tab/style.scss";
@import "./tag-cloud/style.scss";
@import "./table/style.scss";
@import "./table-of-contents/style.scss";
@import "./tabs/style.scss";
@import "./term-description/style.scss";
@import "./text-columns/style.scss";
@import "./verse/style.scss";
Expand Down
34 changes: 34 additions & 0 deletions packages/block-library/src/tab/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "core/tab",
"title": "Tab",
"category": "design",
"description": "Single tab within a tabs block.",
"textdomain": "default",
"__experimental": true,
"attributes": {
"isActive": {
"type": "boolean",
"default": false
},
"label": {
"type": "string",
"default": ""
},
"slug": {
"type": "string",
"default": ""
},
"tabIndex": {
"type": "number"
}
},
"parent": [ "core/tabs" ],
"supports": {
"anchor": true,
"html": false,
"reusable": false
},
"style": "wp-block-tab"
}
68 changes: 68 additions & 0 deletions packages/block-library/src/tab/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* WordPress dependencies
*/
import {
InnerBlocks,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { cleanForSlug } from '@wordpress/url';

/**
* Generates a slug from a tab's text label.
*
* @param {string} label Tab label RichText value.
* @param {number} tabIndex Tab index value.
*
* @return {string} The generated slug with HTML stripped out.
*/
function slugFromLabel( label, tabIndex ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with this approach, if the same tab label exists in multiple tab blocks, we will get duplicate tab ids.

One approach would be to generate a unique ID using something like wp_unique_id() function when rendering on the server side, but I don't know how to share that ID between Tabs and the Tab block 🤔

// Get just the text content, filtering out any HTML tags from the RichText value.
const htmlDocument = new window.DOMParser().parseFromString(
label,
'text/html'
);
if ( htmlDocument.body?.textContent ) {
return cleanForSlug( htmlDocument.body.textContent );
}

// Fall back to using the tab index if the label is empty.
return 'tab-panel-' + tabIndex;
}

export default function Edit( { attributes, clientId, setAttributes } ) {
const { anchor, isActive, label, slug, tabIndex } = attributes;
// Use a custom anchor, if set. Otherwise fall back to the slug generated from the label text.
const tabPanelId = anchor || slug;
const tabLabelId = tabPanelId + '--tab';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a possibility of duplicate IDs. Can't we use the clientId or the useInstanceId() hook?

const hasChildBlocks = useSelect(
( select ) =>
select( blockEditorStore ).getBlockOrder( clientId ).length > 0,
[ clientId ]
);

useEffect( () => {
if ( label ) {
setAttributes( { slug: slugFromLabel( label, tabIndex ) } );
}
}, [ label, setAttributes, tabIndex ] );

const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
renderAppender: hasChildBlocks
? undefined
: InnerBlocks.ButtonBlockAppender,
} );
return (
<div
{ ...innerBlocksProps }
aria-labelledby={ tabLabelId }
hidden={ ! isActive }
id={ tabPanelId }
role="tabpanel"
/>
);
}
19 changes: 19 additions & 0 deletions packages/block-library/src/tab/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/components';

export default (
<SVG
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5498 10.3501V6.3501H9.8498V10.3501H11.3498V6.1001C11.3498 5.40974 10.7902 4.8501 10.0998 4.8501H5.2998C4.60945 4.8501 4.0498 5.40974 4.0498 6.1001V10.3501H5.5498ZM20 12.6001H4V14.1001L20 14.1001V12.6001ZM14 17.1001H4V18.6001H14V17.1001Z"
/>
</SVG>
);
20 changes: 20 additions & 0 deletions packages/block-library/src/tab/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Internal dependencies
*/
import initBlock from '../utils/init-block';
import metadata from './block.json';
import edit from './edit';
import save from './save';
import icon from './icon';

const { name } = metadata;

export { metadata, name };

export const settings = {
icon,
edit,
save,
};

export const init = () => initBlock( { name, metadata, settings } );
53 changes: 53 additions & 0 deletions packages/block-library/src/tab/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* Server-side rendering of the `core/tab` block.
*
* @package WordPress
*/

/**
* Renders the `core/tab` block on the server.
*
* @param array $attributes The block attributes.
* @param string $content The block content.
* @param WP_Block $block The block object.
*
* @return string The block content.
*/
function render_block_core_tab( $attributes, $content ) {
if ( ! $content ) {
return '';
}

// Modify markup to include interactivity API attributes.
$p = new WP_HTML_Tag_Processor( $content );

while ( $p->next_tag( array( 'class_name' => 'wp-block-tab' ) ) ) {
// Add role="tabpanel" to each tab panel.
$p->set_attribute( 'data-wp-bind--role', 'state.roleAttribute' );

// Hide all tab panels that are not currently selected.
$p->set_attribute( 'data-wp-bind--hidden', '!state.isActiveTab' );

// Add tabindex="0" to the selected tab panel, so it can be focused.
$p->set_attribute( 'data-wp-bind--tabindex', 'state.tabindexPanelAttribute' );

// Store the index of each tab panel for tracking the selected tab.
$p->set_attribute( 'data-tab-index', $attributes['tabIndex'] );
}

return $p->get_updated_html();
}

/**
* Registers the `core/tab` block on server.
*/
function register_block_core_tab() {
register_block_type_from_metadata(
__DIR__ . '/tab',
array(
'render_callback' => 'render_block_core_tab',
)
);
}
add_action( 'init', 'register_block_core_tab' );
6 changes: 6 additions & 0 deletions packages/block-library/src/tab/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import { init } from './';

export default init();
14 changes: 14 additions & 0 deletions packages/block-library/src/tab/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* WordPress dependencies
*/
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

export default function save( { attributes } ) {
const { anchor, slug } = attributes;
const tabPanelId = anchor || slug;

const blockProps = useBlockProps.save();
const innerBlocksProps = useInnerBlocksProps.save( blockProps );

return <section { ...innerBlocksProps } id={ tabPanelId } />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a section element?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using <section> elements for tab panels is recommended here: https://inclusive-components.design/tabbed-interfaces/

}
7 changes: 7 additions & 0 deletions packages/block-library/src/tab/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.wp-block-tab {
padding: 1em 0;

> *:first-child {
margin-top: 0;
}
Comment on lines +4 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> *:first-child {
margin-top: 0;
}
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}

}
45 changes: 45 additions & 0 deletions packages/block-library/src/tabs/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "core/tabs",
"title": "Tabs",
"category": "design",
"description": "Organize content into tabs.",
"textdomain": "default",
"__experimental": true,
"allowedBlocks": [ "core/tab" ],
"attributes": {
"innerTabs": {
"type": "array",
"default": [],
"source": "query",
"selector": ".wp-block-tabs__tab-label",
"query": {
"href": {
"type": "string",
"source": "attribute",
"attribute": "href"
},
"label": {
"type": "string",
"source": "html"
}
}
}
},
"supports": {
"align": [ "wide", "full" ],
"color": {
"background": false,
"text": true
},
"html": false,
"interactivity": true,
"spacing": {
"margin": true,
"padding": true
}
},
"editorStyle": "wp-block-tabs-editor",
"style": "wp-block-tabs"
}
Loading
Loading