diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index d96fbbd9331f2a..ff189e70ba88aa 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -140,6 +140,10 @@ Undocumented declaration.
Undocumented declaration.
+# **ButtonBlockerAppender**
+
+Undocumented declaration.
+
# **ColorPalette**
Undocumented declaration.
diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js
index 5af9c48a66f044..215fe23bae5257 100644
--- a/packages/block-editor/src/components/block-list-appender/index.js
+++ b/packages/block-editor/src/components/block-list-appender/index.js
@@ -8,53 +8,56 @@ import { last } from 'lodash';
*/
import { withSelect } from '@wordpress/data';
import { getDefaultBlockName } from '@wordpress/blocks';
-import { __ } from '@wordpress/i18n';
-import { IconButton } from '@wordpress/components';
/**
* Internal dependencies
*/
import IgnoreNestedEvents from '../ignore-nested-events';
import DefaultBlockAppender from '../default-block-appender';
-import Inserter from '../inserter';
+import ButtonBlockAppender from '../button-block-appender';
function BlockListAppender( {
blockClientIds,
rootClientId,
canInsertDefaultBlock,
isLocked,
+ renderAppender,
} ) {
if ( isLocked ) {
return null;
}
+ // A render prop has been provided, use it to render the appender.
+ if ( renderAppender ) {
+ return (
+
+ { renderAppender() }
+
+ );
+ }
+
+ // Render the default block appender when renderAppender has not been
+ // provided and the context supports use of the default appender.
if ( canInsertDefaultBlock ) {
return (
-
-
-
+
+
+
+
+
);
}
+ // Fallback in the case no renderAppender has been provided and the
+ // default block can't be inserted.
return (
- (
-
- ) }
- isAppender
+ className="block-list-appender__toggle"
/>
);
diff --git a/packages/block-editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss
index 9f53f7572f1799..026e899dbe3025 100644
--- a/packages/block-editor/src/components/block-list-appender/style.scss
+++ b/packages/block-editor/src/components/block-list-appender/style.scss
@@ -1,17 +1,7 @@
-.block-list-appender > .block-editor-inserter {
- display: block;
+.block-list-appender {
+ margin: $block-padding;
}
-.block-list-appender__toggle {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: $grid-size-large;
- outline: $border-width dashed $dark-gray-150;
- width: 100%;
- color: $dark-gray-500;
-
- &:hover {
- outline: $border-width dashed $dark-gray-500;
- }
+.block-list-appender > .block-editor-inserter {
+ display: block;
}
diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index 5231dcb71749b7..eef6c55eded1a6 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -196,6 +196,7 @@ class BlockList extends Component {
selectedBlockClientId,
multiSelectedBlockClientIds,
hasMultiSelection,
+ renderAppender,
} = this.props;
return (
@@ -222,7 +223,11 @@ class BlockList extends Component {
);
} ) }
-
+
+
);
}
@@ -244,6 +249,7 @@ export default compose( [
getMultiSelectedBlockClientIds,
hasMultiSelection,
} = select( 'core/block-editor' );
+
const { rootClientId } = ownProps;
return {
diff --git a/packages/block-editor/src/components/button-block-appender/README.md b/packages/block-editor/src/components/button-block-appender/README.md
new file mode 100644
index 00000000000000..6f29d56bea1596
--- /dev/null
+++ b/packages/block-editor/src/components/button-block-appender/README.md
@@ -0,0 +1,43 @@
+ButtonBlockAppender
+=============================
+
+`ButtonBlockAppender` provides button with a `+` (plus) icon which when clicked will trigger the default Block `Inserter` UI to allow a Block to be inserted.
+
+This is typically used as an alternative to the ` ` component to determine the initial placeholder behaviour for a Block when displayed in the editor UI.
+
+## Usage
+
+In a block's `edit` implementation, render a ` ` component passing in the `rootClientId`.
+
+
+```jsx
+function render( { clientId }) {
+ return (
+
+
Some rendered content here
+
+
+ );
+}
+```
+
+_Note:_
+
+## Props
+
+### `rootClientId`
+* **Type:** `String`
+* **Required** `true`
+* **Default:** `undefined`
+
+The `clientId` of the Block from who's root new Blocks should be inserted. This prop is required by the block `Inserter` component. Typically this is the `clientID` of the Block where the prop is being rendered.
+
+### `className`
+* **Type:** `String`
+* **Default:** `""`
+
+A CSS `class` to be _prepended_ to the default class of `"button-block-appender"`.
+
+## Examples
+
+The [`` component](packages/block-editor/src/components/inner-blocks/) exposes an enhanced version of `ButtonBlockAppender` to allow consumers to choose it as an alternative to the standard behaviour of auto-inserting the default Block (typically `core/paragraph`).
\ No newline at end of file
diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js
new file mode 100644
index 00000000000000..8e56596d1827fe
--- /dev/null
+++ b/packages/block-editor/src/components/button-block-appender/index.js
@@ -0,0 +1,35 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import { Button, Icon } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import Inserter from '../inserter';
+
+export default function ButtonBlockAppender( { rootClientId, className } ) {
+ return (
+ (
+
+ { __( 'Add Block' ) }
+
+
+ ) }
+ isAppender
+ />
+ );
+}
diff --git a/packages/block-editor/src/components/button-block-appender/style.scss b/packages/block-editor/src/components/button-block-appender/style.scss
new file mode 100644
index 00000000000000..5c045aee8afb97
--- /dev/null
+++ b/packages/block-editor/src/components/button-block-appender/style.scss
@@ -0,0 +1,33 @@
+.block-editor-button-block-appender {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: $block-padding*1.5;
+ outline: $border-width dashed $dark-gray-150;
+ width: 100%;
+ color: $dark-gray-500;
+ background: $dark-opacity-light-100;
+
+ &:hover,
+ &:focus {
+ outline: $border-width dashed $dark-gray-500;
+ color: $dark-gray-900;
+ }
+
+ &:active {
+ outline: $border-width dashed $dark-gray-900;
+ color: $dark-gray-900;
+ }
+
+ // Use opacity to work in various editor styles
+ .is-dark-theme & {
+ background: $light-opacity-light-100;
+ color: $light-gray-100;
+
+ &:hover,
+ &:focus {
+ outline: $border-width dashed $white;
+ }
+ }
+}
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index c9a454c253d277..22d532f4fd826b 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -8,6 +8,7 @@ export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockNavigationDropdown } from './block-navigation/dropdown';
export { default as BlockIcon } from './block-icon';
export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar';
+export { default as ButtonBlockerAppender } from './button-block-appender';
export { default as ColorPalette } from './color-palette';
export { default as withColorContext } from './color-palette/with-color-context';
export * from './colors';
diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md
index 8904d4b5cceba4..321f9a71d0d114 100644
--- a/packages/block-editor/src/components/inner-blocks/README.md
+++ b/packages/block-editor/src/components/inner-blocks/README.md
@@ -112,3 +112,37 @@ Template locking allows locking the `InnerBlocks` area for the current template.
If locking is not set in an `InnerBlocks` area: the locking of the parent `InnerBlocks` area is used.
If the block is a top level block: the locking of the Custom Post Type is used.
+
+### `renderAppender`
+* **Type:** `Function`
+* **Default:** - `undefined`. When `renderAppender` is not specific the `` component is as a default. It automatically inserts whichever block is configured as the default block via `wp.blocks.setDefaultBlockName` (typically `paragraph`).
+
+A 'render prop' function that can be used to customize the block's appender.
+
+#### Notes
+* For convenience two predefined appender components are exposed on `InnerBlocks` which can be consumed within the render function:
+ - ` ` - display a `+` (plus) icon button that, when clicked, displays the block picker menu. No default Block is inserted.
+ - ` ` - display the default block appender as set by `wp.blocks.setDefaultBlockName`. Typically this is the `paragraph` block.
+* Consumers are also free to pass any valid render function. This provides the full flexibility to define a bespoke block appender.
+
+#### Example usage
+
+```jsx
+// Utilise a predefined component
+ (
+
+ ) }
+/>
+
+// Fully custom
+ (
+ Some Special Appender
+ ) }
+/>
+```
+
+
+
+
diff --git a/packages/block-editor/src/components/inner-blocks/button-block-appender.js b/packages/block-editor/src/components/inner-blocks/button-block-appender.js
new file mode 100644
index 00000000000000..af4f355e861efa
--- /dev/null
+++ b/packages/block-editor/src/components/inner-blocks/button-block-appender.js
@@ -0,0 +1,13 @@
+/**
+ * Internal dependencies
+ */
+import BaseButtonBlockAppender from '../button-block-appender';
+import withClientId from './with-client-id';
+
+export const ButtonBlockAppender = ( { clientId } ) => {
+ return (
+
+ );
+};
+
+export default withClientId( ButtonBlockAppender );
diff --git a/packages/block-editor/src/components/inner-blocks/default-block-appender.js b/packages/block-editor/src/components/inner-blocks/default-block-appender.js
new file mode 100644
index 00000000000000..1aa6dbc8548b89
--- /dev/null
+++ b/packages/block-editor/src/components/inner-blocks/default-block-appender.js
@@ -0,0 +1,43 @@
+/**
+ * External dependencies
+ */
+import { last } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { compose } from '@wordpress/compose';
+import { withSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import IgnoreNestedEvents from '../ignore-nested-events';
+import BaseDefaultBlockAppender from '../default-block-appender';
+import withClientId from './with-client-id';
+
+export const DefaultBlockAppender = ( { clientId, lastBlockClientId } ) => {
+ return (
+
+
+
+ );
+};
+
+export default compose( [
+ withClientId,
+ withSelect( ( select, { clientId } ) => {
+ const {
+ getBlockOrder,
+ } = select( 'core/block-editor' );
+
+ const blockClientIds = getBlockOrder( clientId );
+
+ return {
+ lastBlockClientId: last( blockClientIds ),
+ };
+ } ),
+] )( DefaultBlockAppender );
diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js
index b55802a0dfe90f..3ee0947fd50816 100644
--- a/packages/block-editor/src/components/inner-blocks/index.js
+++ b/packages/block-editor/src/components/inner-blocks/index.js
@@ -14,6 +14,12 @@ import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpre
import isShallowEqual from '@wordpress/is-shallow-equal';
import { compose } from '@wordpress/compose';
+/**
+ * Internal dependencies
+ */
+import ButtonBlockAppender from './button-block-appender';
+import DefaultBlockAppender from './default-block-appender';
+
/**
* Internal dependencies
*/
@@ -102,6 +108,7 @@ class InnerBlocks extends Component {
clientId,
isSmallScreen,
isSelectedBlockInRoot,
+ renderAppender,
} = this.props;
const { templateInProcess } = this.state;
@@ -114,6 +121,7 @@ class InnerBlocks extends Component {
{ ! templateInProcess && (
) }
@@ -135,6 +143,7 @@ InnerBlocks = compose( [
} = select( 'core/block-editor' );
const { clientId } = ownProps;
const rootClientId = getBlockRootClientId( clientId );
+
return {
isSelectedBlockInRoot: isBlockSelected( clientId ) || hasSelectedInnerBlock( clientId ),
block: getBlock( clientId ),
@@ -160,6 +169,10 @@ InnerBlocks = compose( [
} ),
] )( InnerBlocks );
+// Expose default appender placeholders as components.
+InnerBlocks.DefaultBlockAppender = DefaultBlockAppender;
+InnerBlocks.ButtonBlockAppender = ButtonBlockAppender;
+
InnerBlocks.Content = withBlockContentContext(
( { BlockContent } ) =>
);
diff --git a/packages/block-editor/src/components/inner-blocks/with-client-id.js b/packages/block-editor/src/components/inner-blocks/with-client-id.js
new file mode 100644
index 00000000000000..35672ba1681f25
--- /dev/null
+++ b/packages/block-editor/src/components/inner-blocks/with-client-id.js
@@ -0,0 +1,21 @@
+/**
+ * External dependencies
+ */
+import { pick } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import { withBlockEditContext } from '../block-edit/context';
+
+const withClientId = createHigherOrderComponent(
+ ( WrappedComponent ) => withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) )( WrappedComponent ),
+ 'withClientId'
+);
+
+export default withClientId;
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index deedea8be7ec1f..23d70816397dae 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -12,6 +12,7 @@
@import "./components/block-switcher/style.scss";
@import "./components/block-toolbar/style.scss";
@import "./components/block-types-list/style.scss";
+@import "./components/button-block-appender/style.scss";
@import "./components/color-palette/control.scss";
@import "./components/contrast-checker/style.scss";
@import "./components/default-block-appender/style.scss";
diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss
index df861a04359c60..a352eb29c845fd 100644
--- a/packages/block-library/src/columns/editor.scss
+++ b/packages/block-library/src/columns/editor.scss
@@ -119,6 +119,18 @@
}
}
+/**
+ * Columns act as as a "passthrough container"
+ * and therefore has its vertical margins/padding removed via negative margins
+ * therefore we need to compensate for this here by doubling the spacing on the
+ * vertical to ensure there is equal visual spacing around the inserter. Note there
+ * is no formal API for a "passthrough" Block so this is an edge case overide
+ */
+[data-type="core/columns"] .block-list-appender {
+ margin-top: $block-padding*2;
+ margin-bottom: $block-padding*2;
+}
+
/**
* Vertical Alignment Preview
* note: specificity is important here to ensure individual