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

Add 'Widget Group' block to widgets screens #34484

Merged
merged 18 commits into from
Sep 7, 2021
Merged
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
2 changes: 1 addition & 1 deletion docs/reference-guides/block-api/block-transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ A transformation of type `block` is an object that takes the following parameter
- **type** _(string)_: the value `block`.
- **blocks** _(array)_: a list of known block types. It also accepts the wildcard value (`"*"`), meaning that the transform is available to _all_ block types (eg: all blocks can transform into `core/group`).
- **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects.
- **isMatch** _(function, optional)_: a callback that receives the block attributes and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
- **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
- **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default.
- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.

Expand Down
2 changes: 2 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@ function gutenberg_reregister_core_block_types() {
__DIR__ . '/../build/widgets/blocks/' => array(
'block_folders' => array(
'legacy-widget',
'widget-group',
),
'block_names' => array(
'legacy-widget.php' => 'core/legacy-widget',
'widget-group.php' => 'core/widget-group',
),
),
);
Expand Down
4 changes: 4 additions & 0 deletions packages/blocks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Register a block even when an invalid value provided for the icon setting ([#34350](https://github.com/WordPress/gutenberg/pull/34350)).

### New API

- The `isMatch` callback on block transforms now receives the block object (or block objects if `isMulti` is `true`) as its second argument.

## 11.0.0 (2021-07-29)

### Breaking Change
Expand Down
3 changes: 2 additions & 1 deletion packages/blocks/src/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => {
const attributes = transform.isMultiBlock
? blocks.map( ( block ) => block.attributes )
: sourceBlock.attributes;
if ( ! transform.isMatch( attributes ) ) {
const block = transform.isMultiBlock ? blocks : sourceBlock;
if ( ! transform.isMatch( attributes, block ) ) {
return false;
}
}
Expand Down
14 changes: 7 additions & 7 deletions packages/blocks/src/api/test/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ describe( 'block factory', () => {
expect( availableBlocks ).toEqual( [] );
} );

it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object as its first argument', () => {
it( 'for a non multiblock transform, the isMatch function receives the source block’s attributes object and the block object as its arguments', () => {
const isMatch = jest.fn();

registerBlockType( 'core/updated-text-block', {
Expand Down Expand Up @@ -1010,10 +1010,10 @@ describe( 'block factory', () => {

getPossibleBlockTransformations( [ block ] );

expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' } );
expect( isMatch ).toHaveBeenCalledWith( { value: 'ribs' }, block );
} );

it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes as its first argument', () => {
it( 'for a multiblock transform, the isMatch function receives an array containing every source block’s attributes and an array of source blocks as its arguments', () => {
const isMatch = jest.fn();

registerBlockType( 'core/updated-text-block', {
Expand Down Expand Up @@ -1049,10 +1049,10 @@ describe( 'block factory', () => {

getPossibleBlockTransformations( [ meatBlock, cheeseBlock ] );

expect( isMatch ).toHaveBeenCalledWith( [
{ value: 'ribs' },
{ value: 'halloumi' },
] );
expect( isMatch ).toHaveBeenCalledWith(
[ { value: 'ribs' }, { value: 'halloumi' } ],
[ meatBlock, cheeseBlock ]
);
} );

describe( 'wildcard block transforms', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/customize-widgets/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import {
registerLegacyWidgetBlock,
registerLegacyWidgetVariations,
registerWidgetGroupBlock,
} from '@wordpress/widgets';
import { setFreeformContentHandlerName } from '@wordpress/blocks';
import { dispatch } from '@wordpress/data';
Expand Down Expand Up @@ -56,6 +57,7 @@ export function initialize( editorName, blockEditorSettings ) {
} );
}
registerLegacyWidgetVariations( blockEditorSettings );
registerWidgetGroupBlock();

// As we are unregistering `core/freeform` to avoid the Classic block, we must
// replace it with something as the default freeform content handler. Failure to
Expand Down
4 changes: 4 additions & 0 deletions packages/edit-widgets/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wor
import {
registerLegacyWidgetBlock,
registerLegacyWidgetVariations,
registerWidgetGroupBlock,
} from '@wordpress/widgets';
import { dispatch } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';
Expand All @@ -26,6 +27,7 @@ import { store as interfaceStore } from '@wordpress/interface';
import './store';
import './filters';
import * as widgetArea from './blocks/widget-area';

import Layout from './components/layout';
import {
ALLOW_REUSABLE_BLOCKS,
Expand Down Expand Up @@ -89,6 +91,8 @@ export function initialize( id, settings ) {
}
registerLegacyWidgetVariations( settings );
registerBlock( widgetArea );
registerWidgetGroupBlock();

settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) =>
fetchLinkSuggestions( search, searchOptions, settings );

Expand Down
18 changes: 18 additions & 0 deletions packages/widgets/src/blocks/widget-group/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"apiVersion": 2,
"name": "core/widget-group",
"category": "widgets",
"attributes": {
"title": {
"type": "string"
}
},
"supports": {
"html": false,
"inserter": true,
"customClassName": true,
"reusable": false
},
"editorStyle": "wp-block-widget-group-editor",
"style": "wp-block-widget-group"
}
84 changes: 84 additions & 0 deletions packages/widgets/src/blocks/widget-group/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* WordPress dependencies
*/
import {
useBlockProps,
BlockIcon,
ButtonBlockAppender,
InnerBlocks,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { Placeholder, TextControl } from '@wordpress/components';
import { group as groupIcon } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { getBlockType } from '@wordpress/blocks';

export default function Edit( props ) {
const { clientId, isSelected } = props;
const { innerBlocks } = useSelect( ( select ) =>
select( blockEditorStore ).getBlock( clientId )
);

let content;
if ( innerBlocks.length === 0 ) {
content = <PlaceholderContent { ...props } />;
} else if ( isSelected ) {
content = <EditFormContent { ...props } innerBlocks={ innerBlocks } />;
} else {
content = <PreviewContent { ...props } innerBlocks={ innerBlocks } />;
}

return (
<div { ...useBlockProps( { className: 'widget' } ) }>{ content }</div>
);
}

function PlaceholderContent( { clientId } ) {
return (
<>
<Placeholder
className="wp-block-widget-group__placeholder"
icon={ <BlockIcon icon={ groupIcon } /> }
label={ __( 'Widget Group' ) }
>
<ButtonBlockAppender rootClientId={ clientId } />
</Placeholder>
<InnerBlocks renderAppender={ false } />
</>
);
}

function EditFormContent( { attributes, setAttributes, innerBlocks } ) {
return (
<div className="wp-block-widget-group__edit-form">
<h2 className="wp-block-widget-group__edit-form-title">
{ __( 'Widget Group' ) }
</h2>
<TextControl
label={ __( 'Title' ) }
placeholder={ getDefaultTitle( innerBlocks ) }
value={ attributes.title ?? '' }
onChange={ ( title ) => setAttributes( { title } ) }
/>
</div>
);
}

function PreviewContent( { attributes, innerBlocks } ) {
return (
<>
<h2 className="widget-title">
{ attributes.title || getDefaultTitle( innerBlocks ) }
</h2>
<InnerBlocks />
</>
);
}

function getDefaultTitle( innerBlocks ) {
if ( innerBlocks.length === 0 ) {
return null;
}
return getBlockType( innerBlocks[ 0 ].name ).title;
}
46 changes: 46 additions & 0 deletions packages/widgets/src/blocks/widget-group/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.wp-block-widget-group {
&.has-child-selected::after {
border-radius: $radius-block-ui;
border: 1px solid var(--wp-admin-theme-color);
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
}

.widget-title {
font-family: $default-font;
font-size: 18px;
font-weight: 600;
}
}

.wp-block-widget-group__placeholder {
.block-editor-inserter {
width: 100%;
}
}
noisysocks marked this conversation as resolved.
Show resolved Hide resolved

// Force the appender to always have "light mode" styling as it appears in a
// light colored placeholder.
.is-dark-theme .wp-block-widget-group__placeholder .block-editor-button-block-appender {
box-shadow: inset 0 0 0 $border-width $gray-900;
color: $gray-900;
}

.wp-block-widget-group__edit-form {
background: $white;
border-radius: $radius-block-ui;
border: 1px solid $gray-900;
padding: $grid-unit-15 - 1px; // Subtract the border width.

.wp-block-widget-group__edit-form-title {
color: $black;
font-family: $default-font;
font-size: 14px;
font-weight: 600;
margin: 0 0 $grid-unit-15 0;
}
}
76 changes: 76 additions & 0 deletions packages/widgets/src/blocks/widget-group/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';
import { group as icon } from '@wordpress/icons';

/**
* Internal dependencies
*/
import metadata from './block.json';
import edit from './edit';
import save from './save';
const { name } = metadata;
export { metadata, name };

export const settings = {
title: __( 'Widget Group' ),
description: __(
'Create a classic widget layout with a title that’s styled by your theme for your widget areas.'
),
icon,
__experimentalLabel: ( { name: label } ) => label,
edit,
save,
transforms: {
from: [
{
type: 'block',
isMultiBlock: true,
blocks: [ '*' ],
isMatch( attributes, blocks ) {
// Avoid transforming existing `widget-group` blocks.
return ! blocks.some(
( block ) => block.name === 'core/widget-group'
);
},
__experimentalConvert( blocks ) {
// Put the selected blocks inside the new Widget Group's innerBlocks.
let innerBlocks = [
...blocks.map( ( block ) => {
return createBlock(
block.name,
block.attributes,
block.innerBlocks
);
} ),
];

// If the first block is a heading then assume this is intended
// to be the Widget's "title".
const firstHeadingBlock =
innerBlocks[ 0 ].name === 'core/heading'
? innerBlocks[ 0 ]
: null;

// Remove the first heading block as we're copying
// it's content into the Widget Group's title attribute.
innerBlocks = innerBlocks.filter(
( block ) => block !== firstHeadingBlock
);

return createBlock(
'core/widget-group',
{
...( firstHeadingBlock && {
title: firstHeadingBlock.attributes.content,
} ),
},
innerBlocks
);
},
},
],
},
};
Loading