Skip to content

Commit

Permalink
Add API to configure icon colors (#7068)
Browse files Browse the repository at this point in the history
This PR allows the icon to configure 2 colors: background and foreground color.
If the foreground color is not passed it is automatically computed from the 
background color.
This colors right now are being used in the inserter.
  • Loading branch information
jorgefilipecosta authored Jun 4, 2018
1 parent b763387 commit aa5cc43
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 46 deletions.
5 changes: 4 additions & 1 deletion blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 26 additions & 21 deletions blocks/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

/**
Expand Down Expand Up @@ -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',
Expand Down
36 changes: 27 additions & 9 deletions blocks/api/test/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -167,7 +169,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
attributes: {
ok: {
type: 'boolean',
Expand All @@ -186,7 +190,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
} );
} );

Expand Down Expand Up @@ -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' );
Expand All @@ -234,7 +242,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
} );
expect( getBlockTypes() ).toEqual( [] );
} );
Expand Down Expand Up @@ -276,7 +286,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
} );
} );

Expand All @@ -289,7 +301,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
} );
} );
} );
Expand All @@ -309,15 +323,19 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
},
{
name: 'core/test-block-with-settings',
settingName: 'settingValue',
save: noop,
category: 'common',
title: 'block title',
icon: 'block-default',
icon: {
src: 'block-default',
},
},
] );
} );
Expand Down
46 changes: 45 additions & 1 deletion blocks/api/utils.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
/**
* 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
*/
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
Expand All @@ -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;
}
15 changes: 15 additions & 0 deletions docs/block-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion editor/components/autocompleters/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function createBlockCompleter( {
getOptionLabel( inserterItem ) {
const { icon, title } = inserterItem;
return [
<BlockIcon key="icon" icon={ icon } />,
<BlockIcon key="icon" icon={ icon && icon.src } />,
title,
];
},
Expand Down
4 changes: 3 additions & 1 deletion editor/components/autocompleters/test/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ describe( 'block', () => {
it( 'should render a block option label', () => {
const labelComponents = shallow( <div>
{ blockCompleter.getOptionLabel( {
icon: 'expected-icon',
icon: {
src: 'expected-icon',
},
title: 'expected-text',
} ) }
</div> ).children();
Expand Down
2 changes: 1 addition & 1 deletion editor/components/block-inspector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const BlockInspector = ( { selectedBlock, count } ) => {
return [
<div className="editor-block-inspector__card" key="card">
<div className="editor-block-inspector__card-icon">
<BlockIcon icon={ blockType.icon } />
<BlockIcon icon={ blockType.icon && blockType.icon.src } />
</div>
<div className="editor-block-inspector__card-content">
<div className="editor-block-inspector__card-title">{ blockType.title }</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
>
Expand Down
4 changes: 2 additions & 2 deletions editor/components/block-switcher/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function BlockSwitcher( { blocks, onTransform, isLocked } ) {
<Toolbar>
<IconButton
className="editor-block-switcher__toggle"
icon={ <BlockIcon icon={ blockType.icon } /> }
icon={ <BlockIcon icon={ blockType.icon && blockType.icon.src } /> }
onClick={ onToggle }
aria-haspopup="true"
aria-expanded={ isOpen }
Expand Down Expand Up @@ -81,7 +81,7 @@ export function BlockSwitcher( { blocks, onTransform, isLocked } ) {
className="editor-block-switcher__menu-item"
icon={ (
<span className="editor-block-switcher__block-icon">
<BlockIcon icon={ icon } />
<BlockIcon icon={ icon && icon.src } />
</span>
) }
role="menuitem"
Expand Down
2 changes: 1 addition & 1 deletion editor/components/inserter-with-shortcuts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function InserterWithShortcuts( { items, isLocked, onInsert } ) {
onClick={ () => onInsert( item ) }
label={ sprintf( __( 'Add %s' ), item.title ) }
icon={ (
<BlockIcon icon={ item.icon } />
<BlockIcon icon={ item.icon && item.icon.src } />
) }
/>
) ) }
Expand Down
10 changes: 8 additions & 2 deletions editor/components/inserter/child-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) {
{ ( rootBlockIcon || rootBlockTitle ) && (
<div className="editor-inserter__parent-block-header">
{ rootBlockIcon && (
<div className="editor-inserter__parent-block-icon">
<BlockIcon icon={ rootBlockIcon } />
<div
style={ {
backgroundColor: rootBlockIcon.background,
color: rootBlockIcon.foreground,
} }
className="editor-inserter__parent-block-icon"
>
<BlockIcon icon={ rootBlockIcon.src } />
</div>
) }
{ rootBlockTitle && <h2>{ rootBlockTitle }</h2> }
Expand Down
21 changes: 18 additions & 3 deletions editor/components/inserter/item-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class ItemList extends Component {
/* eslint-disable jsx-a11y/no-redundant-roles */
<ul role="list" className="editor-inserter__list">
{ 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 (
<li className="editor-inserter__list-item" key={ item.id }>
<button
Expand All @@ -46,9 +53,17 @@ class ItemList extends Component {
onBlur={ () => onHover( null ) }
aria-label={ item.title } // Fix for IE11 and JAWS 2018.
>
<span className="editor-inserter__item-icon">
<BlockIcon icon={ item.icon } />
{ item.hasChildBlocks && <span className="editor-inserter__item-icon-stack" /> }
<span
className="editor-inserter__item-icon"
style={ itemIconStyle }
>
<BlockIcon icon={ item.icon && item.icon.src } />
{ item.hasChildBlocks &&
<span
className="editor-inserter__item-icon-stack"
style={ itemIconStackStyle }
/>
}
</span>

<span className="editor-inserter__item-title">
Expand Down
Loading

0 comments on commit aa5cc43

Please sign in to comment.