Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugins/newspack-plugin/includes/class-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +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/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() ) {
Expand Down
11 changes: 11 additions & 0 deletions plugins/newspack-plugin/src/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 responsiveContainer from './responsive-container';
import * as responsiveContainerBreakpoint from './responsive-container/breakpoint';

/**
* Block Scripts
Expand All @@ -49,6 +51,8 @@ export const blocks = [
overlayMenuTrigger,
overlayMenuPanel,
overlaySearch,
responsiveContainer,
responsiveContainerBreakpoint,
];

const readerActivationBlocks = [ 'newspack/reader-registration', 'newspack/my-account-button' ];
Expand All @@ -67,7 +71,10 @@ const blockThemeBlocks = [
'newspack/overlay-menu-panel',
'newspack/my-account-button',
'newspack/overlay-search',
'newspack/responsive-container',
'newspack/responsive-container-breakpoint',
];
const siteEditorOnlyBlocks = [ 'newspack/responsive-container', 'newspack/responsive-container-breakpoint' ];

/**
* Function to register an individual block.
Expand Down Expand Up @@ -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 );
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* Responsive Container Breakpoint Block.
*
* @package Newspack
*/

namespace Newspack\Blocks\Responsive_Container;

defined( 'ABSPATH' ) || exit;

/**
* Responsive_Container_Breakpoint_Block Class.
*
* Registers the child breakpoint block. Content is saved statically; this server-side
* registration exists so block supports are recognized.
*/
final class Responsive_Container_Breakpoint_Block {

/**
* Initializes the block.
*
* @return void
*/
public static function init() {
add_action( 'init', [ __CLASS__, 'register_block' ] );
}

/**
* Registers the block type from metadata.
*
* @return void
*/
public static function register_block() {
register_block_type_from_metadata( __DIR__ . '/block.json' );
}
}
Responsive_Container_Breakpoint_Block::init();
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* WordPress dependencies
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import ViewToggle from '../view-toggle';
import { useView } from '../view-state';

/**
* Edit component for a Responsive Container breakpoint.
*
* Reads the container's edited view from ephemeral shared state and hides itself
* when its own `view` is not the active one (content stays in the DOM, so it is
* preserved and editable once switched to). It also renders the shared view
* toggle so the view can be switched without reselecting the container; because
* the view is ephemeral, toggling never dirties the post.
*
* Switching views moves the selection to the breakpoint that is becoming visible
* — the hidden breakpoint uses `display: none`, and a selected-but-hidden block
* has no position for its toolbar to anchor to, so selection must follow the view.
*
* @param {Object} props Block props.
* @param {Object} props.attributes Block attributes.
* @param {string} props.clientId Block client ID.
*
* @return {JSX.Element} The block editor UI.
*/
export default function ResponsiveContainerBreakpointEdit( { attributes, clientId } ) {
const { view } = attributes;

const { parentClientId, siblings, isEmpty } = useSelect(
select => {
const { getBlockRootClientId, getBlocks, getBlockOrder } = select( 'core/block-editor' );
const root = getBlockRootClientId( clientId );
return {
parentClientId: 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 ]
);

// 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' );

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 || clientId );
};
Comment thread
laurelfulford marked this conversation as resolved.

const className =
'newspack-responsive-container-breakpoint' +
` newspack-responsive-container-breakpoint--${ view }` +
( isActive ? '' : ' is-inactive-view' ) +
( isEmpty ? ' is-empty' : '' );

const blockProps = useBlockProps( { className } );

return (
<>
<ViewToggle value={ activeView } onChange={ switchView } />
<div { ...blockProps }>
<InnerBlocks
templateLock={ false }
// Show the placeholder appender only while empty; once the breakpoint
// has content, fall back to the default appender (undefined) so more
// blocks can still be added in place.
renderAppender={ isEmpty ? InnerBlocks.ButtonBlockAppender : undefined }
/>
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
/**
* Responsive Container Block.
*
* @package Newspack
*/

namespace Newspack\Blocks\Responsive_Container;

defined( 'ABSPATH' ) || exit;

/**
* Responsive_Container_Block Class.
*
* Registers the parent container block and outputs the front-end visibility
* stylesheet that swaps the desktop/mobile breakpoints at the resolved breakpoint.
*/
final class Responsive_Container_Block {

/**
* Default breakpoint, in pixels. Desktop breakpoint shows at >= 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();
Loading
Loading