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 {