Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Block Appender as placeholder to empty InnerBlocks #14241

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
842e742
Adds Block Appender as placeholder to empty InnerBlocks
getdave Mar 5, 2019
12e7f5c
Updates to allow InnerBlocks to control it’s placeholder when empty
getdave Mar 6, 2019
4cf6051
Updates Column Block to opt-in to using Block Appender as placeholder
getdave Mar 6, 2019
ea1ea40
Fix edge case where block can be null value
getdave Mar 6, 2019
07557d1
Simplifies wording on Block Appender
getdave Mar 6, 2019
5e648ec
Adds background color to Block Appender
getdave Mar 6, 2019
6470b37
Adds support for dark themes
getdave Mar 7, 2019
1d438c2
Adds keyboard focus support for visual cues
getdave Mar 7, 2019
0a66f77
Fixes comment to approved version
getdave Mar 7, 2019
57a640e
Adjusts background to use lighter colour variant
getdave Mar 12, 2019
88606c2
Adds consistent spacing around appender
getdave Mar 25, 2019
6f1baea
Removes unecessary Fragment usage
getdave Mar 25, 2019
08dc914
Adjusts font weight and space between icon and “Add Block” text
getdave Mar 26, 2019
f736575
Adjusts appender padding to match supplied visual
getdave Mar 26, 2019
30f0922
Aligns appender with sibling Blocks
getdave Mar 26, 2019
606896d
Revert "Aligns appender with sibling Blocks"
getdave Mar 26, 2019
05a1dfc
Updates to use explict and more extenable prop for choosing the place…
getdave Apr 1, 2019
f698f73
Reverts Column to use default placeholder behaviour
getdave Apr 1, 2019
ee2b182
Removes “is passthrough” edge case for Columns
getdave Apr 1, 2019
0b53217
Fixes Columns layout edge case for Block Appender
getdave Apr 1, 2019
536f425
Updates colors for hover and active states
getdave Apr 2, 2019
4abf1f3
Fix docs to show correct variable type
getdave Apr 3, 2019
558771b
Updates to appender rendering to utilise render prop
getdave Apr 5, 2019
b1af612
Adds default appender as explicit constant
getdave Apr 5, 2019
56b87ef
Adds option to hide appender entirely for InnerBlocks if children pre…
getdave Apr 5, 2019
1dc1b8b
Updates documentation for `renderAppender` and `hideAppenderWhenChild…
getdave Apr 5, 2019
a7f0eaa
Updates to remove superflous usage of `appender` suffix
getdave Apr 5, 2019
4fc3918
Fixes Markdown usage on README heading
getdave Apr 5, 2019
78f64fc
Fix to ensure default appender render when default block is not active
getdave Apr 8, 2019
10979e1
Exposes placeholder defaults as components on InnerBlocks
getdave Apr 9, 2019
bd4e2ae
Adds mixins to handle visually hiding content but preserving for scre…
getdave Apr 11, 2019
1df2ca0
Removes the “Add Block” text from the button appender
getdave Apr 11, 2019
f390c55
Adds Button Block Appender component to DRY up component usage
getdave Apr 11, 2019
58cd20a
Removes superfluous enum handling on renderAppender prop
getdave Apr 11, 2019
6a13d86
Minor - updates comments to end in full stops
getdave Apr 11, 2019
b26a5a6
Extracts `hideWhenChildBlocks` component for use on `InnerBlocks`
getdave Apr 11, 2019
d886e56
Updates docs with correct props and usage examples
getdave Apr 11, 2019
39ce905
Add docs for `ButtonBlockAppender` component
getdave Apr 11, 2019
0aaae76
Adds ability to pass custom CSS className to ButtonBlockAppender comp…
getdave Apr 11, 2019
faf147f
Fixes e2e test by ensuring correct className is restored on appender …
getdave Apr 11, 2019
4c0ad0c
Revert "Adds mixins to handle visually hiding content but preserving …
getdave Apr 11, 2019
ff90334
Utilise existing WP Core screen reader text class
getdave Apr 11, 2019
d1efe5c
Removes dependency on compose
getdave Apr 11, 2019
741ccd2
Improves naming of aliased component
getdave Apr 11, 2019
add695c
Capitalise component name
getdave Apr 11, 2019
676bd1b
Correct spelling
getdave Apr 11, 2019
6d19945
Simplifies check for prop
getdave Apr 11, 2019
b36e3a3
Remove `utils` directory and flatten
getdave Apr 11, 2019
bb43af3
Simplify the use of if statements in BlockListAppender
talldan Apr 12, 2019
1dc1ec3
Use classnames utility to concat classes
talldan Apr 12, 2019
267626c
Rename class to block-editor-button-block-appender
talldan Apr 12, 2019
2a5e98b
Switch to either arrow functions assigned to a const or exported func…
talldan Apr 12, 2019
e2f745a
revert unintentional formatting changes
talldan Apr 12, 2019
6f4e295
Remove HideWhenChildBlocks. This will be added on a separate branch
talldan Apr 12, 2019
13d243a
Tidy up docs
talldan Apr 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ Undocumented declaration.

Undocumented declaration.

<a name="ButtonBlockerAppender" href="#ButtonBlockerAppender">#</a> **ButtonBlockerAppender**

Undocumented declaration.

<a name="ColorPalette" href="#ColorPalette">#</a> **ColorPalette**

Undocumented declaration.
Expand Down
47 changes: 25 additions & 22 deletions packages/block-editor/src/components/block-list-appender/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="block-list-appender">
getdave marked this conversation as resolved.
Show resolved Hide resolved
{ renderAppender() }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my prior point about being able to use the component as:

<InnerBlocks renderAppender={ InnerBlocks.ButtonBlockAppender } />

...it depends on treating the render prop as a component definition. It probably works for plain function components, but also might be more correctly expressed as: createElement( renderAppender ).

I see most other resources which talk about render props ([1] [2]) don't really mention this, and in most cases it doesn't make a difference, except if you'd want to reimplement ButtonBlockAppender as a class component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting.

createElement( renderAppender ) would prevent any arguments being passed in the way render props typically do, though I suppose props could still be used.

I've left it as it is for now for consistency with other render props in the codebase, but could be something to explore in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createElement( renderAppender ) would prevent any arguments being passed in the way render props typically do, though I suppose props could still be used.

This is the way I tend to see render props used anyways (passed a props object, if anything), so it confused me that there's a tendency to treat them as components by convention, but not through actual use of createElement.

</div>
);
}

// Render the default block appender when renderAppender has not been
// provided and the context supports use of the default appender.
if ( canInsertDefaultBlock ) {
return (
<IgnoreNestedEvents childHandledEvents={ [ 'onFocus', 'onClick', 'onKeyDown' ] }>
<DefaultBlockAppender
rootClientId={ rootClientId }
lastBlockClientId={ last( blockClientIds ) }
/>
</IgnoreNestedEvents>
<div className="block-list-appender">
<IgnoreNestedEvents childHandledEvents={ [ 'onFocus', 'onClick', 'onKeyDown' ] }>
<DefaultBlockAppender
rootClientId={ rootClientId }
lastBlockClientId={ last( blockClientIds ) }
/>
</IgnoreNestedEvents>
</div>
);
}

// Fallback in the case no renderAppender has been provided and the
// default block can't be inserted.
return (
<div className="block-list-appender">
<Inserter
<ButtonBlockAppender
rootClientId={ rootClientId }
renderToggle={ ( { onToggle, disabled, isOpen } ) => (
<IconButton
label={ __( 'Add block' ) }
icon="insert"
onClick={ onToggle }
className="block-list-appender__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
disabled={ disabled }
/>
) }
isAppender
className="block-list-appender__toggle"
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 7 additions & 1 deletion packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class BlockList extends Component {
selectedBlockClientId,
multiSelectedBlockClientIds,
hasMultiSelection,
renderAppender,
} = this.props;

return (
Expand All @@ -222,7 +223,11 @@ class BlockList extends Component {
</AsyncModeProvider>
);
} ) }
<BlockListAppender rootClientId={ rootClientId } />

<BlockListAppender
rootClientId={ rootClientId }
renderAppender={ renderAppender }
/>
</div>
);
}
Expand All @@ -244,6 +249,7 @@ export default compose( [
getMultiSelectedBlockClientIds,
hasMultiSelection,
} = select( 'core/block-editor' );

const { rootClientId } = ownProps;

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 `<DefaultBlockAppender />` 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 `<ButtonBlockAppender />` component passing in the `rootClientId`.


```jsx
function render( { clientId }) {
return (
<div>
<p>Some rendered content here</p>
<ButtonBlockAppender rootClientId={ clientId } />
</div>
);
}
```

_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 [`<InnerBlocks>` 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`).
Original file line number Diff line number Diff line change
@@ -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 (
<Inserter
rootClientId={ rootClientId }
renderToggle={ ( { onToggle, disabled, isOpen } ) => (
<Button
className={ classnames( className, 'block-editor-button-block-appender' ) }
onClick={ onToggle }
aria-expanded={ isOpen }
disabled={ disabled }
>
<span className="screen-reader-text">{ __( 'Add Block' ) }</span>
<Icon icon="insert" />
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive me if I missed something about this, but why not an IconButton with a label prop instead of a Button with an icon and a hidden aria text.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I wonder if it should also use an aria-label instead of screen-reader-text.

I had an attempt at replacing it with an IconButton, but that component has some high specificity on its hover styles that are hard to override.

) }
isAppender
/>
);
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
34 changes: 34 additions & 0 deletions packages/block-editor/src/components/inner-blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<DefaultBlockAppender>` 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:
- `<InnerBlocks.ButtonBlockAppender />` - display a `+` (plus) icon button that, when clicked, displays the block picker menu. No default Block is inserted.
- `<InnerBlocks.DefaultBlockAppender />` - 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
<InnerBlocks
renderAppender={ () => (
<InnerBlocks.ButtonBlockAppender />
) }
/>

// Fully custom
<InnerBlocks
renderAppender={ () => (
<button className="bespoke-appender" type="button">Some Special Appender</button>
) }
/>
```




Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Internal dependencies
*/
import BaseButtonBlockAppender from '../button-block-appender';
import withClientId from './with-client-id';

export const ButtonBlockAppender = ( { clientId } ) => {
return (
<BaseButtonBlockAppender rootClientId={ clientId } />
);
};

export default withClientId( ButtonBlockAppender );
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
getdave marked this conversation as resolved.
Show resolved Hide resolved
* 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 (
<IgnoreNestedEvents childHandledEvents={ [ 'onFocus', 'onClick', 'onKeyDown' ] }>
<BaseDefaultBlockAppender
rootClientId={ clientId }
lastBlockClientId={ lastBlockClientId }
/>
</IgnoreNestedEvents>
);
};

export default compose( [
withClientId,
withSelect( ( select, { clientId } ) => {
const {
getBlockOrder,
} = select( 'core/block-editor' );

const blockClientIds = getBlockOrder( clientId );

return {
lastBlockClientId: last( blockClientIds ),
};
} ),
] )( DefaultBlockAppender );
Loading