diff --git a/blocks/api/index.js b/blocks/api/index.js index bb7f20c0982d8b..e9e883a33b6a12 100644 --- a/blocks/api/index.js +++ b/blocks/api/index.js @@ -34,6 +34,8 @@ export { getBlockSupport, hasBlockSupport, isSharedBlock, + getChildBlockNames, + hasChildBlocks, } from './registration'; export { isUnmodifiedDefaultBlock, diff --git a/blocks/api/registration.js b/blocks/api/registration.js index b8aecb72dd9a9b..67d21385a74dff 100644 --- a/blocks/api/registration.js +++ b/blocks/api/registration.js @@ -288,3 +288,25 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { export function isSharedBlock( blockOrType ) { return blockOrType.name === 'core/block'; } + +/** + * Returns an array with the child blocks of a given block. + * + * @param {string} blockName Block type name. + * + * @return {Array} Array of child block names. + */ +export const getChildBlockNames = ( blockName ) => { + return select( 'core/blocks' ).getChildBlockNames( blockName ); +}; + +/** + * Returns a boolean indicating if a block has child blocks or not. + * + * @param {string} blockName Block type name. + * + * @return {boolean} True if a block contains child blocks and false otherwise. + */ +export const hasChildBlocks = ( blockName ) => { + return select( 'core/blocks' ).hasChildBlocks( blockName ); +}; diff --git a/blocks/store/selectors.js b/blocks/store/selectors.js index a777f83d777dfd..bf6541ae3a1950 100644 --- a/blocks/store/selectors.js +++ b/blocks/store/selectors.js @@ -2,6 +2,7 @@ * External dependencies */ import createSelector from 'rememo'; +import { filter, includes, map } from 'lodash'; /** * Returns all the available block types. @@ -61,3 +62,37 @@ export function getDefaultBlockName( state ) { export function getFallbackBlockName( state ) { return state.fallbackBlockName; } + +/** + * Returns an array with the child blocks of a given block. + * + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * + * @return {Array} Array of child block names. + */ +export const getChildBlockNames = createSelector( + ( state, blockName ) => { + return map( + filter( state.blockTypes, ( blockType ) => { + return includes( blockType.parent, blockName ); + } ), + ( { name } ) => name + ); + }, + ( state ) => [ + state.blockTypes, + ] +); + +/** + * Returns a boolean indicating if a block has child blocks or not. + * + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * + * @return {boolean} True if a block contains child blocks and false otherwise. + */ +export const hasChildBlocks = ( state, blockName ) => { + return getChildBlockNames( state, blockName ).length > 0; +}; diff --git a/blocks/store/test/selectors.js b/blocks/store/test/selectors.js new file mode 100644 index 00000000000000..ea24fcd7d855b3 --- /dev/null +++ b/blocks/store/test/selectors.js @@ -0,0 +1,137 @@ +/** + * Internal dependencies + */ +import { getChildBlockNames } from '../selectors'; + +describe( 'selectors', () => { + describe( 'getChildBlockNames', () => { + it( 'should return an empty array if state is empty', () => { + const state = {}; + + expect( getChildBlockNames( state, 'parent1' ) ).toHaveLength( 0 ); + } ); + + it( 'should return an empty array if no children exist', () => { + const state = { + blockTypes: [ + { + name: 'child1', + parent: [ 'parent1' ], + }, + { + name: 'child2', + parent: [ 'parent2' ], + }, + { + name: 'parent3', + }, + ], + }; + + expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); + } ); + + it( 'should return an empty array if the parent block is not found', () => { + const state = { + blockTypes: [ + { + name: 'child1', + parent: [ 'parent1' ], + }, + { + name: 'parent1', + }, + ], + }; + + expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); + } ); + + it( 'should return an array with the child block names', () => { + const state = { + blockTypes: [ + { + name: 'child1', + parent: [ 'parent1' ], + }, + { + name: 'child2', + parent: [ 'parent2' ], + }, + { + name: 'child3', + parent: [ 'parent1' ], + }, + { + name: 'child4', + }, + { + name: 'parent1', + }, + { + name: 'parent2', + }, + ], + }; + + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1', 'child3' ] ); + } ); + + it( 'should return an array with the child block names even if only one child exists', () => { + const state = { + blockTypes: [ + { + name: 'child1', + parent: [ 'parent1' ], + }, + { + name: 'child2', + parent: [ 'parent2' ], + }, + { + name: 'child4', + }, + { + name: 'parent1', + }, + { + name: 'parent2', + }, + ], + }; + + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1' ] ); + } ); + + it( 'should return an array with the child block names even if children have multiple parents', () => { + const state = { + blockTypes: [ + { + name: 'child1', + parent: [ 'parent1' ], + }, + { + name: 'child2', + parent: [ 'parent1', 'parent2' ], + }, + { + name: 'child3', + parent: [ 'parent1' ], + }, + { + name: 'child4', + }, + { + name: 'parent1', + }, + { + name: 'parent2', + }, + ], + }; + + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1', 'child2', 'child3' ] ); + expect( getChildBlockNames( state, 'parent2' ) ).toEqual( [ 'child2' ] ); + } ); + } ); +} ); diff --git a/components/notice/list.js b/components/notice/list.js index 369c8819fe72ca..a9e72c546deb6e 100644 --- a/components/notice/list.js +++ b/components/notice/list.js @@ -15,7 +15,7 @@ import Notice from './'; * @param {Array} $0.notices Array of notices to render. * @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. * @param {Object} $0.className Name of the class used by the component. -* @param {Object} $0.children Array of childs to be rendered inside the notice list. +* @param {Object} $0.children Array of children to be rendered inside the notice list. * @return {Object} The rendered notices list. */ function NoticeList( { notices, onRemove = noop, className = 'components-notice-list', children } ) { diff --git a/edit-post/assets/stylesheets/_variables.scss b/edit-post/assets/stylesheets/_variables.scss index 51f5ad40d269a8..1a75998125fa06 100644 --- a/edit-post/assets/stylesheets/_variables.scss +++ b/edit-post/assets/stylesheets/_variables.scss @@ -26,7 +26,7 @@ $admin-sidebar-width-big: 190px; $admin-sidebar-width-collapsed: 36px; // Visuals -$shadow-popover: 0 3px 20px rgba( $dark-gray-900, .1 ), 0 1px 3px rgba( $dark-gray-900, .1 ); +$shadow-popover: 0 3px 30px rgba( $dark-gray-900, .1 ); $shadow-toolbar: 0 2px 10px rgba( $dark-gray-900, .1 ), 0 0 2px rgba( $dark-gray-900, .1 ); $shadow-below-only: 0 5px 10px rgba( $dark-gray-900, .1 ), 0 2px 2px rgba( $dark-gray-900, .1 ); diff --git a/editor/components/inserter/child-blocks.js b/editor/components/inserter/child-blocks.js new file mode 100644 index 00000000000000..3d5bbe261316f3 --- /dev/null +++ b/editor/components/inserter/child-blocks.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; +import { ifCondition } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import './style.scss'; +import ItemList from './item-list'; +import BlockIcon from '../block-icon'; + +function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) { + return ( +
+ { ( rootBlockIcon || rootBlockTitle ) && ( +
+ { rootBlockIcon && ( +
+ +
+ ) } + { rootBlockTitle &&

{ rootBlockTitle }

} +
+ ) } + +
+ ); +} + +export default compose( + ifCondition( ( { items } ) => items && items.length > 0 ), + withSelect( ( select, { rootUID } ) => { + const { + getBlockType, + } = select( 'core/blocks' ); + const { + getBlockName, + } = select( 'core/editor' ); + const rootBlockName = getBlockName( rootUID ); + const rootBlockType = getBlockType( rootBlockName ); + return { + rootBlockTitle: rootBlockType && rootBlockType.title, + rootBlockIcon: rootBlockType && rootBlockType.icon, + }; + } ), +)( ChildBlocks ); diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 567140f969d98f..9eeca66c55fd36 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -41,6 +41,7 @@ class Inserter extends Component { title, children, onInsertBlock, + rootUID, } = this.props; if ( items.length === 0 ) { @@ -74,7 +75,7 @@ class Inserter extends Component { onClose(); }; - return ; + return ; } } /> ); @@ -96,6 +97,7 @@ export default compose( [ insertionPoint, selectedBlock: getSelectedBlock(), items: getInserterItems( rootUID ), + rootUID, }; } ), withDispatch( ( dispatch, ownProps ) => ( { diff --git a/editor/components/inserter/item-list.js b/editor/components/inserter/item-list.js index 7f73bf47e7d7a4..cc6fb8b63bd42a 100644 --- a/editor/components/inserter/item-list.js +++ b/editor/components/inserter/item-list.js @@ -74,7 +74,15 @@ class ItemList extends Component {