From d326b49fa5cd0b42a494c13ce37eb5280da5266e Mon Sep 17 00:00:00 2001 From: Robert Bokori Date: Mon, 21 Aug 2023 16:33:29 +0200 Subject: [PATCH 1/2] Block: Tabs --- .../Providers/PerformanceServiceProvider.php | 2 + web/app/themes/gds/app/setup.php | 17 -- .../gds/resources/blocks/tab/block.json | 25 +++ .../themes/gds/resources/blocks/tab/edit.js | 48 +++++ .../gds/resources/blocks/tab/editor.scss | 11 ++ .../themes/gds/resources/blocks/tab/index.js | 14 ++ .../gds/resources/blocks/tab/style.scss | 11 ++ .../gds/resources/blocks/tab/tab.blade.php | 12 ++ .../themes/gds/resources/blocks/tab/tab.php | 17 ++ .../gds/resources/blocks/tabs/block.json | 17 ++ .../themes/gds/resources/blocks/tabs/edit.js | 185 ++++++++++++++++++ .../themes/gds/resources/blocks/tabs/index.js | 14 ++ .../gds/resources/blocks/tabs/script.js | 41 ++++ .../gds/resources/blocks/tabs/tabs.blade.php | 24 +++ .../themes/gds/resources/blocks/tabs/tabs.php | 20 ++ .../resources/styles/blocks/core-buttons.scss | 1 + 16 files changed, 442 insertions(+), 17 deletions(-) create mode 100644 web/app/themes/gds/resources/blocks/tab/block.json create mode 100644 web/app/themes/gds/resources/blocks/tab/edit.js create mode 100644 web/app/themes/gds/resources/blocks/tab/editor.scss create mode 100644 web/app/themes/gds/resources/blocks/tab/index.js create mode 100644 web/app/themes/gds/resources/blocks/tab/style.scss create mode 100644 web/app/themes/gds/resources/blocks/tab/tab.blade.php create mode 100644 web/app/themes/gds/resources/blocks/tab/tab.php create mode 100644 web/app/themes/gds/resources/blocks/tabs/block.json create mode 100644 web/app/themes/gds/resources/blocks/tabs/edit.js create mode 100644 web/app/themes/gds/resources/blocks/tabs/index.js create mode 100644 web/app/themes/gds/resources/blocks/tabs/script.js create mode 100644 web/app/themes/gds/resources/blocks/tabs/tabs.blade.php create mode 100644 web/app/themes/gds/resources/blocks/tabs/tabs.php diff --git a/web/app/themes/gds/app/Providers/PerformanceServiceProvider.php b/web/app/themes/gds/app/Providers/PerformanceServiceProvider.php index 1afe5a07..138c78d7 100644 --- a/web/app/themes/gds/app/Providers/PerformanceServiceProvider.php +++ b/web/app/themes/gds/app/Providers/PerformanceServiceProvider.php @@ -31,6 +31,8 @@ public function earlyEnqeueueBlockStyles(): void { render_block(['blockName' => 'core/heading']); render_block(['blockName' => 'core/paragraph']); + render_block(['blockName' => 'core/buttons']); + render_block(['blockName' => 'core/button']); // Enqeueue stylesheets of the firt block. if (is_singular() && $post = get_post()) { diff --git a/web/app/themes/gds/app/setup.php b/web/app/themes/gds/app/setup.php index b3895517..463e59a3 100755 --- a/web/app/themes/gds/app/setup.php +++ b/web/app/themes/gds/app/setup.php @@ -36,23 +36,6 @@ echo app(GoogleFonts::class)->load()->toHtml(); }, 7); -/** - * Always enqueue stylesheets for the following blocks in the - */ -add_action('wp_enqueue_scripts', function () { - render_block(['blockName' => 'core/heading']); - render_block(['blockName' => 'core/paragraph']); - - // Enqeueue stylesheets of the first block. - if (is_singular() && $post = get_post()) { - $blocks = parse_blocks($post->post_content); - render_block($blocks[0]); - if ($blocks = parse_blocks($post->post_content)) { - render_block($blocks[0]); - } - } -}, 9); - /** * Register the theme assets with the block editor. * diff --git a/web/app/themes/gds/resources/blocks/tab/block.json b/web/app/themes/gds/resources/blocks/tab/block.json new file mode 100644 index 00000000..249032ce --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/block.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "gds/tab", + "title": "Tab", + "description": "", + "icon": "plus-alt2", + "category": "layout", + "parent": ["gds/tabs"], + "attributes": { + "label": { + "type": "string" + }, + "index": { + "type": "number" + } + }, + "supports": { + "html": false, + "color": {} + }, + "editorScript": "file:./index.js", + "editorStyle": "file:./editor.css", + "style": "file:./style.css" +} diff --git a/web/app/themes/gds/resources/blocks/tab/edit.js b/web/app/themes/gds/resources/blocks/tab/edit.js new file mode 100644 index 00000000..047cd41e --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/edit.js @@ -0,0 +1,48 @@ +import { + InnerBlocks, +} from '@wordpress/block-editor'; +import { + useBlockProps, + useInnerBlocksProps, +} from '@wordpress/block-editor'; +import {useSelect} from '@wordpress/data'; +import {useEffect} from '@wordpress/element'; + +const Edit = (props) => { + const {setAttributes, clientId} = props; + + const hasInnerBlocks = useSelect(select => { + const {getBlock} = select('core/block-editor'); + const block = getBlock(clientId); + + return !!(block && block.innerBlocks.length); + }, [clientId]); + + const index = useSelect(select => { + const {getBlockIndex} = select('core/block-editor'); + return getBlockIndex(clientId); + }); + + useEffect(() => { + setAttributes({index}) + }, [index]); + + const blockProps = useBlockProps({ + className: '', + }); + + const innerBlocksProps = useInnerBlocksProps({ + ref: blockProps.ref, + className: 'wp-block-gds-tab__content', + }, { + renderAppender: hasInnerBlocks ? undefined : InnerBlocks.ButtonBlockAppender, + }); + + return ( +
+
+
+ ); +}; + +export default Edit; diff --git a/web/app/themes/gds/resources/blocks/tab/editor.scss b/web/app/themes/gds/resources/blocks/tab/editor.scss new file mode 100644 index 00000000..2932ba67 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/editor.scss @@ -0,0 +1,11 @@ +.wp-block-gds-tab { + display: block; + + &__content { + display: none; + + &.active { + display: block; + } + } +} diff --git a/web/app/themes/gds/resources/blocks/tab/index.js b/web/app/themes/gds/resources/blocks/tab/index.js new file mode 100644 index 00000000..30e9dcd9 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/index.js @@ -0,0 +1,14 @@ +/** @wordpress */ +import { registerBlockType } from '@wordpress/blocks' +import { InnerBlocks } from '@wordpress/block-editor' + +import edit from './edit' +import meta from './block.json'; + +registerBlockType(meta.name, { + ...meta, + edit, + save() { + return ; + }, +}); diff --git a/web/app/themes/gds/resources/blocks/tab/style.scss b/web/app/themes/gds/resources/blocks/tab/style.scss new file mode 100644 index 00000000..688245ab --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/style.scss @@ -0,0 +1,11 @@ +.wp-block-gds-tab { + display: none; + + &.active { + display: block; + } + + &__content { + padding-top: var(--block-gutter-s); + } +} diff --git a/web/app/themes/gds/resources/blocks/tab/tab.blade.php b/web/app/themes/gds/resources/blocks/tab/tab.blade.php new file mode 100644 index 00000000..3c5a0b36 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/tab.blade.php @@ -0,0 +1,12 @@ +
($attributes->index === 0 ? 'active' : ''), + 'id' => 'tabpanel-' . sanitize_title( $attributes->label ), + 'tabindex' => '0', + 'role' => 'tabpanel', + 'aria-labelledby' => 'tab-' . sanitize_title( $attributes->label ), + ]) !!} +> +
+ {!! $content !!} +
+
diff --git a/web/app/themes/gds/resources/blocks/tab/tab.php b/web/app/themes/gds/resources/blocks/tab/tab.php new file mode 100644 index 00000000..3a22c106 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/tab.php @@ -0,0 +1,17 @@ +path(), [ + 'render_callback' => function (array $attributes, string $content, WP_Block $block) { + $attributes = (object) $attributes; + + return view('blocks::tab.tab', [ + 'attributes' => $attributes, + 'content' => $content, + 'block' => $block, + ]); + } +]); diff --git a/web/app/themes/gds/resources/blocks/tabs/block.json b/web/app/themes/gds/resources/blocks/tabs/block.json new file mode 100644 index 00000000..3c88f31e --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/block.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "gds/tabs", + "title": "Tabs", + "description": "", + "category": "layout", + "icon": "welcome-widgets-menus", + "attributes": { + }, + "supports": { + "html": false, + "color": {} + }, + "editorScript": "file:./index.js", + "script": "file:/script.js" +} diff --git a/web/app/themes/gds/resources/blocks/tabs/edit.js b/web/app/themes/gds/resources/blocks/tabs/edit.js new file mode 100644 index 00000000..34850282 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/edit.js @@ -0,0 +1,185 @@ +import classnames from 'classnames'; +import {__, sprintf} from '@wordpress/i18n'; +import { + RichText, + useBlockProps, + useInnerBlocksProps, + BlockControls, +} from '@wordpress/block-editor'; +import { + Icon, + ToolbarButton, + ToolbarGroup, +} from '@wordpress/components'; +import {createBlock} from '@wordpress/blocks'; +import {useSelect, useDispatch} from '@wordpress/data'; +import {useEffect, useState, useRef} from '@wordpress/element'; + +const Edit = (props) => { + const {isSelected, clientId} = props; + const contentRef = useRef(null); + const [activeTab, setActiveTab] = useState(''); + + const children = useSelect(select => { + const {getBlock} = select('core/block-editor'); + return getBlock(clientId).innerBlocks; + }); + + const isParentOfSelectedBlock = useSelect( + (select) => select('core/block-editor').hasSelectedInnerBlock(clientId, true), + ); + + const { + insertBlock, + removeBlock, + // selectBlock, + moveBlockToPosition, + updateBlockAttributes, + } = useDispatch('core/block-editor'); + + // const selectTab = (blockId) => { + // if (0 < children?.length) { + // const block = children.filter(block => block.clientId === blockId)[0]; + // selectBlock(block.clientId); + // } + // }; + + const toggleActiveTab = (blockId) => { + if (contentRef.current) { + children.forEach(block => { + const blockContent = contentRef.current.querySelector(`#block-${block.clientId} .wp-block-gds-tab__content`); + blockContent?.classList.toggle('active', block.clientId === blockId); + }); + + setActiveTab(blockId); + } + }; + + const moveTab = (blockId, position) => { + const blockClientId = children.filter(block => block.clientId === blockId)[0]?.clientId; + if (blockClientId) { + moveBlockToPosition(blockClientId, clientId, clientId, position); + } + }; + + const deleteTab = (blockId) => { + if (0 < children?.length) { + const block = children.filter(block => block.clientId === blockId)[0]; + removeBlock(block.clientId, false); + if (activeTab === blockId) { + setActiveTab(''); + } + } + }; + + const addTab = () => { + const itemBlock = createBlock('gds/tab', {label: sprintf(__('Tab #%d'), children.length + 1)}); + insertBlock(itemBlock, (children?.length) || 0, clientId, false); + }; + + useEffect(() => { + // Handle breakpoint + const activeContent = contentRef.current.querySelector(`#block-${activeTab} .wp-block-gds-tab__content.active`) + + if (activeTab && !activeContent) { + toggleActiveTab(activeTab); + } + }); + + useEffect(() => { + // Activate the first tab when no tabs are selected + if (0 < children?.length && ('' === activeTab || 0 === children?.filter(block => block.clientId === activeTab).length)) { + toggleActiveTab(children[0].clientId); + } + }, [children]); + + const Controls = () => { + const index = children?.findIndex(({clientId}) => clientId === activeTab); + + return ( + + + deleteTab(activeTab)} + /> + + moveTab(activeTab, index - 1)} + /> + + moveTab(activeTab, index + 1)} + /> + + + ); + }; + + const blockProps = useBlockProps({ + className: '', + }); + + const innerBlocksProps = useInnerBlocksProps({ + ref: contentRef, + className: 'wp-block-gds-tabs__panels', + }, { + orientation: 'horizontal', + renderAppender: false, + allowedBlocks: ['gds/tab'], + template: [ + ['gds/tab', {label: sprintf(__('Tab #%d'), 1)}], + ], + }); + + return ( + <> + + +
+
+ {children?.map((tab, index) => { + return ( +
+ +
+ ); + }) || ''} + + {(isSelected || isParentOfSelectedBlock || 0 === children.length) && ( +
  • + +
  • + )} +
    + +
    +
    + + ); +}; + +export default Edit; diff --git a/web/app/themes/gds/resources/blocks/tabs/index.js b/web/app/themes/gds/resources/blocks/tabs/index.js new file mode 100644 index 00000000..30e9dcd9 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/index.js @@ -0,0 +1,14 @@ +/** @wordpress */ +import { registerBlockType } from '@wordpress/blocks' +import { InnerBlocks } from '@wordpress/block-editor' + +import edit from './edit' +import meta from './block.json'; + +registerBlockType(meta.name, { + ...meta, + edit, + save() { + return ; + }, +}); diff --git a/web/app/themes/gds/resources/blocks/tabs/script.js b/web/app/themes/gds/resources/blocks/tabs/script.js new file mode 100644 index 00000000..51edf5db --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/script.js @@ -0,0 +1,41 @@ +import {ready} from '~/utils'; + +const init = (block) => { + const tabs = block.querySelectorAll('[role="tab"] button'); + const panels = block.querySelectorAll('.wp-block-gds-tab'); + + if (!tabs) { + return; + } + + for (const tab of tabs) { + tab.addEventListener('click', (e) => { + e.preventDefault(); + + tabs.forEach((tab) => { + tab.classList.remove('active'); + tab.setAttribute('aria-selected', false); + tab.setAttribute('tabindex', '-1'); + }); + + panels.forEach((panel) => { + panel.classList.remove('active'); + }); + + e.currentTarget.classList.add('active'); + e.currentTarget.setAttribute('aria-selected', true); + e.currentTarget.setAttribute('tabindex', '0'); + + const activePanelId = e.currentTarget.getAttribute('aria-controls'); + const activePanel = block.querySelector(`#${activePanelId}.wp-block-gds-tab`); + activePanel?.classList.add('active'); + }); + } +} + +ready(()=> { + for (const block of document.querySelectorAll('.wp-block-gds-tabs')) { + init(block); + } +}); + diff --git a/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php b/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php new file mode 100644 index 00000000..a08250fa --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php @@ -0,0 +1,24 @@ +@if( $innerBlocks ?? false ) +
    +
    + @foreach( $innerBlocks as $key => $tab ) + + @endforeach +
    + +
    + {!! $content !!} +
    +
    +@endif diff --git a/web/app/themes/gds/resources/blocks/tabs/tabs.php b/web/app/themes/gds/resources/blocks/tabs/tabs.php new file mode 100644 index 00000000..eccd0b10 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/tabs.php @@ -0,0 +1,20 @@ +path(), [ + 'render_callback' => function (array $attributes, string $content, WP_Block $block) { + return view('blocks::tabs.tabs', [ + 'attributes' => (object) $attributes, + 'content' => $content, + 'block' => $block, + 'innerBlocks' => $block->parsed_block['innerBlocks'], + ]); + } +]); + +add_action('wp_enqueue_scripts', function () { + wp_script_add_data('gds-tabs-item-script', 'strategy', 'async'); +}); diff --git a/web/app/themes/gds/resources/styles/blocks/core-buttons.scss b/web/app/themes/gds/resources/styles/blocks/core-buttons.scss index 8727856f..5eb70355 100644 --- a/web/app/themes/gds/resources/styles/blocks/core-buttons.scss +++ b/web/app/themes/gds/resources/styles/blocks/core-buttons.scss @@ -1,5 +1,6 @@ .wp-block-buttons { display: flex; + flex-wrap: wrap; // Set the block gap so button width settings work --wp--style--block-gap: var(--grid-gutter); From 8377f8004a61a6d2ac916bc77e1ef9a1ed9d6eff Mon Sep 17 00:00:00 2001 From: Robert Bokori Date: Mon, 21 Aug 2023 20:35:56 +0200 Subject: [PATCH 2/2] Fixes --- .../gds/resources/blocks/tab/block.json | 3 -- .../themes/gds/resources/blocks/tab/edit.js | 12 +------ .../gds/resources/blocks/tab/tab.blade.php | 7 ++-- .../themes/gds/resources/blocks/tab/tab.php | 4 ++- .../gds/resources/blocks/tabs/script.js | 11 ++---- .../gds/resources/blocks/tabs/tabs.blade.php | 34 +++++++++++-------- .../themes/gds/resources/blocks/tabs/tabs.php | 2 +- 7 files changed, 30 insertions(+), 43 deletions(-) diff --git a/web/app/themes/gds/resources/blocks/tab/block.json b/web/app/themes/gds/resources/blocks/tab/block.json index 249032ce..e708b003 100644 --- a/web/app/themes/gds/resources/blocks/tab/block.json +++ b/web/app/themes/gds/resources/blocks/tab/block.json @@ -10,9 +10,6 @@ "attributes": { "label": { "type": "string" - }, - "index": { - "type": "number" } }, "supports": { diff --git a/web/app/themes/gds/resources/blocks/tab/edit.js b/web/app/themes/gds/resources/blocks/tab/edit.js index 047cd41e..cee22d0d 100644 --- a/web/app/themes/gds/resources/blocks/tab/edit.js +++ b/web/app/themes/gds/resources/blocks/tab/edit.js @@ -6,10 +6,9 @@ import { useInnerBlocksProps, } from '@wordpress/block-editor'; import {useSelect} from '@wordpress/data'; -import {useEffect} from '@wordpress/element'; const Edit = (props) => { - const {setAttributes, clientId} = props; + const {clientId} = props; const hasInnerBlocks = useSelect(select => { const {getBlock} = select('core/block-editor'); @@ -18,15 +17,6 @@ const Edit = (props) => { return !!(block && block.innerBlocks.length); }, [clientId]); - const index = useSelect(select => { - const {getBlockIndex} = select('core/block-editor'); - return getBlockIndex(clientId); - }); - - useEffect(() => { - setAttributes({index}) - }, [index]); - const blockProps = useBlockProps({ className: '', }); diff --git a/web/app/themes/gds/resources/blocks/tab/tab.blade.php b/web/app/themes/gds/resources/blocks/tab/tab.blade.php index 3c5a0b36..6d38a352 100644 --- a/web/app/themes/gds/resources/blocks/tab/tab.blade.php +++ b/web/app/themes/gds/resources/blocks/tab/tab.blade.php @@ -1,9 +1,8 @@
    ($attributes->index === 0 ? 'active' : ''), - 'id' => 'tabpanel-' . sanitize_title( $attributes->label ), - 'tabindex' => '0', + 'class' => ($index === 0 ? 'active' : ''), + 'id' => 'tabpanel-' . $uid . '-' . $index, 'role' => 'tabpanel', - 'aria-labelledby' => 'tab-' . sanitize_title( $attributes->label ), + 'aria-labelledby' => 'tab-' . $uid . '-' . $index, ]) !!} >
    diff --git a/web/app/themes/gds/resources/blocks/tab/tab.php b/web/app/themes/gds/resources/blocks/tab/tab.php index 3a22c106..0a5d8235 100644 --- a/web/app/themes/gds/resources/blocks/tab/tab.php +++ b/web/app/themes/gds/resources/blocks/tab/tab.php @@ -5,13 +5,15 @@ use WP_Block; register_block_type(asset('blocks/tab/block.json')->path(), [ - 'render_callback' => function (array $attributes, string $content, WP_Block $block) { + 'render_callback' => function (array $attributes, string $content, $block) { $attributes = (object) $attributes; return view('blocks::tab.tab', [ 'attributes' => $attributes, 'content' => $content, 'block' => $block, + 'index' => $attributes->index ?? 0, + 'uid' => $attributes->uid ?? 0, ]); } ]); diff --git a/web/app/themes/gds/resources/blocks/tabs/script.js b/web/app/themes/gds/resources/blocks/tabs/script.js index 51edf5db..53883274 100644 --- a/web/app/themes/gds/resources/blocks/tabs/script.js +++ b/web/app/themes/gds/resources/blocks/tabs/script.js @@ -1,7 +1,7 @@ import {ready} from '~/utils'; const init = (block) => { - const tabs = block.querySelectorAll('[role="tab"] button'); + const tabs = block.querySelectorAll('button[role="tab"]'); const panels = block.querySelectorAll('.wp-block-gds-tab'); if (!tabs) { @@ -13,18 +13,14 @@ const init = (block) => { e.preventDefault(); tabs.forEach((tab) => { - tab.classList.remove('active'); - tab.setAttribute('aria-selected', false); - tab.setAttribute('tabindex', '-1'); + tab.setAttribute('aria-selected', 'false'); }); panels.forEach((panel) => { panel.classList.remove('active'); }); - e.currentTarget.classList.add('active'); - e.currentTarget.setAttribute('aria-selected', true); - e.currentTarget.setAttribute('tabindex', '0'); + e.currentTarget.setAttribute('aria-selected', 'true'); const activePanelId = e.currentTarget.getAttribute('aria-controls'); const activePanel = block.querySelector(`#${activePanelId}.wp-block-gds-tab`); @@ -38,4 +34,3 @@ ready(()=> { init(block); } }); - diff --git a/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php b/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php index a08250fa..40f2f3a8 100644 --- a/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php +++ b/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php @@ -1,24 +1,28 @@ -@if( $innerBlocks ?? false ) +@if( $block->inner_blocks ?? false )
    - @foreach( $innerBlocks as $key => $tab ) - + @foreach( $block->inner_blocks as $index => $inner_block ) + @endforeach
    - {!! $content !!} + @foreach( $block->inner_blocks as $index => $inner_block ) + @php + $inner_block->attributes['index'] = $index; + $inner_block->attributes['uid'] = $uid; + @endphp + + {!! $inner_block->render() !!} + @endforeach
    @endif diff --git a/web/app/themes/gds/resources/blocks/tabs/tabs.php b/web/app/themes/gds/resources/blocks/tabs/tabs.php index eccd0b10..6050549c 100644 --- a/web/app/themes/gds/resources/blocks/tabs/tabs.php +++ b/web/app/themes/gds/resources/blocks/tabs/tabs.php @@ -10,7 +10,7 @@ 'attributes' => (object) $attributes, 'content' => $content, 'block' => $block, - 'innerBlocks' => $block->parsed_block['innerBlocks'], + 'uid' => wp_unique_id(), ]); } ]);