diff --git a/blocks/README.md b/blocks/README.md index 77a7624ed0a067..5762963145c15b 100644 --- a/blocks/README.md +++ b/blocks/README.md @@ -232,10 +232,13 @@ editor interface where blocks are implemented. - `title: string` - A human-readable [localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files) label for the block. Shown in the block inserter. -- `icon: string | WPElement | Function` - Slug of the +- `icon: string | WPElement | Function | Object` - Slug of the [Dashicon](https://developer.wordpress.org/resource/dashicons/#awards) to be shown in the control's button, or an element (or function returning an element) if you choose to render your own SVG. + An object can also be passed, in this case, icon, as specified above, should be included in the src property. + Besides src the object can contain background and foreground colors, this colors will appear with the icon + when they are applicable e.g.: in the inserter. - `attributes: Object | Function` - An object of attribute schemas, where the keys of the object define the shape of attributes, and each value an object schema describing the `type`, `default` (optional), and diff --git a/blocks/api/registration.js b/blocks/api/registration.js index 67d21385a74dff..66b172a7a6c23b 100644 --- a/blocks/api/registration.js +++ b/blocks/api/registration.js @@ -12,29 +12,34 @@ import { applyFilters } from '@wordpress/hooks'; import { select, dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; +/** + * Internal dependencies + */ +import { normalizeIconObject } from './utils'; + /** * Defined behavior of a block type. * * @typedef {WPBlockType} * - * @property {string} name Block's namespaced name. - * @property {string} title Human-readable label for a block. - * Shown in the block inserter. - * @property {string} category Category classification of block, - * impacting where block is shown in - * inserter results. - * @property {(string|WPElement)} icon Slug of the Dashicon to be shown - * as the icon for the block in the - * inserter, or element. - * @property {?string[]} keywords Additional keywords to produce - * block as inserter search result. - * @property {?Object} attributes Block attributes. - * @property {Function} save Serialize behavior of a block, - * returning an element describing - * structure of the block's post - * content markup. - * @property {WPComponent} edit Component rendering element to be - * interacted with in an editor. + * @property {string} name Block's namespaced name. + * @property {string} title Human-readable label for a block. + * Shown in the block inserter. + * @property {string} category Category classification of block, + * impacting where block is shown in + * inserter results. + * @property {(Object|string|WPElement)} icon Slug of the Dashicon to be shown + * as the icon for the block in the + * inserter, or element or an object describing the icon. + * @property {?string[]} keywords Additional keywords to produce + * block as inserter search result. + * @property {?Object} attributes Block attributes. + * @property {Function} save Serialize behavior of a block, + * returning an element describing + * structure of the block's post + * content markup. + * @property {WPComponent} edit Component rendering element to be + * interacted with in an editor. */ /** @@ -134,9 +139,9 @@ export function registerBlockType( name, settings ) { ); return; } - if ( ! settings.icon ) { - settings.icon = 'block-default'; - } + + settings.icon = normalizeIconObject( settings.icon ); + if ( 'isPrivate' in settings ) { deprecated( 'isPrivate', { version: '3.1', diff --git a/blocks/api/test/registration.js b/blocks/api/test/registration.js index 29f437579a943d..5cc8421b616664 100644 --- a/blocks/api/test/registration.js +++ b/blocks/api/test/registration.js @@ -84,7 +84,9 @@ describe( 'blocks', () => { expect( console ).not.toHaveErrored(); expect( block ).toEqual( { name: 'my-plugin/fancy-block-4', - icon: 'block-default', + icon: { + src: 'block-default', + }, save: noop, category: 'common', title: 'block title', @@ -167,7 +169,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, attributes: { ok: { type: 'boolean', @@ -186,7 +190,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, } ); } ); @@ -224,7 +230,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, }, ] ); const oldBlock = unregisterBlockType( 'core/test-block' ); @@ -234,7 +242,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, } ); expect( getBlockTypes() ).toEqual( [] ); } ); @@ -276,7 +286,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, } ); } ); @@ -289,7 +301,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, } ); } ); } ); @@ -309,7 +323,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, }, { name: 'core/test-block-with-settings', @@ -317,7 +333,9 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: 'block-default', + icon: { + src: 'block-default', + }, }, ] ); } ); diff --git a/blocks/api/utils.js b/blocks/api/utils.js index 185f4961808e11..19c587b9a0acfc 100644 --- a/blocks/api/utils.js +++ b/blocks/api/utils.js @@ -1,12 +1,14 @@ /** * External dependencies */ -import { every, keys, isEqual } from 'lodash'; +import { every, keys, isEqual, isFunction, isString } from 'lodash'; +import { default as tinycolor, mostReadable } from 'tinycolor2'; /** * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; +import { Component } from '@wordpress/element'; /** * Internal dependencies @@ -14,6 +16,14 @@ import { applyFilters } from '@wordpress/hooks'; import { getDefaultBlockName } from './registration'; import { createBlock } from './factory'; +/** + * Array of icon colors containing a color to be used if the icon color + * was not explicitly set but the icon background color was. + * + * @type {Object} + */ +const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; + /** * Determines whether the block is a default block * and its attributes are equal to the default attributes @@ -40,3 +50,37 @@ export function isUnmodifiedDefaultBlock( block ) { isEqual( newDefaultBlock.attributes[ key ], block.attributes[ key ] ) ); } + +/** + * Function that receives an icon as set by the blocks during the registration + * and returns a new icon object that is normalized so we can rely on just on possible icon structure + * in the codebase. + * + * @param {(Object|string|WPElement)} icon Slug of the Dashicon to be shown + * as the icon for the block in the + * inserter, or element or an object describing the icon. + * + * @return {Object} Object describing the icon. + */ +export function normalizeIconObject( icon ) { + if ( ! icon ) { + return { src: 'block-default' }; + } + if ( isString( icon ) || isFunction( icon ) || icon instanceof Component ) { + return { src: icon }; + } + + if ( icon.background ) { + const tinyBgColor = tinycolor( icon.background ); + if ( ! icon.foreground ) { + const foreground = mostReadable( + tinyBgColor, + ICON_COLORS, + { includeFallbackColors: true, level: 'AA', size: 'large' } + ).toHexString(); + icon.foreground = foreground; + } + icon.shadowColor = tinyBgColor.setAlpha( 0.3 ).toRgbString(); + } + return icon; +} diff --git a/docs/block-api.md b/docs/block-api.md index 263266a2c9fcf2..3a688a09bd6d98 100644 --- a/docs/block-api.md +++ b/docs/block-api.md @@ -70,6 +70,21 @@ An icon property should be specified to make it easier to identify a block. Thes icon: 'book-alt', ``` +An object can also be passed as icon, in this case, icon, as specified above, should be included in the src property. +Besides src the object can contain background and foreground colors, this colors will appear with the icon +when they are applicable e.g.: in the inserter. + +```js + +icon: { + // Specifying a background color to appear with the icon e.g.: in the inserter. + background: '#7e70af', + // Specifying a dashicon for the block + src: 'book-alt', +} , +``` + + #### Keywords (optional) Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). It is only allowed to add as much as three terms per block. diff --git a/editor/components/autocompleters/block.js b/editor/components/autocompleters/block.js index 08f2e015628f16..824e416445809a 100644 --- a/editor/components/autocompleters/block.js +++ b/editor/components/autocompleters/block.js @@ -42,7 +42,7 @@ export function createBlockCompleter( { getOptionLabel( inserterItem ) { const { icon, title } = inserterItem; return [ - , + , title, ]; }, diff --git a/editor/components/autocompleters/test/block.js b/editor/components/autocompleters/test/block.js index 2a39110101d3e7..f8d9f91a4364e5 100644 --- a/editor/components/autocompleters/test/block.js +++ b/editor/components/autocompleters/test/block.js @@ -54,7 +54,9 @@ describe( 'block', () => { it( 'should render a block option label', () => { const labelComponents = shallow(
{ blockCompleter.getOptionLabel( { - icon: 'expected-icon', + icon: { + src: 'expected-icon', + }, title: 'expected-text', } ) }
).children(); diff --git a/editor/components/block-inspector/index.js b/editor/components/block-inspector/index.js index bf8786d9d7e227..ce2f7f42903ff5 100644 --- a/editor/components/block-inspector/index.js +++ b/editor/components/block-inspector/index.js @@ -34,7 +34,7 @@ const BlockInspector = ( { selectedBlock, count } ) => { return [
- +
{ blockType.title }
diff --git a/editor/components/block-settings-menu/block-transformations.js b/editor/components/block-settings-menu/block-transformations.js index f4b32027ec05cd..8f936e2503752a 100644 --- a/editor/components/block-settings-menu/block-transformations.js +++ b/editor/components/block-settings-menu/block-transformations.js @@ -39,7 +39,7 @@ function BlockTransformations( { blocks, small = false, onTransform, onClick = n onTransform( blocks, name ); onClick( event ); } } - icon={ icon } + icon={ icon.src } label={ small ? title : undefined } role={ itemsRole } > diff --git a/editor/components/block-switcher/index.js b/editor/components/block-switcher/index.js index 7bdbda4aeb4d72..23441f8b3da71b 100644 --- a/editor/components/block-switcher/index.js +++ b/editor/components/block-switcher/index.js @@ -47,7 +47,7 @@ export function BlockSwitcher( { blocks, onTransform, isLocked } ) { } + icon={ } onClick={ onToggle } aria-haspopup="true" aria-expanded={ isOpen } @@ -81,7 +81,7 @@ export function BlockSwitcher( { blocks, onTransform, isLocked } ) { className="editor-block-switcher__menu-item" icon={ ( - + ) } role="menuitem" diff --git a/editor/components/inserter-with-shortcuts/index.js b/editor/components/inserter-with-shortcuts/index.js index d61d8eadb4aa65..c9fa5db554e925 100644 --- a/editor/components/inserter-with-shortcuts/index.js +++ b/editor/components/inserter-with-shortcuts/index.js @@ -36,7 +36,7 @@ function InserterWithShortcuts( { items, isLocked, onInsert } ) { onClick={ () => onInsert( item ) } label={ sprintf( __( 'Add %s' ), item.title ) } icon={ ( - + ) } /> ) ) } diff --git a/editor/components/inserter/child-blocks.js b/editor/components/inserter/child-blocks.js index 3d5bbe261316f3..c2a2da56e0db0c 100644 --- a/editor/components/inserter/child-blocks.js +++ b/editor/components/inserter/child-blocks.js @@ -18,8 +18,14 @@ function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) { { ( rootBlockIcon || rootBlockTitle ) && (
{ rootBlockIcon && ( -
- +
+
) } { rootBlockTitle &&

{ rootBlockTitle }

} diff --git a/editor/components/inserter/item-list.js b/editor/components/inserter/item-list.js index 3dcf4ea8bb3a03..76ae8535e5b50a 100644 --- a/editor/components/inserter/item-list.js +++ b/editor/components/inserter/item-list.js @@ -26,6 +26,13 @@ class ItemList extends Component { /* eslint-disable jsx-a11y/no-redundant-roles */
    { items.map( ( item ) => { + const itemIconStyle = item.icon ? { + backgroundColor: item.icon.background, + color: item.icon.foreground, + } : {}; + const itemIconStackStyle = item.icon && item.icon.shadowColor ? { + backgroundColor: item.icon.shadowColor, + } : {}; return (