-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Changes from all commits
5bf4de5
89c8035
f3074ba
ccebbe2
aa0a710
4a42b99
c47f407
9b30278
4fe919e
d141de9
590f3b2
fa1873b
0247925
04b2b49
b42ca9c
e5bff1b
58fe918
28c682c
5f91330
2db172b
cc304b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
} |
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 ) { | ||
// 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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is a possibility of duplicate IDs. Can't we use the |
||
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" | ||
/> | ||
); | ||
} |
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> | ||
); |
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 } ); |
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' ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { init } from './'; | ||
|
||
export default init(); |
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 } />; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
} |
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" | ||
} |
There was a problem hiding this comment.
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 🤔