Skip to content

Commit

Permalink
Blocks: Try new context API to simplify display of block controls (#5029
Browse files Browse the repository at this point in the history
)

* Blocks: Refactor block controls to use block edit context

* Docs: Update docs to reflect the change in how block controls are handled

* Blocks: Add isSelected handling to InspectorAdvancedControls

* Blocks: Replace getWrapperDisplayName with its new alternative

* Blocks: Use createSlotFill for BlockControls

* Blocks: Introduce BlockFormatControls component

* Blocks: Make sure that RichText controls are only displayed when block is selected
This makes it backward compatible with the existing core blocks

* Blocks: Update test helper to work with block edit context

* Blocks: Make sure that EditContextProvider is only rerendered when isSelected changes

* Blocks: Merge Provider with BlockEdit

* Edit Post: Add information for developers about `isSelected` changes introduced

* Blocks: Improve state handling for BlockEdit context

* Blocks: Addressed feedback related to BlockEditContext changes
  • Loading branch information
gziolo authored Apr 16, 2018
1 parent 33dc86e commit faabea1
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 117 deletions.
22 changes: 7 additions & 15 deletions blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,6 @@ buttons. This is useful for block-level modifications to be made available when
a block is selected. For example, if your block supports alignment, you may
want to display alignment options in the selected block's toolbar.

Because the toolbar should only be shown when the block is selected, it is
important that a `BlockControls` element is only returned when the block's
`isSelected` prop is
[truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy),
meaning that the block is currently selected.

Example:

