Skip to content
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

New Block: Try a modal/popover block #393

Draft
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions mu-plugins/blocks/modal/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
137 changes: 137 additions & 0 deletions mu-plugins/blocks/modal/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php
/**
* Block Name: Modal
* Description: A container hidden behind a button, which pops up on click.
*
* This contains 3 variations on this concept:
* - Modal: The container that floats in the middle of the screen. Clicking
* outside the container or hitting escape will close it.
* - Popover: The container stays attached to the toggle button but overlaps
* the content, like a dropdown menu. Clicking outside the container or
* hitting escape will close it.
* - Collapsed (inline): The container is hidden by default, but when expanded,
* pushes the content below down to not overlap. Only closed by clicking the
* toggle, to prevent content jumps.
*
* @package wporg
*/

namespace WordPressdotorg\MU_Plugins\Modal_Block;

add_action( 'init', __NAMESPACE__ . '\init' );

/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/reference/functions/register_block_type/
*/
function init() {
register_block_type(
__DIR__ . '/build/modal',
array(
'render_callback' => __NAMESPACE__ . '\render',
)
);
register_block_type(
__DIR__ . '/build/inner-content',
array(
'render_callback' => __NAMESPACE__ . '\render_inner_content',
)
);
}

/**
* Returns a local SVG icon.
*
* @param string $icon Name of the icon to render, corresponds to file name.
* @return string
*/
function render_icon( $icon ) {
$file_path = __DIR__ . '/icons/' . $icon . '.svg';
if ( file_exists( $file_path ) ) {
return file_get_contents( $file_path );
}
}

/**
* Render the block content for the modal/popover/inline container.
*
* The modal requires more HTML for micromodal support, but inline and popover
* use the same markup with slightly different CSS.
*
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
*
* @return string Returns the block markup.
*/
function render_inner_content( $attributes, $content, $block ) {
// Fetch the type from the parent block.
$type = $block->context['wporg/modal/type'] ?? '';
if ( ! $type ) {
return;
}

if ( 'inline' === $type || 'popover' === $type ) {
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'wporg-modal__modal alignwide' ) );
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
$content
);
}

$wrapper_attributes = get_block_wrapper_attributes();
$close_icon = render_icon( 'close' );

return <<<HTML
<div class="wporg-modal__modal" aria-hidden="true">
<div tabindex="-1" class="wporg-modal__overlay" data-micromodal-close>
<div class="wporg-modal__container" role="dialog" aria-modal="true">
<div {$wrapper_attributes}>
<button class="wporg-modal__button" aria-label="Close" data-micromodal-close>{$close_icon}</button>
{$content}
</div>
</div>
</div>
</div>
HTML;
}

/**
* Render the block content for the parent Modal block (button). The modal
* container itself is rendered by the child block.
*
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
*
* @return string Returns the block markup.
*/
function render( $attributes, $content, $block ) {
$type = $attributes['type'];
$class = 'is-type-' . $type;

// Replace the button block's link with an HTML button, retain all block styles.
// This only replaces the first link, so the modal can still contain links/buttons.
if ( preg_match( '/(<a([^>]*)>)(.*?)(<\/a>)/i', $content, $matches ) ) {
$link = $matches[0]; // full match.
// Add in the modal button class, used by the view script.
$attributes = str_replace( 'class="', 'class="wporg-modal__button ', $matches[2] );
$button = sprintf(
'<button aria-expanded="false" %1$s>%2$s</button>',
$attributes,
$matches[3] // innerText.
);
$content = str_replace( $link, $button, $content );
}

$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $class ) );
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
$content
);
}
9 changes: 9 additions & 0 deletions mu-plugins/blocks/modal/postcss/editor-style.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.wporg-modal__modal {
border: 1px dashed #000;
padding: 8px;
}

.wp-block-wporg-modal.is-selected .wporg-modal__modal,
.wp-block-wporg-modal.has-child-selected .wporg-modal__modal {
display: block;
}
93 changes: 93 additions & 0 deletions mu-plugins/blocks/modal/postcss/style.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
.wporg-modal__modal {
display: none;
}

