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..e708b003 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/block.json @@ -0,0 +1,22 @@ +{ + "$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" + } + }, + "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..cee22d0d --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/edit.js @@ -0,0 +1,38 @@ +import { + InnerBlocks, +} from '@wordpress/block-editor'; +import { + useBlockProps, + useInnerBlocksProps, +} from '@wordpress/block-editor'; +import {useSelect} from '@wordpress/data'; + +const Edit = (props) => { + const {clientId} = props; + + const hasInnerBlocks = useSelect(select => { + const {getBlock} = select('core/block-editor'); + const block = getBlock(clientId); + + return !!(block && block.innerBlocks.length); + }, [clientId]); + + 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..6d38a352 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/tab.blade.php @@ -0,0 +1,11 @@ +
($index === 0 ? 'active' : ''), + 'id' => 'tabpanel-' . $uid . '-' . $index, + 'role' => 'tabpanel', + 'aria-labelledby' => 'tab-' . $uid . '-' . $index, + ]) !!} +> +
+ {!! $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..0a5d8235 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tab/tab.php @@ -0,0 +1,19 @@ +path(), [ + '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/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..53883274 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/script.js @@ -0,0 +1,36 @@ +import {ready} from '~/utils'; + +const init = (block) => { + const tabs = block.querySelectorAll('button[role="tab"]'); + 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.setAttribute('aria-selected', 'false'); + }); + + panels.forEach((panel) => { + panel.classList.remove('active'); + }); + + e.currentTarget.setAttribute('aria-selected', 'true'); + + 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..40f2f3a8 --- /dev/null +++ b/web/app/themes/gds/resources/blocks/tabs/tabs.blade.php @@ -0,0 +1,28 @@ +@if( $block->inner_blocks ?? false ) +
    +
    + @foreach( $block->inner_blocks as $index => $inner_block ) + + @endforeach +
    + +
    + @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 new file mode 100644 index 00000000..6050549c --- /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, + 'uid' => wp_unique_id(), + ]); + } +]); + +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);