From 86a9adfd80ae369efab11205282c97a6618b8f91 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Tue, 2 Jun 2026 09:40:34 -0700 Subject: [PATCH 01/16] feat(blocks): add adaptive container breakpoint logic Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/adaptive-container/block.json | 27 ++++ .../class-adaptive-container-block.php | 99 +++++++++++++++ .../class-test-adaptive-container-block.php | 116 ++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/block.json create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php create mode 100644 plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/block.json b/plugins/newspack-plugin/src/blocks/adaptive-container/block.json new file mode 100644 index 0000000000..97f77c7c4b --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "newspack/adaptive-container", + "title": "Adaptive Container", + "category": "newspack", + "description": "A container that shows one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint. Ideal for responsive headers and footers.", + "keywords": [ "responsive", "adaptive", "header", "footer", "mobile", "desktop" ], + "textdomain": "newspack-plugin", + "attributes": { + "editorView": { + "type": "string", + "enum": [ "desktop", "mobile" ], + "default": "desktop" + } + }, + "providesContext": { + "newspack-adaptive-container/editorView": "editorView" + }, + "supports": { + "html": false, + "anchor": true, + "position": { "sticky": true } + }, + "editorStyle": "file:../../../dist/blocks.css", + "style": "file:../../../dist/blocks.css" +} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php b/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php new file mode 100644 index 0000000000..806c90b8e9 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php @@ -0,0 +1,99 @@ += this width; + * mobile slot shows at <= ( breakpoint - 1 ). + */ + const DEFAULT_BREAKPOINT = 782; + + /** + * Initializes the block. + * + * @return void + */ + public static function init() { + add_action( 'init', [ __CLASS__, 'register_block' ] ); + add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_visibility_style' ] ); + } + + /** + * Registers the block type from metadata (static block, no render callback). + * + * @return void + */ + public static function register_block() { + register_block_type_from_metadata( __DIR__ . '/block.json' ); + } + + /** + * Resolves the swap breakpoint. + * + * Precedence (highest first): the `newspack_adaptive_container_breakpoint` + * filter, the theme.json `settings.custom.newspackAdaptiveBreakpoint` value, + * then the default. + * + * @return int Breakpoint in pixels. + */ + public static function get_breakpoint() { + $default = self::DEFAULT_BREAKPOINT; + $custom = wp_get_global_settings( [ 'custom', 'newspackAdaptiveBreakpoint' ] ); + $value = is_numeric( $custom ) ? (int) $custom : $default; + + /** + * Filters the breakpoint (in pixels) at which the Adaptive Container + * swaps between its desktop and mobile slots. + * + * @param int $value Resolved breakpoint in pixels. + */ + $value = (int) apply_filters( 'newspack_adaptive_container_breakpoint', $value ); + + // A non-positive breakpoint would produce invalid media queries; fall back. + return $value > 0 ? $value : $default; + } + + /** + * Builds the front-end visibility CSS for the current breakpoint. + * + * @return string CSS rules. + */ + public static function get_visibility_css() { + $breakpoint = self::get_breakpoint(); + return sprintf( + '@media (max-width:%1$dpx){.newspack-adaptive-container-slot--desktop{display:none !important;}}@media (min-width:%2$dpx){.newspack-adaptive-container-slot--mobile{display:none !important;}}', + $breakpoint - 1, + $breakpoint + ); + } + + /** + * Enqueues the single global visibility stylesheet on the front-end. + * + * Always enqueued (a few bytes) because the block commonly lives in header/ + * footer template parts, which `has_block()` cannot detect from post content. + * + * @return void + */ + public static function enqueue_visibility_style() { + wp_register_style( 'newspack-adaptive-container', false, [], NEWSPACK_PLUGIN_VERSION ); + wp_enqueue_style( 'newspack-adaptive-container' ); + wp_add_inline_style( 'newspack-adaptive-container', self::get_visibility_css() ); + } +} +Adaptive_Container_Block::init(); diff --git a/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php b/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php new file mode 100644 index 0000000000..76be4eb90c --- /dev/null +++ b/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php @@ -0,0 +1,116 @@ +update_with( + [ + 'version' => 2, + 'settings' => [ 'custom' => [ 'newspackAdaptiveBreakpoint' => $value ] ], + ] + ); + } + ); + wp_clean_theme_json_cache(); + } + + /** + * Default breakpoint is 782 when nothing overrides it. + */ + public function test_default_breakpoint(): void { + $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * The filter overrides the default. + */ + public function test_filter_overrides_breakpoint(): void { + add_filter( 'newspack_adaptive_container_breakpoint', fn() => 1024 ); + $this->assertSame( 1024, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * A numeric theme.json custom setting is used. + */ + public function test_theme_json_custom_breakpoint(): void { + $this->set_theme_json_breakpoint( 900 ); + $this->assertSame( 900, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * A non-numeric theme.json custom setting falls back to the default. + */ + public function test_non_numeric_custom_falls_back_to_default(): void { + $this->set_theme_json_breakpoint( 'nope' ); + $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * The filter beats the theme.json custom setting. + */ + public function test_filter_beats_theme_json(): void { + $this->set_theme_json_breakpoint( 900 ); + add_filter( 'newspack_adaptive_container_breakpoint', fn() => 600 ); + $this->assertSame( 600, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * A non-positive breakpoint (from filter or theme.json) falls back to the default. + */ + public function test_non_positive_breakpoint_falls_back_to_default(): void { + add_filter( 'newspack_adaptive_container_breakpoint', fn() => 0 ); + $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); + } + + /** + * The visibility CSS uses the resolved breakpoint and the slot modifier classes. + */ + public function test_visibility_css_uses_breakpoint(): void { + add_filter( 'newspack_adaptive_container_breakpoint', fn() => 800 ); + $css = Adaptive_Container_Block::get_visibility_css(); + $this->assertStringContainsString( '(max-width:799px)', $css ); + $this->assertStringContainsString( '(min-width:800px)', $css ); + $this->assertStringContainsString( 'newspack-adaptive-container-slot--desktop', $css ); + $this->assertStringContainsString( 'newspack-adaptive-container-slot--mobile', $css ); + } +} From 6e40f6559af66d28cb4b5571927c9e77ed7a2df5 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Tue, 2 Jun 2026 11:28:23 -0700 Subject: [PATCH 02/16] feat(blocks): register adaptive container in block bootstrap Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/newspack-plugin/includes/class-blocks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/newspack-plugin/includes/class-blocks.php b/plugins/newspack-plugin/includes/class-blocks.php index 6035d14131..85fa4d753a 100644 --- a/plugins/newspack-plugin/includes/class-blocks.php +++ b/plugins/newspack-plugin/includes/class-blocks.php @@ -41,6 +41,7 @@ public static function init() { require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-menu/trigger/class-overlay-menu-trigger-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-menu/panel/class-overlay-menu-panel-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-search/class-overlay-search-block.php'; + require_once NEWSPACK_ABSPATH . 'src/blocks/adaptive-container/class-adaptive-container-block.php'; Social_Icons::init(); } if ( Collections::is_module_active() ) { From 037be6fa18979755c331083b63fcf0ffedd1f7c7 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Tue, 2 Jun 2026 11:51:15 -0700 Subject: [PATCH 03/16] feat(blocks): add adaptive container parent block UI Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/adaptive-container/edit.js | 58 +++++++++++++++++++ .../src/blocks/adaptive-container/index.js | 43 ++++++++++++++ .../src/blocks/adaptive-container/style.scss | 18 ++++++ 3 files changed, 119 insertions(+) create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/edit.js create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/index.js create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/style.scss diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js b/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js new file mode 100644 index 0000000000..9aa4b03d54 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { desktop, mobile } from '@wordpress/icons'; +import { BlockControls, InnerBlocks, useBlockProps } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; + +const ALLOWED_BLOCKS = [ 'newspack/adaptive-container-slot' ]; +const BLOCKS_TEMPLATE = [ + [ 'newspack/adaptive-container-slot', { view: 'desktop' } ], + [ 'newspack/adaptive-container-slot', { view: 'mobile' } ], +]; + +/** + * Edit component for the Adaptive Container block. + * + * Renders a locked template of exactly two slots (desktop + mobile) and a + * toolbar toggle that sets `editorView`. The active view is shared with the + * slots via block context; the inactive slot hides itself in the editor. + * + * @param {Object} props Block props. + * @param {Object} props.attributes Block attributes. + * @param {Function} props.setAttributes Attribute setter. + * + * @return {JSX.Element} The block editor UI. + */ +export default function AdaptiveContainerEdit( { attributes, setAttributes } ) { + const { editorView } = attributes; + + const blockProps = useBlockProps( { + className: `newspack-adaptive-container is-editing-${ editorView }`, + } ); + + return ( + <> + + + setAttributes( { editorView: 'desktop' } ) } + /> + setAttributes( { editorView: 'mobile' } ) } + /> + + +
+ +
+ + ); +} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/index.js b/plugins/newspack-plugin/src/blocks/adaptive-container/index.js new file mode 100644 index 0000000000..5a514be6ba --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/index.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { mobile as icon } from '@wordpress/icons'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import colors from '../../../packages/colors/colors.module.scss'; +import './style.scss'; + +export const title = __( 'Adaptive Container', 'newspack-plugin' ); + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + title, + icon: { + src: icon, + foreground: colors[ 'primary-400' ], + }, + keywords: [ + __( 'responsive', 'newspack-plugin' ), + __( 'adaptive', 'newspack-plugin' ), + __( 'header', 'newspack-plugin' ), + __( 'footer', 'newspack-plugin' ), + __( 'mobile', 'newspack-plugin' ), + __( 'desktop', 'newspack-plugin' ), + ], + description: __( 'Show one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint.', 'newspack-plugin' ), + edit: Edit, + save: () => ( +
+ +
+ ), +}; diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss b/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss new file mode 100644 index 0000000000..97088f99e5 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss @@ -0,0 +1,18 @@ +// Editor-only helpers. Front-end slot visibility is handled by the PHP-generated +// media-query stylesheet, not here. These classes are only added in the editor, +// so they are inert on the front-end even though they ship in the shared bundle. +.newspack-adaptive-container-slot { + &.is-inactive-view { + display: none !important; + } + + &__label { + display: block; + margin-bottom: 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + opacity: 0.6; + } +} From c18b6c66aef23b7d4a994ee7e6436fc150c00465 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Tue, 2 Jun 2026 13:27:08 -0700 Subject: [PATCH 04/16] feat(blocks): add adaptive container slot child block Co-Authored-By: Claude Opus 4.8 (1M context) --- .../blocks/adaptive-container/slot/block.json | 29 ++++++++++++++ .../class-adaptive-container-slot-block.php | 38 +++++++++++++++++++ .../blocks/adaptive-container/slot/edit.js | 38 +++++++++++++++++++ .../blocks/adaptive-container/slot/index.js | 38 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/edit.js create mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json new file mode 100644 index 0000000000..271c1b0bcf --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "newspack/adaptive-container-slot", + "title": "Adaptive Slot", + "category": "newspack", + "description": "A single view (desktop or mobile) within an Adaptive Container.", + "textdomain": "newspack-plugin", + "parent": [ "newspack/adaptive-container" ], + "usesContext": [ "newspack-adaptive-container/editorView" ], + "attributes": { + "view": { + "type": "string", + "enum": [ "desktop", "mobile" ], + "default": "desktop" + }, + "lock": { + "type": "object", + "default": { "move": true, "remove": true } + } + }, + "supports": { + "html": false, + "lock": false, + "inserter": false + }, + "editorStyle": "file:../../../../dist/blocks.css", + "style": "file:../../../../dist/blocks.css" +} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php new file mode 100644 index 0000000000..49a77b2f87 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php @@ -0,0 +1,38 @@ + + { label } + + + ); +} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js new file mode 100644 index 0000000000..73ce32acb8 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { mobile as icon } from '@wordpress/icons'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import colors from '../../../../packages/colors/colors.module.scss'; + +export const title = __( 'Adaptive Slot', 'newspack-plugin' ); + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + title, + icon: { + src: icon, + foreground: colors[ 'primary-400' ], + }, + edit: Edit, + save: ( { attributes } ) => { + const blockProps = useBlockProps.save( { + className: `newspack-adaptive-container-slot--${ attributes.view }`, + } ); + return ( +
+ +
+ ); + }, +}; From a04d98772352fcd8c833a753bc3e68a8406d5ce3 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Tue, 2 Jun 2026 13:51:56 -0700 Subject: [PATCH 05/16] feat(blocks): register adaptive container blocks in site editor Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/newspack-plugin/includes/class-blocks.php | 1 + plugins/newspack-plugin/src/blocks/index.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/plugins/newspack-plugin/includes/class-blocks.php b/plugins/newspack-plugin/includes/class-blocks.php index 85fa4d753a..585a763b66 100644 --- a/plugins/newspack-plugin/includes/class-blocks.php +++ b/plugins/newspack-plugin/includes/class-blocks.php @@ -42,6 +42,7 @@ public static function init() { require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-menu/panel/class-overlay-menu-panel-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-search/class-overlay-search-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/adaptive-container/class-adaptive-container-block.php'; + require_once NEWSPACK_ABSPATH . 'src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php'; Social_Icons::init(); } if ( Collections::is_module_active() ) { diff --git a/plugins/newspack-plugin/src/blocks/index.js b/plugins/newspack-plugin/src/blocks/index.js index 6ff2df0540..44f3dbb03a 100644 --- a/plugins/newspack-plugin/src/blocks/index.js +++ b/plugins/newspack-plugin/src/blocks/index.js @@ -25,6 +25,8 @@ import * as overlayMenu from './overlay-menu'; import * as overlayMenuTrigger from './overlay-menu/trigger'; import * as overlayMenuPanel from './overlay-menu/panel'; import * as overlaySearch from './overlay-search'; +import * as adaptiveContainer from './adaptive-container'; +import * as adaptiveContainerSlot from './adaptive-container/slot'; /** * Block Scripts @@ -49,6 +51,8 @@ export const blocks = [ overlayMenuTrigger, overlayMenuPanel, overlaySearch, + adaptiveContainer, + adaptiveContainerSlot, ]; const readerActivationBlocks = [ 'newspack/reader-registration', 'newspack/my-account-button' ]; @@ -67,7 +71,10 @@ const blockThemeBlocks = [ 'newspack/overlay-menu-panel', 'newspack/my-account-button', 'newspack/overlay-search', + 'newspack/adaptive-container', + 'newspack/adaptive-container-slot', ]; +const siteEditorOnlyBlocks = [ 'newspack/adaptive-container', 'newspack/adaptive-container-slot' ]; /** * Function to register an individual block. @@ -102,6 +109,10 @@ const registerBlock = block => { if ( blockThemeBlocks.includes( name ) && ! newspack_blocks.is_block_theme ) { return; } + /** Do not register Site Editor-only blocks outside the Site Editor. */ + if ( siteEditorOnlyBlocks.includes( name ) && window.pagenow !== 'site-editor' ) { + return; + } registerBlockType( blockMetadata, settings ); }; From 092229815cdfbeff29b97be8345f323d5b7a6fcc Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 10:36:03 -0700 Subject: [PATCH 06/16] fix: refine behaviour, update icons and rename --- .../blocks/responsive-container/block.json | 17 +++ .../breakpoint/block.json | 28 +++++ ...-responsive-container-breakpoint-block.php | 38 ++++++ .../responsive-container/breakpoint/edit.js | 77 ++++++++++++ .../responsive-container/breakpoint/index.js | 59 +++++++++ .../class-responsive-container-block.php | 99 +++++++++++++++ .../src/blocks/responsive-container/edit.js | 48 ++++++++ .../src/blocks/responsive-container/index.js | 43 +++++++ .../blocks/responsive-container/style.scss | 11 ++ .../blocks/responsive-container/view-state.js | 69 +++++++++++ .../responsive-container/view-toggle.js | 31 +++++ .../class-test-responsive-container-block.php | 116 ++++++++++++++++++ 12 files changed, 636 insertions(+) create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/block.json create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/block.json create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/index.js create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/class-responsive-container-block.php create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/edit.js create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/index.js create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/style.scss create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/view-state.js create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js create mode 100644 plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/block.json b/plugins/newspack-plugin/src/blocks/responsive-container/block.json new file mode 100644 index 0000000000..a086dd8a70 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/block.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "newspack/responsive-container", + "title": "Responsive Container", + "category": "newspack", + "description": "A container that shows one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint. Ideal for responsive headers and footers.", + "keywords": [ "responsive", "adaptive", "header", "footer", "mobile", "desktop" ], + "textdomain": "newspack-plugin", + "supports": { + "html": false, + "anchor": true, + "position": { "sticky": true } + }, + "editorStyle": "file:../../../dist/blocks.css", + "style": "file:../../../dist/blocks.css" +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/block.json b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/block.json new file mode 100644 index 0000000000..dd3e57b606 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "newspack/responsive-container-breakpoint", + "title": "Breakpoint", + "category": "newspack", + "description": "A single view (desktop or mobile) within a Responsive Container.", + "textdomain": "newspack-plugin", + "parent": [ "newspack/responsive-container" ], + "attributes": { + "view": { + "type": "string", + "enum": [ "desktop", "mobile" ], + "default": "desktop" + }, + "lock": { + "type": "object", + "default": { "move": true, "remove": true } + } + }, + "supports": { + "html": false, + "lock": false, + "inserter": false + }, + "editorStyle": "file:../../../../dist/blocks.css", + "style": "file:../../../../dist/blocks.css" +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php new file mode 100644 index 0000000000..a387c320be --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php @@ -0,0 +1,38 @@ + { + const { getBlockRootClientId, getBlocks, getBlockOrder } = select( 'core/block-editor' ); + const root = getBlockRootClientId( clientId ); + return { + parentClientId: root, + siblings: getBlocks( root ), + isEmpty: getBlockOrder( clientId ).length === 0, + }; + }, + [ clientId ] + ); + + const [ activeView, setView ] = useView( parentClientId ); + const isActive = activeView === view; + + const { selectBlock } = useDispatch( 'core/block-editor' ); + + const switchView = newView => { + setView( newView ); + // Move selection onto the breakpoint that is becoming visible (falling + // back to the container) so the toolbar always anchors to a visible block. + const target = siblings.find( block => block.attributes?.view === newView ); + selectBlock( target ? target.clientId : parentClientId ); + }; + + const className = + 'newspack-responsive-container-breakpoint' + + ` newspack-responsive-container-breakpoint--${ view }` + + ( isActive ? '' : ' is-inactive-view' ) + + ( isEmpty ? ' is-empty' : '' ); + + const blockProps = useBlockProps( { className } ); + + return ( + <> + +
+ +
+ + ); +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/index.js b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/index.js new file mode 100644 index 0000000000..94ec683420 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/index.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { desktop, mobile } from '@wordpress/icons'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import colors from '../../../../packages/colors/colors.module.scss'; + +export const title = __( 'Breakpoint', 'newspack-plugin' ); + +const { name } = metadata; + +export { metadata, name }; + +const foreground = colors[ 'primary-400' ]; + +export const settings = { + title, + icon: { + src: desktop, + foreground, + }, + // A single block type shares one static icon, so two variations — matched to + // the `view` attribute via `isActive` — give the desktop and mobile breakpoints + // their own icon and label in the List View and breadcrumb. + variations: [ + { + name: 'desktop', + title: __( 'Desktop', 'newspack-plugin' ), + icon: { src: desktop, foreground }, + attributes: { view: 'desktop' }, + isActive: [ 'view' ], + }, + { + name: 'mobile', + title: __( 'Mobile', 'newspack-plugin' ), + icon: { src: mobile, foreground }, + attributes: { view: 'mobile' }, + isActive: [ 'view' ], + }, + ], + edit: Edit, + save: ( { attributes } ) => { + const blockProps = useBlockProps.save( { + className: `newspack-responsive-container-breakpoint--${ attributes.view }`, + } ); + return ( +
+ +
+ ); + }, +}; diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/class-responsive-container-block.php b/plugins/newspack-plugin/src/blocks/responsive-container/class-responsive-container-block.php new file mode 100644 index 0000000000..c644af541d --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/class-responsive-container-block.php @@ -0,0 +1,99 @@ += this width; + * mobile breakpoint shows at <= ( breakpoint - 1 ). + */ + const DEFAULT_BREAKPOINT = 782; + + /** + * Initializes the block. + * + * @return void + */ + public static function init() { + add_action( 'init', [ __CLASS__, 'register_block' ] ); + add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_visibility_style' ] ); + } + + /** + * Registers the block type from metadata (static block, no render callback). + * + * @return void + */ + public static function register_block() { + register_block_type_from_metadata( __DIR__ . '/block.json' ); + } + + /** + * Resolves the swap breakpoint. + * + * Precedence (highest first): the `newspack_responsive_container_breakpoint` + * filter, the theme.json `settings.custom.newspackResponsiveBreakpoint` value, + * then the default. + * + * @return int Breakpoint in pixels. + */ + public static function get_breakpoint() { + $default = self::DEFAULT_BREAKPOINT; + $custom = wp_get_global_settings( [ 'custom', 'newspackResponsiveBreakpoint' ] ); + $value = is_numeric( $custom ) ? (int) $custom : $default; + + /** + * Filters the breakpoint (in pixels) at which the Responsive Container + * swaps between its desktop and mobile breakpoints. + * + * @param int $value Resolved breakpoint in pixels. + */ + $value = (int) apply_filters( 'newspack_responsive_container_breakpoint', $value ); + + // A non-positive breakpoint would produce invalid media queries; fall back. + return $value > 0 ? $value : $default; + } + + /** + * Builds the front-end visibility CSS for the current breakpoint. + * + * @return string CSS rules. + */ + public static function get_visibility_css() { + $breakpoint = self::get_breakpoint(); + return sprintf( + '@media (max-width:%1$dpx){.newspack-responsive-container-breakpoint--desktop{display:none !important;}}@media (min-width:%2$dpx){.newspack-responsive-container-breakpoint--mobile{display:none !important;}}', + $breakpoint - 1, + $breakpoint + ); + } + + /** + * Enqueues the single global visibility stylesheet on the front-end. + * + * Always enqueued (a few bytes) because the block commonly lives in header/ + * footer template parts, which `has_block()` cannot detect from post content. + * + * @return void + */ + public static function enqueue_visibility_style() { + wp_register_style( 'newspack-responsive-container', false, [], NEWSPACK_PLUGIN_VERSION ); + wp_enqueue_style( 'newspack-responsive-container' ); + wp_add_inline_style( 'newspack-responsive-container', self::get_visibility_css() ); + } +} +Responsive_Container_Block::init(); diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/edit.js b/plugins/newspack-plugin/src/blocks/responsive-container/edit.js new file mode 100644 index 0000000000..c0d74fd318 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/edit.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import ViewToggle from './view-toggle'; +import { useView } from './view-state'; + +const ALLOWED_BLOCKS = [ 'newspack/responsive-container-breakpoint' ]; +const BLOCKS_TEMPLATE = [ + [ 'newspack/responsive-container-breakpoint', { view: 'desktop' } ], + [ 'newspack/responsive-container-breakpoint', { view: 'mobile' } ], +]; + +/** + * Edit component for the Responsive Container block. + * + * Renders a locked template of exactly two breakpoints (desktop + mobile) and + * the view toggle. The edited view defaults to desktop and is held in ephemeral + * editor-only state (shared with the breakpoints, so toggling never dirties the + * post); the inactive breakpoint hides itself in the editor. The same toggle is + * rendered by each breakpoint so it can be switched without reselecting the + * container. + * + * @param {Object} props Block props. + * @param {string} props.clientId Block client ID. + * + * @return {JSX.Element} The block editor UI. + */ +export default function ResponsiveContainerEdit( { clientId } ) { + const [ view, setView ] = useView( clientId ); + + const blockProps = useBlockProps( { + className: 'newspack-responsive-container', + } ); + + return ( + <> + +
+ +
+ + ); +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/index.js b/plugins/newspack-plugin/src/blocks/responsive-container/index.js new file mode 100644 index 0000000000..34eedd7582 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/index.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { resizeCornerNE as icon } from '@wordpress/icons'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import colors from '../../../packages/colors/colors.module.scss'; +import './style.scss'; + +export const title = __( 'Responsive Container', 'newspack-plugin' ); + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + title, + icon: { + src: icon, + foreground: colors[ 'primary-400' ], + }, + keywords: [ + __( 'responsive', 'newspack-plugin' ), + __( 'adaptive', 'newspack-plugin' ), + __( 'header', 'newspack-plugin' ), + __( 'footer', 'newspack-plugin' ), + __( 'mobile', 'newspack-plugin' ), + __( 'desktop', 'newspack-plugin' ), + ], + description: __( 'Show one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint.', 'newspack-plugin' ), + edit: Edit, + save: () => ( +
+ +
+ ), +}; diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/style.scss b/plugins/newspack-plugin/src/blocks/responsive-container/style.scss new file mode 100644 index 0000000000..375fca6ea0 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/style.scss @@ -0,0 +1,11 @@ +// Editor-only helpers (these classes are added only in the editor). +.newspack-responsive-container-breakpoint { + &.is-inactive-view { + display: none !important; + } + + // Keep an empty breakpoint clickable instead of collapsing. + &.is-empty { + min-height: 48px; + } +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/view-state.js b/plugins/newspack-plugin/src/blocks/responsive-container/view-state.js new file mode 100644 index 0000000000..fcdf931715 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/view-state.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; + +/** + * Ephemeral, editor-only store for which view (desktop / mobile) is being edited + * in each Responsive Container instance. + * + * The value is kept in module memory rather than a block attribute so toggling + * views never dirties the post — the same approach the Overlay Menu block uses + * for its preview state. State is keyed by the container's clientId and shared + * between the container and its breakpoint children. + */ +const views = new Map(); +const listeners = new Map(); + +const DEFAULT_VIEW = 'desktop'; + +/** + * Returns the current view for a container, defaulting to desktop. + * + * @param {string} clientId Container clientId. + * @return {string} 'desktop' | 'mobile' + */ +export function getView( clientId ) { + return views.get( clientId ) || DEFAULT_VIEW; +} + +/** + * Sets the view for a container and notifies subscribers. + * + * @param {string} clientId Container clientId. + * @param {string} view 'desktop' | 'mobile' + */ +function setView( clientId, view ) { + views.set( clientId, view ); + listeners.get( clientId )?.forEach( callback => callback( view ) ); +} + +/** + * Subscribe to the view of a container instance and read/update it. + * + * @param {string} clientId Container clientId (the container's own, or a + * breakpoint's parent clientId). + * @return {Array} `[ view, setViewForContainer ]`. + */ +export function useView( clientId ) { + const [ view, setLocal ] = useState( () => getView( clientId ) ); + + useEffect( () => { + // Re-sync in case the value changed between render and subscribe. + setLocal( getView( clientId ) ); + let subscribers = listeners.get( clientId ); + if ( ! subscribers ) { + subscribers = new Set(); + listeners.set( clientId, subscribers ); + } + subscribers.add( setLocal ); + return () => { + subscribers.delete( setLocal ); + if ( subscribers.size === 0 ) { + listeners.delete( clientId ); + } + }; + }, [ clientId ] ); + + return [ view, newView => setView( clientId, newView ) ]; +} diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js new file mode 100644 index 0000000000..adc35866f6 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { BlockControls } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; + +/** + * Toolbar toggle for switching the edited view (desktop / mobile). + * + * Rendered in the BlockControls of both the container and its breakpoints so the + * active view can be switched from whichever block is selected — mirroring how + * the Overlay Menu surfaces its toggle on more than one related block, and + * avoiding the need to climb back up to the container to switch views. + * + * @param {Object} props Component props. + * @param {string} props.value Current view ( 'desktop' | 'mobile' ). + * @param {Function} props.onChange Called with the chosen view. + * + * @return {JSX.Element} The toolbar control. + */ +export default function ViewToggle( { value, onChange } ) { + return ( + + + onChange( 'desktop' ) } /> + onChange( 'mobile' ) } /> + + + ); +} diff --git a/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php b/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php new file mode 100644 index 0000000000..e7e8123bce --- /dev/null +++ b/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php @@ -0,0 +1,116 @@ +update_with( + [ + 'version' => 2, + 'settings' => [ 'custom' => [ 'newspackResponsiveBreakpoint' => $value ] ], + ] + ); + } + ); + wp_clean_theme_json_cache(); + } + + /** + * Default breakpoint is 782 when nothing overrides it. + */ + public function test_default_breakpoint(): void { + $this->assertSame( 782, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * The filter overrides the default. + */ + public function test_filter_overrides_breakpoint(): void { + add_filter( 'newspack_responsive_container_breakpoint', fn() => 1024 ); + $this->assertSame( 1024, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * A numeric theme.json custom setting is used. + */ + public function test_theme_json_custom_breakpoint(): void { + $this->set_theme_json_breakpoint( 900 ); + $this->assertSame( 900, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * A non-numeric theme.json custom setting falls back to the default. + */ + public function test_non_numeric_custom_falls_back_to_default(): void { + $this->set_theme_json_breakpoint( 'nope' ); + $this->assertSame( 782, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * The filter beats the theme.json custom setting. + */ + public function test_filter_beats_theme_json(): void { + $this->set_theme_json_breakpoint( 900 ); + add_filter( 'newspack_responsive_container_breakpoint', fn() => 600 ); + $this->assertSame( 600, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * A non-positive breakpoint (from filter or theme.json) falls back to the default. + */ + public function test_non_positive_breakpoint_falls_back_to_default(): void { + add_filter( 'newspack_responsive_container_breakpoint', fn() => 0 ); + $this->assertSame( 782, Responsive_Container_Block::get_breakpoint() ); + } + + /** + * The visibility CSS uses the resolved breakpoint and the breakpoint modifier classes. + */ + public function test_visibility_css_uses_breakpoint(): void { + add_filter( 'newspack_responsive_container_breakpoint', fn() => 800 ); + $css = Responsive_Container_Block::get_visibility_css(); + $this->assertStringContainsString( '(max-width:799px)', $css ); + $this->assertStringContainsString( '(min-width:800px)', $css ); + $this->assertStringContainsString( 'newspack-responsive-container-breakpoint--desktop', $css ); + $this->assertStringContainsString( 'newspack-responsive-container-breakpoint--mobile', $css ); + } +} From 2944336aac51a0736b75718e14268fd1816699fe Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 10:44:26 -0700 Subject: [PATCH 07/16] chore: remove duplicate adaptive-container, wire responsive-container Co-Authored-By: Claude Opus 4.8 (1M context) --- .../newspack-plugin/includes/class-blocks.php | 4 +- .../src/blocks/adaptive-container/block.json | 27 ---- .../class-adaptive-container-block.php | 99 --------------- .../src/blocks/adaptive-container/edit.js | 58 --------- .../src/blocks/adaptive-container/index.js | 43 ------- .../blocks/adaptive-container/slot/block.json | 29 ----- .../class-adaptive-container-slot-block.php | 38 ------ .../blocks/adaptive-container/slot/edit.js | 38 ------ .../blocks/adaptive-container/slot/index.js | 38 ------ .../src/blocks/adaptive-container/style.scss | 18 --- plugins/newspack-plugin/src/blocks/index.js | 14 +-- .../class-test-adaptive-container-block.php | 116 ------------------ 12 files changed, 9 insertions(+), 513 deletions(-) delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/block.json delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/edit.js delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/index.js delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/edit.js delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js delete mode 100644 plugins/newspack-plugin/src/blocks/adaptive-container/style.scss delete mode 100644 plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php diff --git a/plugins/newspack-plugin/includes/class-blocks.php b/plugins/newspack-plugin/includes/class-blocks.php index 585a763b66..0d03216995 100644 --- a/plugins/newspack-plugin/includes/class-blocks.php +++ b/plugins/newspack-plugin/includes/class-blocks.php @@ -41,8 +41,8 @@ public static function init() { require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-menu/trigger/class-overlay-menu-trigger-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-menu/panel/class-overlay-menu-panel-block.php'; require_once NEWSPACK_ABSPATH . 'src/blocks/overlay-search/class-overlay-search-block.php'; - require_once NEWSPACK_ABSPATH . 'src/blocks/adaptive-container/class-adaptive-container-block.php'; - require_once NEWSPACK_ABSPATH . 'src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php'; + require_once NEWSPACK_ABSPATH . 'src/blocks/responsive-container/class-responsive-container-block.php'; + require_once NEWSPACK_ABSPATH . 'src/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php'; Social_Icons::init(); } if ( Collections::is_module_active() ) { diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/block.json b/plugins/newspack-plugin/src/blocks/adaptive-container/block.json deleted file mode 100644 index 97f77c7c4b..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/block.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "newspack/adaptive-container", - "title": "Adaptive Container", - "category": "newspack", - "description": "A container that shows one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint. Ideal for responsive headers and footers.", - "keywords": [ "responsive", "adaptive", "header", "footer", "mobile", "desktop" ], - "textdomain": "newspack-plugin", - "attributes": { - "editorView": { - "type": "string", - "enum": [ "desktop", "mobile" ], - "default": "desktop" - } - }, - "providesContext": { - "newspack-adaptive-container/editorView": "editorView" - }, - "supports": { - "html": false, - "anchor": true, - "position": { "sticky": true } - }, - "editorStyle": "file:../../../dist/blocks.css", - "style": "file:../../../dist/blocks.css" -} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php b/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php deleted file mode 100644 index 806c90b8e9..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/class-adaptive-container-block.php +++ /dev/null @@ -1,99 +0,0 @@ -= this width; - * mobile slot shows at <= ( breakpoint - 1 ). - */ - const DEFAULT_BREAKPOINT = 782; - - /** - * Initializes the block. - * - * @return void - */ - public static function init() { - add_action( 'init', [ __CLASS__, 'register_block' ] ); - add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_visibility_style' ] ); - } - - /** - * Registers the block type from metadata (static block, no render callback). - * - * @return void - */ - public static function register_block() { - register_block_type_from_metadata( __DIR__ . '/block.json' ); - } - - /** - * Resolves the swap breakpoint. - * - * Precedence (highest first): the `newspack_adaptive_container_breakpoint` - * filter, the theme.json `settings.custom.newspackAdaptiveBreakpoint` value, - * then the default. - * - * @return int Breakpoint in pixels. - */ - public static function get_breakpoint() { - $default = self::DEFAULT_BREAKPOINT; - $custom = wp_get_global_settings( [ 'custom', 'newspackAdaptiveBreakpoint' ] ); - $value = is_numeric( $custom ) ? (int) $custom : $default; - - /** - * Filters the breakpoint (in pixels) at which the Adaptive Container - * swaps between its desktop and mobile slots. - * - * @param int $value Resolved breakpoint in pixels. - */ - $value = (int) apply_filters( 'newspack_adaptive_container_breakpoint', $value ); - - // A non-positive breakpoint would produce invalid media queries; fall back. - return $value > 0 ? $value : $default; - } - - /** - * Builds the front-end visibility CSS for the current breakpoint. - * - * @return string CSS rules. - */ - public static function get_visibility_css() { - $breakpoint = self::get_breakpoint(); - return sprintf( - '@media (max-width:%1$dpx){.newspack-adaptive-container-slot--desktop{display:none !important;}}@media (min-width:%2$dpx){.newspack-adaptive-container-slot--mobile{display:none !important;}}', - $breakpoint - 1, - $breakpoint - ); - } - - /** - * Enqueues the single global visibility stylesheet on the front-end. - * - * Always enqueued (a few bytes) because the block commonly lives in header/ - * footer template parts, which `has_block()` cannot detect from post content. - * - * @return void - */ - public static function enqueue_visibility_style() { - wp_register_style( 'newspack-adaptive-container', false, [], NEWSPACK_PLUGIN_VERSION ); - wp_enqueue_style( 'newspack-adaptive-container' ); - wp_add_inline_style( 'newspack-adaptive-container', self::get_visibility_css() ); - } -} -Adaptive_Container_Block::init(); diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js b/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js deleted file mode 100644 index 9aa4b03d54..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/edit.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { desktop, mobile } from '@wordpress/icons'; -import { BlockControls, InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; - -const ALLOWED_BLOCKS = [ 'newspack/adaptive-container-slot' ]; -const BLOCKS_TEMPLATE = [ - [ 'newspack/adaptive-container-slot', { view: 'desktop' } ], - [ 'newspack/adaptive-container-slot', { view: 'mobile' } ], -]; - -/** - * Edit component for the Adaptive Container block. - * - * Renders a locked template of exactly two slots (desktop + mobile) and a - * toolbar toggle that sets `editorView`. The active view is shared with the - * slots via block context; the inactive slot hides itself in the editor. - * - * @param {Object} props Block props. - * @param {Object} props.attributes Block attributes. - * @param {Function} props.setAttributes Attribute setter. - * - * @return {JSX.Element} The block editor UI. - */ -export default function AdaptiveContainerEdit( { attributes, setAttributes } ) { - const { editorView } = attributes; - - const blockProps = useBlockProps( { - className: `newspack-adaptive-container is-editing-${ editorView }`, - } ); - - return ( - <> - - - setAttributes( { editorView: 'desktop' } ) } - /> - setAttributes( { editorView: 'mobile' } ) } - /> - - -
- -
- - ); -} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/index.js b/plugins/newspack-plugin/src/blocks/adaptive-container/index.js deleted file mode 100644 index 5a514be6ba..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/index.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { mobile as icon } from '@wordpress/icons'; -import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import metadata from './block.json'; -import Edit from './edit'; -import colors from '../../../packages/colors/colors.module.scss'; -import './style.scss'; - -export const title = __( 'Adaptive Container', 'newspack-plugin' ); - -const { name } = metadata; - -export { metadata, name }; - -export const settings = { - title, - icon: { - src: icon, - foreground: colors[ 'primary-400' ], - }, - keywords: [ - __( 'responsive', 'newspack-plugin' ), - __( 'adaptive', 'newspack-plugin' ), - __( 'header', 'newspack-plugin' ), - __( 'footer', 'newspack-plugin' ), - __( 'mobile', 'newspack-plugin' ), - __( 'desktop', 'newspack-plugin' ), - ], - description: __( 'Show one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint.', 'newspack-plugin' ), - edit: Edit, - save: () => ( -
- -
- ), -}; diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json deleted file mode 100644 index 271c1b0bcf..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/block.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "newspack/adaptive-container-slot", - "title": "Adaptive Slot", - "category": "newspack", - "description": "A single view (desktop or mobile) within an Adaptive Container.", - "textdomain": "newspack-plugin", - "parent": [ "newspack/adaptive-container" ], - "usesContext": [ "newspack-adaptive-container/editorView" ], - "attributes": { - "view": { - "type": "string", - "enum": [ "desktop", "mobile" ], - "default": "desktop" - }, - "lock": { - "type": "object", - "default": { "move": true, "remove": true } - } - }, - "supports": { - "html": false, - "lock": false, - "inserter": false - }, - "editorStyle": "file:../../../../dist/blocks.css", - "style": "file:../../../../dist/blocks.css" -} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php deleted file mode 100644 index 49a77b2f87..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/class-adaptive-container-slot-block.php +++ /dev/null @@ -1,38 +0,0 @@ - - { label } - - - ); -} diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js b/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js deleted file mode 100644 index 73ce32acb8..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/slot/index.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { mobile as icon } from '@wordpress/icons'; -import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import metadata from './block.json'; -import Edit from './edit'; -import colors from '../../../../packages/colors/colors.module.scss'; - -export const title = __( 'Adaptive Slot', 'newspack-plugin' ); - -const { name } = metadata; - -export { metadata, name }; - -export const settings = { - title, - icon: { - src: icon, - foreground: colors[ 'primary-400' ], - }, - edit: Edit, - save: ( { attributes } ) => { - const blockProps = useBlockProps.save( { - className: `newspack-adaptive-container-slot--${ attributes.view }`, - } ); - return ( -
- -
- ); - }, -}; diff --git a/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss b/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss deleted file mode 100644 index 97088f99e5..0000000000 --- a/plugins/newspack-plugin/src/blocks/adaptive-container/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -// Editor-only helpers. Front-end slot visibility is handled by the PHP-generated -// media-query stylesheet, not here. These classes are only added in the editor, -// so they are inert on the front-end even though they ship in the shared bundle. -.newspack-adaptive-container-slot { - &.is-inactive-view { - display: none !important; - } - - &__label { - display: block; - margin-bottom: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - opacity: 0.6; - } -} diff --git a/plugins/newspack-plugin/src/blocks/index.js b/plugins/newspack-plugin/src/blocks/index.js index 44f3dbb03a..dfbb953ea6 100644 --- a/plugins/newspack-plugin/src/blocks/index.js +++ b/plugins/newspack-plugin/src/blocks/index.js @@ -25,8 +25,8 @@ import * as overlayMenu from './overlay-menu'; import * as overlayMenuTrigger from './overlay-menu/trigger'; import * as overlayMenuPanel from './overlay-menu/panel'; import * as overlaySearch from './overlay-search'; -import * as adaptiveContainer from './adaptive-container'; -import * as adaptiveContainerSlot from './adaptive-container/slot'; +import * as responsiveContainer from './responsive-container'; +import * as responsiveContainerBreakpoint from './responsive-container/breakpoint'; /** * Block Scripts @@ -51,8 +51,8 @@ export const blocks = [ overlayMenuTrigger, overlayMenuPanel, overlaySearch, - adaptiveContainer, - adaptiveContainerSlot, + responsiveContainer, + responsiveContainerBreakpoint, ]; const readerActivationBlocks = [ 'newspack/reader-registration', 'newspack/my-account-button' ]; @@ -71,10 +71,10 @@ const blockThemeBlocks = [ 'newspack/overlay-menu-panel', 'newspack/my-account-button', 'newspack/overlay-search', - 'newspack/adaptive-container', - 'newspack/adaptive-container-slot', + 'newspack/responsive-container', + 'newspack/responsive-container-breakpoint', ]; -const siteEditorOnlyBlocks = [ 'newspack/adaptive-container', 'newspack/adaptive-container-slot' ]; +const siteEditorOnlyBlocks = [ 'newspack/responsive-container', 'newspack/responsive-container-breakpoint' ]; /** * Function to register an individual block. diff --git a/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php b/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php deleted file mode 100644 index 76be4eb90c..0000000000 --- a/plugins/newspack-plugin/tests/unit-tests/class-test-adaptive-container-block.php +++ /dev/null @@ -1,116 +0,0 @@ -update_with( - [ - 'version' => 2, - 'settings' => [ 'custom' => [ 'newspackAdaptiveBreakpoint' => $value ] ], - ] - ); - } - ); - wp_clean_theme_json_cache(); - } - - /** - * Default breakpoint is 782 when nothing overrides it. - */ - public function test_default_breakpoint(): void { - $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * The filter overrides the default. - */ - public function test_filter_overrides_breakpoint(): void { - add_filter( 'newspack_adaptive_container_breakpoint', fn() => 1024 ); - $this->assertSame( 1024, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * A numeric theme.json custom setting is used. - */ - public function test_theme_json_custom_breakpoint(): void { - $this->set_theme_json_breakpoint( 900 ); - $this->assertSame( 900, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * A non-numeric theme.json custom setting falls back to the default. - */ - public function test_non_numeric_custom_falls_back_to_default(): void { - $this->set_theme_json_breakpoint( 'nope' ); - $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * The filter beats the theme.json custom setting. - */ - public function test_filter_beats_theme_json(): void { - $this->set_theme_json_breakpoint( 900 ); - add_filter( 'newspack_adaptive_container_breakpoint', fn() => 600 ); - $this->assertSame( 600, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * A non-positive breakpoint (from filter or theme.json) falls back to the default. - */ - public function test_non_positive_breakpoint_falls_back_to_default(): void { - add_filter( 'newspack_adaptive_container_breakpoint', fn() => 0 ); - $this->assertSame( 782, Adaptive_Container_Block::get_breakpoint() ); - } - - /** - * The visibility CSS uses the resolved breakpoint and the slot modifier classes. - */ - public function test_visibility_css_uses_breakpoint(): void { - add_filter( 'newspack_adaptive_container_breakpoint', fn() => 800 ); - $css = Adaptive_Container_Block::get_visibility_css(); - $this->assertStringContainsString( '(max-width:799px)', $css ); - $this->assertStringContainsString( '(min-width:800px)', $css ); - $this->assertStringContainsString( 'newspack-adaptive-container-slot--desktop', $css ); - $this->assertStringContainsString( 'newspack-adaptive-container-slot--mobile', $css ); - } -} From a35786cabbb865cc9f0ced9a12e5c4b3ea091367 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 11:53:24 -0700 Subject: [PATCH 08/16] fix(blocks): only show appender while a breakpoint is empty Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/responsive-container/breakpoint/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js index 5e77293313..47db9440ce 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js @@ -70,7 +70,7 @@ export default function ResponsiveContainerBreakpointEdit( { attributes, clientI <>
- +
); From 1869924b474270aff7931deb2bd0f0033e5cb2a5 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 12:10:05 -0700 Subject: [PATCH 09/16] fix: hide placeholder inserter when the block is actually populated --- .../src/blocks/responsive-container/breakpoint/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js index 47db9440ce..46c8d47abf 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js @@ -70,7 +70,7 @@ export default function ResponsiveContainerBreakpointEdit( { attributes, clientI <>
- +
); From 358ee8a3e161b70af6eda9a7e533308bf54db2cf Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 12:33:44 -0700 Subject: [PATCH 10/16] fix(blocks): add front-end class to responsive container save Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/responsive-container/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/index.js b/plugins/newspack-plugin/src/blocks/responsive-container/index.js index 34eedd7582..8eafae55fe 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/index.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/index.js @@ -33,10 +33,13 @@ export const settings = { __( 'mobile', 'newspack-plugin' ), __( 'desktop', 'newspack-plugin' ), ], - description: __( 'Show one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint.', 'newspack-plugin' ), + description: __( + 'A container that shows one set of blocks on desktop and another on mobile, swapping automatically at a breakpoint. Ideal for responsive headers and footers.', + 'newspack-plugin' + ), edit: Edit, save: () => ( -
+
), From 441715aabfbe7ddbe4a6a02c6addb7672afe95d7 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 12:44:05 -0700 Subject: [PATCH 11/16] test(blocks): cover responsive container view-state hook Co-Authored-By: Claude Opus 4.8 (1M context) --- .../responsive-container/view-state.test.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 plugins/newspack-plugin/src/blocks/responsive-container/view-state.test.js diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/view-state.test.js b/plugins/newspack-plugin/src/blocks/responsive-container/view-state.test.js new file mode 100644 index 0000000000..c3a750c573 --- /dev/null +++ b/plugins/newspack-plugin/src/blocks/responsive-container/view-state.test.js @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import { renderHook, act } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { getView, useView } from './view-state'; + +/** + * Each test uses a unique clientId because the view store is module-global and + * persists for the lifetime of the editor session (and so across tests here). + */ +describe( 'responsive-container view-state', () => { + it( 'defaults to the desktop view for an unknown container', () => { + expect( getView( 'unknown' ) ).toBe( 'desktop' ); + } ); + + it( 'useView returns the current view and a setter', () => { + const { result } = renderHook( () => useView( 'returns' ) ); + const [ view, setView ] = result.current; + expect( view ).toBe( 'desktop' ); + expect( typeof setView ).toBe( 'function' ); + } ); + + it( 'updates the view and reflects it in getView', () => { + const { result } = renderHook( () => useView( 'updates' ) ); + act( () => result.current[ 1 ]( 'mobile' ) ); + expect( result.current[ 0 ] ).toBe( 'mobile' ); + expect( getView( 'updates' ) ).toBe( 'mobile' ); + } ); + + it( 'shares state across instances of the same container', () => { + // The container and its breakpoint children all subscribe to the same + // clientId; setting from one must update the others. + const container = renderHook( () => useView( 'shared' ) ); + const breakpoint = renderHook( () => useView( 'shared' ) ); + + act( () => container.result.current[ 1 ]( 'mobile' ) ); + + expect( container.result.current[ 0 ] ).toBe( 'mobile' ); + expect( breakpoint.result.current[ 0 ] ).toBe( 'mobile' ); + } ); + + it( 'keeps separate containers independent', () => { + const first = renderHook( () => useView( 'independent-a' ) ); + const second = renderHook( () => useView( 'independent-b' ) ); + + act( () => first.result.current[ 1 ]( 'mobile' ) ); + + expect( first.result.current[ 0 ] ).toBe( 'mobile' ); + expect( second.result.current[ 0 ] ).toBe( 'desktop' ); + } ); + + it( 'unsubscribing one instance does not break the others', () => { + const a = renderHook( () => useView( 'cleanup' ) ); + const b = renderHook( () => useView( 'cleanup' ) ); + + a.unmount(); + act( () => b.result.current[ 1 ]( 'mobile' ) ); + + expect( b.result.current[ 0 ] ).toBe( 'mobile' ); + expect( getView( 'cleanup' ) ).toBe( 'mobile' ); + } ); + + it( 'initializes from the persisted view when remounted', () => { + // View is ephemeral (not a block attribute) but lives in module memory, + // so a remounted instance should pick up the last-set value. + const first = renderHook( () => useView( 'persist' ) ); + act( () => first.result.current[ 1 ]( 'mobile' ) ); + first.unmount(); + + const second = renderHook( () => useView( 'persist' ) ); + expect( second.result.current[ 0 ] ).toBe( 'mobile' ); + } ); +} ); From 83302236af819a8d33cf4cd8585040541511b38b Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 14:11:08 -0700 Subject: [PATCH 12/16] fix(blocks): address responsive container review feedback Co-Authored-By: Claude Opus 4.8 (1M context) --- .../responsive-container/view-toggle.js | 14 ++++++-- .../class-test-responsive-container-block.php | 33 ++++++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js index adc35866f6..131029f96d 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js @@ -23,8 +23,18 @@ export default function ViewToggle( { value, onChange } ) { return ( - onChange( 'desktop' ) } /> - onChange( 'mobile' ) } /> + onChange( 'desktop' ) } + /> + onChange( 'mobile' ) } + /> ); diff --git a/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php b/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php index e7e8123bce..322439d4ed 100644 --- a/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php +++ b/plugins/newspack-plugin/tests/unit-tests/class-test-responsive-container-block.php @@ -15,6 +15,14 @@ */ class Newspack_Test_Responsive_Container_Block extends WP_UnitTestCase { + /** + * The test-only `wp_theme_json_data_theme` filter, kept so teardown can + * remove exactly this callback rather than wiping the whole hook. + * + * @var callable|null + */ + private $theme_json_filter = null; + /** * Setup. */ @@ -29,7 +37,10 @@ public function set_up(): void { */ public function tear_down(): void { remove_all_filters( 'newspack_responsive_container_breakpoint' ); - remove_all_filters( 'wp_theme_json_data_theme' ); + if ( $this->theme_json_filter ) { + remove_filter( 'wp_theme_json_data_theme', $this->theme_json_filter ); + $this->theme_json_filter = null; + } wp_clean_theme_json_cache(); parent::tear_down(); } @@ -40,17 +51,15 @@ public function tear_down(): void { * @param mixed $value Value to set for settings.custom.newspackResponsiveBreakpoint. */ private function set_theme_json_breakpoint( $value ): void { - add_filter( - 'wp_theme_json_data_theme', - function ( $theme_json ) use ( $value ) { - return $theme_json->update_with( - [ - 'version' => 2, - 'settings' => [ 'custom' => [ 'newspackResponsiveBreakpoint' => $value ] ], - ] - ); - } - ); + $this->theme_json_filter = function ( $theme_json ) use ( $value ) { + return $theme_json->update_with( + [ + 'version' => 2, + 'settings' => [ 'custom' => [ 'newspackResponsiveBreakpoint' => $value ] ], + ] + ); + }; + add_filter( 'wp_theme_json_data_theme', $this->theme_json_filter ); wp_clean_theme_json_cache(); } From 6af308d4bac27193636658e340651c51f1a5b58d Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Wed, 3 Jun 2026 16:07:32 -0700 Subject: [PATCH 13/16] fix(blocks): guard breakpoint against missing parent root Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/responsive-container/breakpoint/edit.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js index 46c8d47abf..ae8c273fc0 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js @@ -38,14 +38,18 @@ export default function ResponsiveContainerBreakpointEdit( { attributes, clientI const root = getBlockRootClientId( clientId ); return { parentClientId: root, - siblings: getBlocks( root ), + // Guard against a missing root during transitions (e.g. template-part + // switches): getBlocks( falsy ) returns the top-level blocks, not []. + siblings: root ? getBlocks( root ) : [], isEmpty: getBlockOrder( clientId ).length === 0, }; }, [ clientId ] ); - const [ activeView, setView ] = useView( parentClientId ); + // Key to our own clientId until the parent resolves, so transient state is + // never shared under a `null` key; useView re-subscribes when it changes. + const [ activeView, setView ] = useView( parentClientId || clientId ); const isActive = activeView === view; const { selectBlock } = useDispatch( 'core/block-editor' ); @@ -55,7 +59,7 @@ export default function ResponsiveContainerBreakpointEdit( { attributes, clientI // Move selection onto the breakpoint that is becoming visible (falling // back to the container) so the toolbar always anchors to a visible block. const target = siblings.find( block => block.attributes?.view === newView ); - selectBlock( target ? target.clientId : parentClientId ); + selectBlock( target ? target.clientId : parentClientId || clientId ); }; const className = From 886ff1f80f510c9a797461ae4662db0d2c87200c Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Thu, 4 Jun 2026 09:26:52 -0700 Subject: [PATCH 14/16] fix(blocks): keep default appender after breakpoint has content Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/responsive-container/breakpoint/edit.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js index ae8c273fc0..d4c7461ef1 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js @@ -74,7 +74,13 @@ export default function ResponsiveContainerBreakpointEdit( { attributes, clientI <>
- +
); From 694169424bff2e80dbd441e10e0a583c016ae40b Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Thu, 4 Jun 2026 11:38:38 -0700 Subject: [PATCH 15/16] fix(blocks): order responsive container toggle mobile-first Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/blocks/responsive-container/view-toggle.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js index 131029f96d..6689b4289d 100644 --- a/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js +++ b/plugins/newspack-plugin/src/blocks/responsive-container/view-toggle.js @@ -23,18 +23,18 @@ export default function ViewToggle( { value, onChange } ) { return ( - onChange( 'desktop' ) } - /> onChange( 'mobile' ) } /> + onChange( 'desktop' ) } + /> ); From e0522e10c2ebeb4eb5cc3a93e585d4b511071ab2 Mon Sep 17 00:00:00 2001 From: Laurel Fulford Date: Mon, 8 Jun 2026 12:52:04 -0700 Subject: [PATCH 16/16] feat: add repsonsive container block to theme --- themes/newspack-block-theme/parts/footer.html | 12 ++++++++++-- themes/newspack-block-theme/parts/header.html | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/themes/newspack-block-theme/parts/footer.html b/themes/newspack-block-theme/parts/footer.html index f165bebade..e7c1410549 100644 --- a/themes/newspack-block-theme/parts/footer.html +++ b/themes/newspack-block-theme/parts/footer.html @@ -1,3 +1,11 @@ - + +
+
+
+ - \ No newline at end of file + +
+
+
+ \ No newline at end of file diff --git a/themes/newspack-block-theme/parts/header.html b/themes/newspack-block-theme/parts/header.html index 57bbfcdf21..3d556506d9 100644 --- a/themes/newspack-block-theme/parts/header.html +++ b/themes/newspack-block-theme/parts/header.html @@ -1,3 +1,9 @@ - + +
+
+ - \ No newline at end of file + +
+
+ \ No newline at end of file