```js
Expand All @@ -292,15 +286,13 @@ Example:
function edit( props ) {
return [
// Controls: (only visible when block is selected)
props.isSelected && (
el( BlockControls, { key: 'controls' },
el( AlignmentToolbar, {
value: props.align,
onChange: function( nextAlign ) {
props.setAttributes( { align: nextAlign } )
}
} )
)
el( BlockControls, { key: 'controls' },
el( AlignmentToolbar, {
value: props.align,
onChange: function( nextAlign ) {
props.setAttributes( { align: nextAlign } )
}
} )
),

// Block content: (with alignment as attribute)
Expand Down
32 changes: 22 additions & 10 deletions blocks/block-controls/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
/**
* WordPress dependencies
*/
import { Toolbar, Fill } from '@wordpress/components';

export default function BlockControls( { controls, children } ) {
return (
<Fill name="Block.Toolbar">
<Toolbar controls={ controls } />
{ children }
</Fill>
);
}
import { createSlotFill, Toolbar } from '@wordpress/components';

/**
* Internal dependencies
*/
import { ifBlockEditSelected } from '../block-edit/context';

const Fill = createSlotFill( 'BlockControls' );
const { Slot } = Fill;

const BlockControlsFill = ( { controls, children } ) => (
<Fill>
<Toolbar controls={ controls } />
{ children }
</Fill>
);

const BlockControls = ifBlockEditSelected( BlockControlsFill );

BlockControls.Slot = Slot;

export default BlockControls;
6 changes: 4 additions & 2 deletions blocks/block-controls/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { shallow } from 'enzyme';
/**
* Internal dependencies
*/
import BlockControls from '../';
import { BlockControls } from '../';

describe( 'BlockControls', () => {
const controls = [
Expand All @@ -27,7 +27,9 @@ describe( 'BlockControls', () => {
},
];

test( 'Should render a dynamic toolbar of controls', () => {
// Skipped temporarily until Enzyme publishes new version that works with React 16.3.0 APIs.
// eslint-disable-next-line jest/no-disabled-tests
test.skip( 'Should render a dynamic toolbar of controls', () => {
expect( shallow( <BlockControls controls={ controls } children={ <p>Child</p> } /> ) ).toMatchSnapshot();
} );
} );
49 changes: 49 additions & 0 deletions blocks/block-edit/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { createContext, createHigherOrderComponent } from '@wordpress/element';

const { Consumer, Provider } = createContext( {
isSelected: true,
} );

export { Provider as BlockEditContextProvider };

/**
* A Higher Order Component used to inject BlockEdit context to the
* wrapped component.
*
* @param {Component} OriginalComponent Component to wrap.
*
* @return {Component} Component which has BlockEdit context injected.
*/
export const withBlockEditContext = createHigherOrderComponent( ( OriginalComponent ) => {
return ( props ) => (
<Consumer>
{ ( context ) => (
<OriginalComponent
{ ...props }
blockEditContext={ context }
/>
) }
</Consumer>
);
}, 'withBlockEditContext' );

/**
* A Higher Order Component used to render conditionally the wrapped
* component only when the BlockEdit has selected state set.
*
* @param {Component} OriginalComponent Component to wrap.
*
* @return {Component} Component which renders only when the BlockEdit is selected.
*/
export const ifBlockEditSelected = createHigherOrderComponent( ( OriginalComponent ) => {
return ( props ) => (
<Consumer>
{ ( { isSelected } ) => isSelected && (
<OriginalComponent { ...props } />
) }
</Consumer>
);
}, 'ifBlockEditSelected' );
32 changes: 26 additions & 6 deletions blocks/block-edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ import {
getBlockDefaultClassName,
hasBlockSupport,
} from '../api';
import { BlockEditContextProvider } from './context';

export class BlockEdit extends Component {
constructor( props ) {
super( props );
this.state = {};
}

getChildContext() {
const {
id: uid,
Expand All @@ -37,6 +43,18 @@ export class BlockEdit extends Component {
};
}

static getDerivedStateFromProps( nextProps, prevState ) {
if ( nextProps.isSelected === get( prevState, [ 'context', 'isSelected' ] ) ) {
return null;
}

return {
context: {
isSelected: nextProps.isSelected,
},
};
}

render() {
const { name, attributes = {}, isSelected } = this.props;
const blockType = getBlockType( name );
Expand All @@ -59,12 +77,14 @@ export class BlockEdit extends Component {
// For backwards compatibility concerns adds a focus and setFocus prop
// These should be removed after some time (maybe when merging to Core)
return (
<Edit
{ ...this.props }
className={ className }
focus={ isSelected ? {} : false }
setFocus={ noop }
/>
<BlockEditContextProvider value={ this.state.context }>
<Edit
{ ...this.props }
className={ className }
focus={ isSelected ? {} : false }
setFocus={ noop }
/>
</BlockEditContextProvider>
);
}
}
Expand Down
6 changes: 3 additions & 3 deletions blocks/block-edit/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe( 'BlockEdit', () => {

const wrapper = shallow( <BlockEdit name="core/test-block" /> );

expect( wrapper.type() ).toBe( edit );
expect( wrapper.find( edit ) ).toBePresent();
} );

it( 'should use save implementation of block as fallback', () => {
Expand All @@ -51,7 +51,7 @@ describe( 'BlockEdit', () => {

const wrapper = shallow( <BlockEdit name="core/test-block" /> );

expect( wrapper.type() ).toBe( save );
expect( wrapper.find( save ) ).toBePresent();
} );

it( 'should combine the default class name with a custom one', () => {
Expand All @@ -70,6 +70,6 @@ describe( 'BlockEdit', () => {
<BlockEdit name="core/test-block" attributes={ attributes } />
);

expect( wrapper.prop( 'className' ) ).toBe( 'wp-block-test-block my-class' );
expect( wrapper.find( edit ) ).toHaveClassName( 'wp-block-test-block my-class' );
} );
} );
18 changes: 18 additions & 0 deletions blocks/block-format-controls/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* WordPress dependencies
*/
import { createSlotFill } from '@wordpress/components';

/**
* Internal dependencies
*/
import { ifBlockEditSelected } from '../block-edit/context';

const Fill = createSlotFill( 'BlockFormatControls' );
const { Slot } = Fill;

const BlockFormatControls = ifBlockEditSelected( Fill );

BlockFormatControls.Slot = Slot;

export default BlockFormatControls;
4 changes: 3 additions & 1 deletion blocks/hooks/test/align.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ describe( 'align', () => {
expect( wrapper.children() ).toHaveLength( 1 );
} );

it( 'should render toolbar controls if valid alignments', () => {
// Skipped temporarily until Enzyme publishes new version that works with React 16.3.0 APIs.
// eslint-disable-next-line jest/no-disabled-tests
it.skip( 'should render toolbar controls if valid alignments', () => {
registerBlockType( 'core/foo', {
...blockSettings,
supports: {
Expand Down
1 change: 1 addition & 0 deletions blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as AlignmentToolbar } from './alignment-toolbar';
export { default as Autocomplete } from './autocomplete';
export { default as BlockAlignmentToolbar } from './block-alignment-toolbar';
export { default as BlockControls } from './block-controls';
export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockEdit } from './block-edit';
export { default as BlockIcon } from './block-icon';
export { default as ColorPalette } from './color-palette';
Expand Down
14 changes: 13 additions & 1 deletion blocks/inspector-advanced-controls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,16 @@
*/
import { createSlotFill } from '@wordpress/components';

export default createSlotFill( 'InspectorAdvancedControls' );
/**
* Internal dependencies
*/
import { ifBlockEditSelected } from '../block-edit/context';

const Fill = createSlotFill( 'InspectorAdvancedControls' );
const { Slot } = Fill;

const InspectorAdvancedControls = ifBlockEditSelected( Fill );

InspectorAdvancedControls.Slot = Slot;

export default InspectorAdvancedControls;
14 changes: 13 additions & 1 deletion blocks/inspector-controls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,16 @@
*/
import { createSlotFill } from '@wordpress/components';

export default createSlotFill( 'InspectorControls' );
/**
* Internal dependencies
*/
import { ifBlockEditSelected } from '../block-edit/context';

const Fill = createSlotFill( 'InspectorControls' );
const { Slot } = Fill;

const InspectorControls = ifBlockEditSelected( Fill );

InspectorControls.Slot = Slot;

export default InspectorControls;
Loading

0 comments on commit faabea1

Please sign in to comment.