Skip to content

Commit

Permalink
Block variations transformations (#26687)
Browse files Browse the repository at this point in the history
* __experimentalBlockVariationTransforms first iteration

* some small fixes

* test with Navigation variations

* Update docs/designers-developers/developers/block-api/block-registration.md

Co-authored-by: Greg Ziółkowski <grzegorz@gziolo.pl>

* move navigation variations to its own file

* doc scope options

* change getBlockVariations + add tests

* change doc for scope

* change selector implementation

* add readme

* minor changes

* remove test

* simplify API by passing only client id

* change design to DropdownMenu

* remove obsolete spread

* rename prop

* polish styles

* add variation matcher for attributes only

* iconPosition doc + change

* make matcher to return only name

* getMatchingVariationName change to array.filter + unit tests

Co-authored-by: Greg Ziółkowski <grzegorz@gziolo.pl>
  • Loading branch information
ntsekouras and gziolo authored Nov 13, 2020
1 parent 80e30fd commit 0b9b34f
Show file tree
Hide file tree
Showing 18 changed files with 401 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ An object describing a variation defined for the block type can contain the foll
- `attributes` (optional, type `Object`) – Values that override block attributes.
- `innerBlocks` (optional, type `Array[]`) – Initial configuration of nested blocks.
- `example` (optional, type `Object`) – Example provides structured data for the block preview. You can set to `undefined` to disable the preview shown for the block type.
- `scope` (optional, type `string[]`) - the list of scopes where the variation is applicable. When not provided, it assumes all available scopes. Available options: `block`, `inserter`.
- `scope` (optional, type `WPBlockVariationScope[]`) - the list of scopes where the variation is applicable. When not provided, it defaults to `block` and `inserter`. Available options:
- `inserter` - Block Variation is shown on the inserter.
- `block` - Used by blocks to filter specific block variations. Mostly used in Placeholder patterns like `Columns` block.
- `transform` - Block Variation will be shown in the component for Block Variations transformations.
- `keywords` (optional, type `string[]`) - An array of terms (which can be translated) that help users discover the variation while searching.

It's also possible to override the default block style variation using the `className` attribute when defining block variations.
Expand Down
10 changes: 4 additions & 6 deletions packages/block-editor/src/components/block-card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
*/
import BlockIcon from '../block-icon';

function BlockCard( { blockType } ) {
function BlockCard( { blockType: { icon, title, description } } ) {
return (
<div className="block-editor-block-card">
<BlockIcon icon={ blockType.icon } showColors />
<BlockIcon icon={ icon } showColors />
<div className="block-editor-block-card__content">
<h2 className="block-editor-block-card__title">
{ blockType.title }
</h2>
<h2 className="block-editor-block-card__title">{ title }</h2>
<span className="block-editor-block-card__description">
{ blockType.description }
{ description }
</span>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/block-inspector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import InspectorAdvancedControls from '../inspector-advanced-controls';
import BlockStyles from '../block-styles';
import MultiSelectionInspector from '../multi-selection-inspector';
import DefaultStylePicker from '../default-style-picker';
import BlockVariationTransforms from '../block-variation-transforms';
const BlockInspector = ( {
blockType,
count,
Expand Down Expand Up @@ -66,6 +67,7 @@ const BlockInspector = ( {
return (
<div className="block-editor-block-inspector">
<BlockCard blockType={ blockType } />
<BlockVariationTransforms blockClientId={ selectedBlockClientId } />
{ hasBlockStyles && (
<div>
<PanelBody title={ __( 'Styles' ) }>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Block Variation Transforms

This component allows to display the selected block's variations which have the `transform` option set in `scope` property and to choose one of them.

By selecting such a variation an update to the selected block's attributes happen, based on the variation's attributes.

## Table of contents

1. [Development guidelines](#development-guidelines)
2. [Related components](#related-components)

## Development guidelines

### Usage

Renders the block's variations which have the `transform` option set in `scope` property.

```jsx
import { useSelect } from '@wordpress/data';
import {
__experimentalBlockVariationTransforms as BlockVariationTransforms,
} from '@wordpress/block-editor';

const MyBlockVariationTransforms = () => {
const { selectedBlockClientId } = useSelect(
( select ) => {
const { getSelectedBlockClientId } = select(
'core/block-editor'
);
return {
selectedBlockClientId: getSelectedBlockClientId(),
};
}
);

return (
<BlockVariationTransforms
blockClientId={ selectedBlockClientId }
/>
);
};
```

### Props

#### blockClientId

The block's client id.

- Type: `string`

## Related components

Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [BlockEditorProvider](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/provider/README.md) in the components tree.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* External dependencies
*/
import { isMatch } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
DropdownMenu,
MenuGroup,
MenuItemsChoice,
} from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { useState, useEffect } from '@wordpress/element';
import { chevronDown } from '@wordpress/icons';

export const getMatchingVariationName = ( blockAttributes, variations ) => {
if ( ! variations || ! blockAttributes ) return;
const matches = variations.filter( ( { attributes } ) => {
if ( ! attributes || ! Object.keys( attributes ).length ) return false;
return isMatch( blockAttributes, attributes );
} );
if ( matches.length !== 1 ) return;
return matches[ 0 ].name;
};

function __experimentalBlockVariationTransforms( { blockClientId } ) {
const [ selectedValue, setSelectedValue ] = useState();
const { updateBlockAttributes } = useDispatch( 'core/block-editor' );
const { variations, blockAttributes } = useSelect(
( select ) => {
const { getBlockVariations } = select( 'core/blocks' );
const { getBlockName, getBlockAttributes } = select(
'core/block-editor'
);
const blockName = blockClientId && getBlockName( blockClientId );
return {
variations:
blockName && getBlockVariations( blockName, 'transform' ),
blockAttributes: getBlockAttributes( blockClientId ),
};
},
[ blockClientId ]
);
useEffect( () => {
setSelectedValue(
getMatchingVariationName( blockAttributes, variations )
);
}, [ blockAttributes, variations ] );
if ( ! variations?.length ) return null;

const selectOptions = variations.map(
( { name, title, description } ) => ( {
value: name,
label: title,
info: description,
} )
);
const onSelectVariation = ( variationName ) => {
updateBlockAttributes( blockClientId, {
...variations.find( ( { name } ) => name === variationName )
.attributes,
} );
};
const baseClass = 'block-editor-block-variation-transforms';
return (
<DropdownMenu
className={ baseClass }
label={ __( 'Transform to variation' ) }
text={ __( 'Transform to variation' ) }
popoverProps={ {
position: 'bottom center',
className: `${ baseClass }__popover`,
} }
icon={ chevronDown }
toggleProps={ { iconPosition: 'right' } }
>
{ () => (
<div className={ `${ baseClass }__container` }>
<MenuGroup>
<MenuItemsChoice
choices={ selectOptions }
value={ selectedValue }
onSelect={ onSelectVariation }
/>
</MenuGroup>
</div>
) }
</DropdownMenu>
);
}

export default __experimentalBlockVariationTransforms;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.block-editor-block-variation-transforms {
padding: 0 $grid-unit-20 $grid-unit-20 56px;
width: 100%;

.components-dropdown-menu__toggle {
border: 1px solid $gray-700;
border-radius: $radius-block-ui;
min-height: 30px;
width: 100%;
position: relative;
text-align: left;
justify-content: left;
padding: 6px 12px;

// For all button sizes allow sufficient space for the
// dropdown "arrow" icon to display.
&.components-dropdown-menu__toggle {
padding-right: $icon-size;
}

&:focus:not(:disabled) {
border-color: var(--wp-admin-theme-color);
box-shadow: 0 0 0 ($border-width-focus - $border-width) var(--wp-admin-theme-color);
}

svg {
height: 100%;
padding: 0;
position: absolute;
right: 0;
top: 0;
}
}
}

.block-editor-block-variation-transforms__popover .components-popover__content {
min-width: 230px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Internal dependencies
*/
import { getMatchingVariationName } from '../index';

describe( 'BlockVariationTransforms', () => {
describe( 'getMatchingVariationName', () => {
describe( 'should not find a match', () => {
it( 'when no variations or attributes passed', () => {
expect(
getMatchingVariationName( null, { content: 'hi' } )
).toBeUndefined();
expect( getMatchingVariationName( {} ) ).toBeUndefined();
} );
it( 'when no variation matched', () => {
const variations = [
{ name: 'one', attributes: { level: 1 } },
{ name: 'two', attributes: { level: 2 } },
];
expect(
getMatchingVariationName( { level: 4 }, variations )
).toBeUndefined();
} );
it( 'when more than one match found', () => {
const variations = [
{ name: 'one', attributes: { level: 1 } },
{ name: 'two', attributes: { level: 1, content: 'hi' } },
];
expect(
getMatchingVariationName(
{ level: 1, content: 'hi', other: 'prop' },
variations
)
).toBeUndefined();
} );
it( 'when variation is a superset of attributes', () => {
const variations = [
{ name: 'one', attributes: { level: 1, content: 'hi' } },
];
expect(
getMatchingVariationName(
{ level: 1, other: 'prop' },
variations
)
).toBeUndefined();
} );
} );
describe( 'should find a match', () => {
it( 'when variation has one attribute', () => {
const variations = [
{ name: 'one', attributes: { level: 1 } },
{ name: 'two', attributes: { level: 2 } },
];
expect(
getMatchingVariationName(
{ level: 2, content: 'hi', other: 'prop' },
variations
)
).toEqual( 'two' );
} );
it( 'when variation has many attributes', () => {
const variations = [
{ name: 'one', attributes: { level: 1, content: 'hi' } },
{ name: 'two', attributes: { level: 2 } },
];
expect(
getMatchingVariationName(
{ level: 1, content: 'hi', other: 'prop' },
variations
)
).toEqual( 'one' );
} );
} );
} );
} );
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { BlockNavigationBlockFill as __experimentalBlockNavigationBlockFill } fr
export { default as __experimentalBlockNavigationEditor } from './block-navigation/editor';
export { default as __experimentalBlockNavigationTree } from './block-navigation/tree';
export { default as __experimentalBlockVariationPicker } from './block-variation-picker';
export { default as __experimentalBlockVariationTransforms } from './block-variation-transforms';
export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar';
export { default as ButtonBlockerAppender } from './button-block-appender';
export { default as ColorPalette } from './color-palette';
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
@import "./components/block-switcher/style.scss";
@import "./components/block-types-list/style.scss";
@import "./components/block-variation-picker/style.scss";
@import "./components/block-variation-transforms/style.scss";
@import "./components/button-block-appender/style.scss";
@import "./components/colors-gradients/style.scss";
@import "./components/contrast-checker/style.scss";
Expand Down
25 changes: 2 additions & 23 deletions packages/block-library/src/navigation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,20 @@ import metadata from './block.json';
import edit from './edit';
import save from './save';
import deprecated from './deprecated';
import variations from './variations';

const { name } = metadata;

export { metadata, name };

export const settings = {
title: __( 'Navigation' ),

icon,

description: __(
'A collection of blocks that allow visitors to get around your site.'
),

keywords: [ __( 'menu' ), __( 'navigation' ), __( 'links' ) ],

variations: [
{
name: 'horizontal',
isDefault: true,
title: __( 'Navigation (horizontal)' ),
description: __( 'Links shown in a row.' ),
attributes: { orientation: 'horizontal' },
},
{
name: 'vertical',
title: __( 'Navigation (vertical)' ),
description: __( 'Links shown in a column.' ),
attributes: { orientation: 'vertical' },
},
],

variations,
example: {
innerBlocks: [
{
Expand Down Expand Up @@ -71,10 +53,7 @@ export const settings = {
},
],
},

edit,

save,

deprecated,
};
Loading

0 comments on commit 0b9b34f

Please sign in to comment.