-
Notifications
You must be signed in to change notification settings - Fork 9
feat(block-theme): add a responsive container block for headers/footer #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
laurelfulford
wants to merge
16
commits into
main
Choose a base branch
from
feat/adaptive-container-block
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+758
−0
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
86a9adf
feat(blocks): add adaptive container breakpoint logic
laurelfulford 6e40f65
feat(blocks): register adaptive container in block bootstrap
laurelfulford 037be6f
feat(blocks): add adaptive container parent block UI
laurelfulford c18b6c6
feat(blocks): add adaptive container slot child block
laurelfulford a04d987
feat(blocks): register adaptive container blocks in site editor
laurelfulford 0922298
fix: refine behaviour, update icons and rename
laurelfulford 2944336
chore: remove duplicate adaptive-container, wire responsive-container
laurelfulford a35786c
fix(blocks): only show appender while a breakpoint is empty
laurelfulford 1869924
fix: hide placeholder inserter when the block is actually populated
laurelfulford 358ee8a
fix(blocks): add front-end class to responsive container save
laurelfulford 441715a
test(blocks): cover responsive container view-state hook
laurelfulford 8330223
fix(blocks): address responsive container review feedback
laurelfulford 6af308d
fix(blocks): guard breakpoint against missing parent root
laurelfulford 886ff1f
fix(blocks): keep default appender after breakpoint has content
laurelfulford 6941694
fix(blocks): order responsive container toggle mobile-first
laurelfulford 687183f
Merge branch 'main' into feat/adaptive-container-block
laurelfulford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
plugins/newspack-plugin/src/blocks/responsive-container/block.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } |
28 changes: 28 additions & 0 deletions
28
plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/block.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } |
38 changes: 38 additions & 0 deletions
38
...rc/blocks/responsive-container/breakpoint/class-responsive-container-breakpoint-block.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |
87 changes: 87 additions & 0 deletions
87
plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/edit.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ); | ||
| }; | ||
|
|
||
| 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> | ||
| </> | ||
| ); | ||
| } | ||
59 changes: 59 additions & 0 deletions
59
plugins/newspack-plugin/src/blocks/responsive-container/breakpoint/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ); | ||
| }, | ||
| }; |
99 changes: 99 additions & 0 deletions
99
plugins/newspack-plugin/src/blocks/responsive-container/class-responsive-container-block.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.