diff --git a/blocks/hooks/anchor.js b/blocks/hooks/anchor.js index 65371a4088c433..a61b194f144a6c 100644 --- a/blocks/hooks/anchor.js +++ b/blocks/hooks/anchor.js @@ -6,7 +6,7 @@ import { assign } from 'lodash'; /** * WordPress dependencies */ -import { createHigherOrderComponent } from '@wordpress/element'; +import { createHigherOrderComponent, Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -15,7 +15,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { hasBlockSupport } from '../api'; -import InspectorControls from '../inspector-controls'; +import InspectorAdvancedControls from '../inspector-advanced-controls'; /** * Regular expression matching invalid anchor characters for replacement. @@ -58,22 +58,29 @@ export function addAttribute( settings ) { */ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { - const hasAnchor = hasBlockSupport( props.name, 'anchor' ) && props.isSelected; - return [ - , - hasAnchor && - { - nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); - props.setAttributes( { - anchor: nextValue, - } ); - } } /> - , - ]; + const hasAnchor = hasBlockSupport( props.name, 'anchor' ); + + if ( hasAnchor && props.isSelected ) { + return ( + + + + { + nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); + props.setAttributes( { + anchor: nextValue, + } ); + } } /> + + + ); + } + + return ; }; }, 'withInspectorControl' ); diff --git a/blocks/hooks/custom-class-name.js b/blocks/hooks/custom-class-name.js index a8a8fa07591e76..bd58312857fcbc 100644 --- a/blocks/hooks/custom-class-name.js +++ b/blocks/hooks/custom-class-name.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { createHigherOrderComponent } from '@wordpress/element'; +import { createHigherOrderComponent, Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -16,7 +16,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { hasBlockSupport } from '../api'; -import InspectorControls from '../inspector-controls'; +import InspectorAdvancedControls from '../inspector-advanced-controls'; /** * Filters registered block settings, extending attributes with anchor using ID @@ -49,22 +49,28 @@ export function addAttribute( settings ) { */ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { - const hasCustomClassName = hasBlockSupport( props.name, 'customClassName', true ) && props.isSelected; + const hasCustomClassName = hasBlockSupport( props.name, 'customClassName', true ); - return [ - , - hasCustomClassName && - { - props.setAttributes( { - className: nextValue, - } ); - } } - /> - , - ]; + if ( hasCustomClassName && props.isSelected ) { + return ( + + + + { + props.setAttributes( { + className: nextValue, + } ); + } } + /> + + + ); + } + + return ; }; }, 'withInspectorControl' ); diff --git a/blocks/index.js b/blocks/index.js index c157fc834c928f..9adcc900ddb508 100644 --- a/blocks/index.js +++ b/blocks/index.js @@ -24,6 +24,7 @@ export { default as Editable } from './rich-text/editable'; export { default as ImagePlaceholder } from './image-placeholder'; export { default as InnerBlocks } from './inner-blocks'; export { default as InspectorControls } from './inspector-controls'; +export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; export { default as PlainText } from './plain-text'; export { default as MediaUpload } from './media-upload'; export { default as RichText } from './rich-text'; diff --git a/blocks/inspector-advanced-controls/index.js b/blocks/inspector-advanced-controls/index.js new file mode 100644 index 00000000000000..b725f38ee72bf4 --- /dev/null +++ b/blocks/inspector-advanced-controls/index.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +export default createSlotFill( 'InspectorAdvancedControls' ); diff --git a/blocks/inspector-controls/index.js b/blocks/inspector-controls/index.js index 05c1dea42d5592..4a91e43d798fee 100644 --- a/blocks/inspector-controls/index.js +++ b/blocks/inspector-controls/index.js @@ -1,12 +1,6 @@ /** * WordPress dependencies */ -import { Fill } from '@wordpress/components'; +import { createSlotFill } from '@wordpress/components'; -export default function InspectorControls( { children } ) { - return ( - - { children } - - ); -} +export default createSlotFill( 'InspectorControls' ); diff --git a/components/index.js b/components/index.js index 789c92124067d5..c50b14168b770f 100644 --- a/components/index.js +++ b/components/index.js @@ -49,7 +49,7 @@ export { default as ToggleControl } from './toggle-control'; export { default as Toolbar } from './toolbar'; export { default as Tooltip } from './tooltip'; export { default as TreeSelect } from './tree-select'; -export { Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; +export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; // Higher-Order Components export { default as ifCondition } from './higher-order/if-condition'; diff --git a/components/slot-fill/README.md b/components/slot-fill/README.md index 9584b47d493d6b..45d4bd24491f88 100644 --- a/components/slot-fill/README.md +++ b/components/slot-fill/README.md @@ -42,13 +42,48 @@ Any Fill will automatically occupy this Slot space, even if rendered elsewhere i You can either use the Fill component directly, or a wrapper component type as in the above example to abstract the slot name from consumer awareness. +There is also `createSlotFill` helper method which was created to simplify the process of matching the corresponding `Slot` and `Fill` components: + +```jsx +const Toolbar = createSlotFill( 'Toolbar' ); + +const MyToolbarItem = () => ( + + My item + +); + +const MyToolbar = () => ( +
+ +
+); +``` + ## Props The `SlotFillProvider` component does not accept any props. Both `Slot` and `Fill` accept a `name` string prop, where a `Slot` with a given `name` will render the `children` of any associated `Fill`s. -`Slot` also accepts a `bubblesVirtually` prop which changes the event bubbling behaviour: +`Slot` accepts a `bubblesVirtually` prop which changes the event bubbling behaviour: - By default, events will bubble to their parents on the DOM hierarchy (native event bubbling) - If `bubblesVirtually` is set to true, events will bubble to their virtual parent in the React elements hierarchy instead. + +`Slot` also accepts optional `children` function prop, which takes `fills` as a param. It allows to perform additional processing and wrap `fills` conditionally. + +_Example_: +```jsx +const Toolbar = ( { isMobile } ) => ( +
+ + { ( fills ) => { + return isMobile && fills.length > 3 ? +
{ fills }
: + fills; + } } +
+
+); +``` diff --git a/components/slot-fill/index.js b/components/slot-fill/index.js index 6f4e9f2141e5be..c81d29eafd2e3a 100644 --- a/components/slot-fill/index.js +++ b/components/slot-fill/index.js @@ -9,4 +9,19 @@ export { Slot }; export { Fill }; export { Provider }; -export default { Slot, Fill, Provider }; +export function createSlotFill( name ) { + const Component = ( { children, ...props } ) => ( + + { children } + + ); + Component.displayName = name; + + Component.Slot = ( { children, ...props } ) => ( + + { children } + + ); + + return Component; +} diff --git a/components/slot-fill/slot.js b/components/slot-fill/slot.js index 60df7a4987344d..4785e6e0837d1a 100644 --- a/components/slot-fill/slot.js +++ b/components/slot-fill/slot.js @@ -45,32 +45,34 @@ class Slot extends Component { } render() { - const { name, bubblesVirtually = false, fillProps = {} } = this.props; + const { children, name, bubblesVirtually = false, fillProps = {} } = this.props; const { getFills = noop } = this.context; if ( bubblesVirtually ) { return
; } + const fills = map( getFills( name ), ( fill ) => { + const fillKey = fill.occurrence; + + // If a function is passed as a child, render it with the fillProps. + if ( isFunction( fill.props.children ) ) { + return cloneElement( fill.props.children( fillProps ), { key: fillKey } ); + } + + return Children.map( fill.props.children, ( child, childIndex ) => { + if ( ! child || isString( child ) ) { + return child; + } + + const childKey = `${ fillKey }---${ child.key || childIndex }`; + return cloneElement( child, { key: childKey } ); + } ); + } ); + return (
- { map( getFills( name ), ( fill ) => { - const fillKey = fill.occurrence; - - // If a function is passed as a child, render it with the fillProps. - if ( isFunction( fill.props.children ) ) { - return cloneElement( fill.props.children( fillProps ), { key: fillKey } ); - } - - return Children.map( fill.props.children, ( child, childIndex ) => { - if ( ! child || isString( child ) ) { - return child; - } - - const childKey = `${ fillKey }---${ child.key || childIndex }`; - return cloneElement( child, { key: childKey } ); - } ); - } ) } + { isFunction( children ) ? children( fills.filter( Boolean ) ) : fills }
); } diff --git a/components/slot-fill/test/index.js b/components/slot-fill/test/index.js new file mode 100644 index 00000000000000..041c8337a34b7a --- /dev/null +++ b/components/slot-fill/test/index.js @@ -0,0 +1,28 @@ +/** + * External dependecies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import { createSlotFill, Fill, Slot } from '../'; + +describe( 'createSlotFill', () => { + const SLOT_NAME = 'MyFill'; + const MyFill = createSlotFill( SLOT_NAME ); + + test( 'should match snapshot for Fill', () => { + const wrapper = shallow( ); + + expect( wrapper.type() ).toBe( Fill ); + expect( wrapper ).toHaveProp( 'name', SLOT_NAME ); + } ); + + test( 'should match snapshot for Slot', () => { + const wrapper = shallow( ); + + expect( wrapper.type() ).toBe( Slot ); + expect( wrapper ).toHaveProp( 'name', SLOT_NAME ); + } ); +} ); diff --git a/components/slot-fill/test/slot.js b/components/slot-fill/test/slot.js index 0fe7112f22bf91..3d84e9d5f0289b 100644 --- a/components/slot-fill/test/slot.js +++ b/components/slot-fill/test/slot.js @@ -2,6 +2,7 @@ * External dependencies */ import { mount } from 'enzyme'; +import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -103,6 +104,42 @@ describe( 'Slot', () => { expect( onClose ).toHaveBeenCalledTimes( 1 ); } ); + it( 'should render empty Fills without HTML wrapper when render props used', () => { + const element = mount( + + + { ( fills ) => ( ! isEmpty( fills ) && ( +
+ { fills } +
+ ) ) } +
+ +
+ ); + + expect( element.find( 'Slot > div' ).html() ).toBe( '
' ); + } ); + + it( 'should render a string Fill with HTML wrapper when render props used', () => { + const element = mount( + + + { ( fills ) => ( fills && ( +
+ { fills } +
+ ) ) } +
+ + content + +
+ ); + + expect( element.find( 'Slot > div' ).html() ).toBe( '
content
' ); + } ); + it( 'should re-render Slot when not bubbling virtually', () => { const element = mount( diff --git a/edit-post/components/sidebar/block-inspector-panel/style.scss b/edit-post/components/sidebar/block-inspector-panel/style.scss index 636e0721425299..3e59bcb43fe535 100644 --- a/edit-post/components/sidebar/block-inspector-panel/style.scss +++ b/edit-post/components/sidebar/block-inspector-panel/style.scss @@ -9,4 +9,9 @@ .components-panel__body-toggle { color: $dark-gray-500; } + + &.editor-block-inspector__advanced { + border-top: 1px solid $light-gray-500; + margin-bottom: -16px; + } } diff --git a/editor/components/block-inspector/index.js b/editor/components/block-inspector/index.js index 6d9b0545430776..940311a2ee15ed 100644 --- a/editor/components/block-inspector/index.js +++ b/editor/components/block-inspector/index.js @@ -1,14 +1,20 @@ /** * External dependencies */ +import { isEmpty } from 'lodash'; import { connect } from 'react-redux'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Slot } from '@wordpress/components'; -import { getBlockType, BlockIcon } from '@wordpress/blocks'; +import { + BlockIcon, + getBlockType, + InspectorControls, + InspectorAdvancedControls, +} from '@wordpress/blocks'; +import { PanelBody } from '@wordpress/components'; /** * Internal Dependencies @@ -37,7 +43,17 @@ const BlockInspector = ( { selectedBlock, count } ) => {
{ blockType.description }
, - , + , + + { ( fills ) => ! isEmpty( fills ) && ( + + { fills } + + ) } + , ]; };