Skip to content

Commit

Permalink
Adds basic tab inserting and management
Browse files Browse the repository at this point in the history
  • Loading branch information
creativecoder committed Jul 23, 2024
1 parent 4fd35fd commit d9dbd90
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 64 deletions.
7 changes: 3 additions & 4 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -895,8 +895,8 @@ Single tab within a tabs block. ([Source](https://github.com/WordPress/gutenberg
- **Experimental:** true
- **Category:** design
- **Parent:** core/tabs
- **Supports:** ~~html~~, ~~inserter~~, ~~reusable~~
- **Attributes:** title
- **Supports:** anchor, ~~html~~, ~~reusable~~
- **Attributes:** isActive, label

## Table

Expand Down Expand Up @@ -925,8 +925,7 @@ Organize content into tabs. ([Source](https://github.com/WordPress/gutenberg/tre
- **Experimental:** true
- **Category:** design
- **Allowed Blocks:** core/tab
- **Supports:** align (full, wide), color (text, ~~background~~), interactivity, layout (default, ~~allowJustification~~, ~~allowSwitching~~), shadow, spacing (margin, padding), ~~html~~
- **Attributes:** activeTab
- **Supports:** align (full, wide), color (text, ~~background~~), interactivity, spacing (margin, padding), ~~html~~

## Tag Cloud

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 "./template-part/editor.scss";
@import "./text-columns/editor.scss";
@import "./video/editor.scss";
Expand Down
9 changes: 6 additions & 3 deletions packages/block-library/src/tab/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@
"textdomain": "default",
"__experimental": true,
"attributes": {
"title": {
"isActive": {
"type": "boolean",
"default": false
},
"label": {
"type": "string"
}
},
"parent": [ "core/tabs" ],
"supports": {
"anchor": true,
"html": false,
"inserter": false,
"reusable": false
},
"usesContext": [ "tabs/activeTab" ],
"style": "wp-block-tab"
}
16 changes: 13 additions & 3 deletions packages/block-library/src/tab/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ import {
InnerBlocks,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';

export default function Edit( { attributes, clientId } ) {
const hasChildBlocks = useSelect(
( select ) =>
select( blockEditorStore ).getBlockOrder( clientId ).length > 0,
[ clientId ]
);
const { isActive } = attributes;

export default function Edit( { clientId, context } ) {
const isActive = context[ 'tabs/activeTab' ] === clientId;
const blockProps = useBlockProps( {
className: isActive ? 'is-active' : '',
} );
const innerBlocksProps = useInnerBlocksProps( blockProps, {
renderAppender: InnerBlocks.ButtonBlockAppender,
renderAppender: hasChildBlocks
? undefined
: InnerBlocks.ButtonBlockAppender,
} );
return <div { ...innerBlocksProps } role="tabpanel"></div>;
}
8 changes: 2 additions & 6 deletions packages/block-library/src/tab/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

export default function save( { attributes } ) {
const { title } = attributes;

export default function save() {
return (
<div { ...useBlockProps.save() }>
<div className="wp-block-tab__content">
<InnerBlocks.Content />
</div>
<InnerBlocks.Content />
</div>
);
}
5 changes: 5 additions & 0 deletions packages/block-library/src/tab/style.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
.wp-block-tab {
display: none;
padding: 1em 0;

&.is-active {
display: block;
}

> *:first-child {
margin-top: 0;
}
}

17 changes: 2 additions & 15 deletions packages/block-library/src/tabs/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,7 @@
"textdomain": "default",
"__experimental": true,
"allowedBlocks": [ "core/tab" ],
"attributes": {
"activeTab": {
"type": "string",
"default": ""
}
},
"providesContext": {
"tabs/activeTab": "activeTab"
},
"attributes": {},
"supports": {
"align": [ "wide", "full" ],
"color": {
Expand All @@ -25,16 +17,11 @@
},
"html": false,
"interactivity": true,
"layout": {
"default": { "type": "flex", "orientation": "horizontal" },
"allowSwitching": false,
"allowJustification": false
},
"shadow": true,
"spacing": {
"margin": true,
"padding": true
}
},
"editorStyle": "wp-block-tabs-editor",
"style": "wp-block-tabs"
}
114 changes: 81 additions & 33 deletions packages/block-library/src/tabs/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import clsx from 'clsx';
* WordPress dependencies
*/
import {
InnerBlocks,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
RichText,
} from '@wordpress/block-editor';
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { useCallback, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

const TABS_TEMPLATE = [
[ 'core/tab', { title: 'Tab 1' } ],
[ 'core/tab', { title: 'Tab 2' } ],
[ 'core/tab', { label: 'Tab 1' } ],
[ 'core/tab', { label: 'Tab 2' } ],
];

const ALLOWED_FORMATS = [
Expand All @@ -34,49 +34,96 @@ const ALLOWED_FORMATS = [
'core/text-color',
];

export default function Edit( { attributes, clientId, setAttributes } ) {
const { activeTab } = attributes;
const innerBlocks = useSelect(
( select ) => select( blockEditorStore ).getBlocks( clientId ),
[ clientId ]
);
export default function Edit( { clientId } ) {
const { innerTabs, selectedTabClientId } = useSelect(
( select ) => {
const {
getBlocks,
getSelectedBlockClientId,
hasSelectedInnerBlock,
} = select( blockEditorStore );
const innerBlocks = getBlocks( clientId );
const selectedBlockClientId = getSelectedBlockClientId();
let selectedTabId = null;

const blockProps = useBlockProps();
// Find the first tab that is selected or has selected inner blocks so we can set it as active.
for ( const block of innerBlocks ) {
if (
block.clientId === selectedBlockClientId ||
hasSelectedInnerBlock( block.clientId, true )
) {
selectedTabId = block.clientId;
break;
}
}

const innerBlockProps = useInnerBlocksProps(
{
className: 'wp-block-tabs__content',
return {
innerTabs: innerBlocks,
selectedTabClientId: selectedTabId,
};
},
{
renderAppender: InnerBlocks.ButtonBlockAppender,
template: TABS_TEMPLATE,
}
[ clientId ]
);

const { __unstableMarkNextChangeAsNotPersistent, updateBlockAttributes } =
useDispatch( blockEditorStore );

const setActiveTab = ( tabId ) => {
__unstableMarkNextChangeAsNotPersistent();
setAttributes( { activeTab: tabId } );
};
const setActiveTab = useCallback(
( activeTabClientId ) => {
// Set each inner tab's `isActive` attribute.
innerTabs.forEach( ( block ) => {
__unstableMarkNextChangeAsNotPersistent();
updateBlockAttributes( block.clientId, {
isActive: block.clientId === activeTabClientId,
} );
} );
},
[
innerTabs,
updateBlockAttributes,
__unstableMarkNextChangeAsNotPersistent,
]
);

useEffect( () => {
// Initialize the first tab as active when the component mounts.
if ( innerBlocks.length ) {
setActiveTab( innerBlocks[ 0 ].clientId );
if ( innerTabs?.length ) {
// Set the first tab as active when the editor is loaded
setActiveTab( innerTabs[ 0 ].clientId );
}
}, [] ); // eslint-disable-line react-hooks/exhaustive-deps -- only run effect once when component mounts.
}, [] ); // eslint-disable-line react-hooks/exhaustive-deps -- set first tab as active when the editor is loaded.

// if ( ! innerBlocks || innerBlocks.length === 0 ) {
// return null;
// }
useEffect( () => {
const hasActiveTab =
innerTabs &&
innerTabs.some( ( block ) => block.attributes.isActive );

if ( selectedTabClientId ) {
// If an inner tab block is selected, or its inner blocks are selected, it becomes the active tab.
setActiveTab( selectedTabClientId );
} else if ( ! hasActiveTab && innerTabs?.length ) {
// Otherwise, if there's no active tab, default to the first inner tab.
setActiveTab( innerTabs[ 0 ].clientId );
}
}, [ innerTabs, selectedTabClientId, setActiveTab ] );

const blockProps = useBlockProps();
const innerBlockProps = useInnerBlocksProps(
{
className: 'wp-block-tabs__content',
},
{
__experimentalCaptureToolbars: true,
clientId,
orientation: 'horizontal',
template: TABS_TEMPLATE,
}
);

return (
<div { ...blockProps }>
<ul className="wp-block-tabs__list" role="tablist">
{ innerBlocks.map( ( block ) => {
const isActive = block.clientId === activeTab;
{ innerTabs.map( ( block ) => {
const isActive = block.attributes.isActive;
const tabIndex = isActive ? '0' : '-1';

// TODO: Add unique ids and aria attributes for accessibility.
Expand All @@ -98,12 +145,13 @@ export default function Edit( { attributes, clientId, setAttributes } ) {
<RichText
allowedFormats={ ALLOWED_FORMATS }
tagName="span"
value={ block.attributes.title }
onChange={ ( value ) =>
updateBlockAttributes( block.clientId, {
title: value,
label: value,
} )
}
placeholder={ __( 'Add label…' ) }
value={ block.attributes.label }
/>
</button>
</li>
Expand Down
5 changes: 5 additions & 0 deletions packages/block-library/src/tabs/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.wp-block-tabs {
&__content > .block-list-appender {
top: -30px;
}
}

0 comments on commit d9dbd90

Please sign in to comment.