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 ( + ( + + ) } + 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 + ( + + ) } +/> +``` + + + + 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