.wporg-modal__modal.is-open {
display: block;
}

.wporg-modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999; /* Needs to be at leas this to overlay admin bar. */
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
}

.wporg-modal__container {
width: 100%;
max-width: var(--wp--style--global--content-size);
max-height: 100vh;
overflow-y: auto;
box-sizing: border-box;

/* This extra div is needed to prevent clicks inside the container padding from closing the modal. */
& > div {
position: relative;
padding: var(--wp--preset--spacing--40);
background-color: var(--wp--preset--color--white);

& > button + * {
margin-top: 0;
}

& > *:last-child {
margin-bottom: 0;
}
}
}

.wporg-modal__button[data-micromodal-close] {
position: absolute;
top: var(--wp--preset--spacing--10);
right: var(--wp--preset--spacing--10);
width: calc(24px + var(--wp--preset--spacing--10));
height: calc(24px + var(--wp--preset--spacing--10));
line-height: 1;
padding: 0;
box-shadow: none;
text-decoration: none;
border: none;
color: currentColor;
background-color: transparent;
background-image: none;
cursor: pointer;
z-index: 10; /* Just enough to raise it above the other modal content. */

& svg {
vertical-align: middle;
fill: currentColor;
}
}

.wp-block-wporg-modal.is-type-popover {
position: relative;

& .wporg-modal__modal {
position: absolute;
top: 100%;
left: 0;
z-index: 2;

opacity: 0;
overflow: hidden;
visibility: hidden;
height: 0;
width: 0;

&.is-open {
opacity: 1;
overflow: visible;
visibility: visible;

height: auto;
min-width: 200px;
width: 100%;
}
}
}
45 changes: 45 additions & 0 deletions mu-plugins/blocks/modal/src/inner-content/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "wporg/modal-inner-content",
"title": "Modal Content",
"icon": "text-page",
"category": "layout",
"description": "Hidden content which appears when a button is clicked.",
"textdomain": "wporg",
"attributes": {},
"usesContext": [ "wporg/modal/type" ],
"parent": [ "wporg/modal" ],
"supports": {
"color": {
"text": true,
"background": true,
"link": true
},
"spacing": {
"margin": true,
"padding": true,
"__experimentalDefaultControls": {
"margin": true,
"padding": true
}
},
"typography": {
"fontSize": true,
"lineHeight": true
},
"__experimentalBorder": {
"color": true,
"radius": true,
"style": true,
"width": true,
"__experimentalDefaultControls": {
"color": true,
"radius": true,
"style": true,
"width": true
}
}
},
"editorScript": "file:./index.js"
}
35 changes: 35 additions & 0 deletions mu-plugins/blocks/modal/src/inner-content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* WordPress dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import metadata from './block.json';

function Edit() {
return (
<div className="wporg-modal__modal">
<div { ...useBlockProps() }>
<InnerBlocks
templateLock={ false }
template={ [
[
'core/navigation',
{ overlayMenu: 'never', layout: { type: 'flex', orientation: 'vertical' } },
],
] }
/>
</div>
</div>
);
}

registerBlockType( metadata.name, {
edit: Edit,
save: () => {
return <InnerBlocks.Content />;
},
} );
34 changes: 34 additions & 0 deletions mu-plugins/blocks/modal/src/modal/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "wporg/modal",
"title": "Modal",
"icon": "button",
"category": "layout",
"description": "A container hidden behind a button, which pops up on click.",
"textdomain": "wporg",
"attributes": {
"label": {
"type": "string"
},
"type": {
"type": "string",
"default": "modal",
"enum": [ "inline", "modal", "popover" ]
}
},
"supports": {
"align": true,
"spacing": {
"margin": true,
"padding": true
}
},
"providesContext": {
"wporg/modal/type": "type"
},
"editorScript": "file:./index.js",
"editorStyle": "file:../editor-style.css",
"style": "file:../style.css",
"viewScript": "file:./view.js"
}
Loading