Skip to content

Commit

Permalink
Merge pull request #2301 from WordPress/fix/popover-cascade
Browse files Browse the repository at this point in the history
Components: Allow style cascade to Popovers by rendering into Gutenberg root element
  • Loading branch information
aduth authored Aug 11, 2017
2 parents 726769d + 4273abf commit 44f97ab
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 37 deletions.
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as PanelHeader } from './panel/header';
export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
export { default as PopoverProvider } from './popover/provider';
export { default as ResponsiveWrapper } from './responsive-wrapper';
export { default as SandBox } from './sandbox';
export { default as Spinner } from './spinner';
Expand Down
17 changes: 17 additions & 0 deletions components/popover/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ function ToggleButton( { isVisible, toggleVisible } ) {
}
```

If you want Popover elementss to render to a specific location on the page to allow style cascade to take effect, you must render a `PopoverProvider` further up the element tree, specifying a target:

```
import { render } from '@wordpress/element';
import { PopoverContext } from '@wordpress/components';
import App from './app';
const app = document.getElementById( 'app' );
render(
<PopoverContext target={ app }>
<App />
</PopoverContext>,
app
);
```

## Props

The component accepts the following props:
Expand Down
9 changes: 7 additions & 2 deletions components/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { isEqual } from 'lodash';
import { isEqual, noop } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -146,6 +146,7 @@ export class Popover extends Component {
return null;
}

const { popoverTarget = document.body } = this.context;
const classes = classnames(
'components-popover',
className,
Expand All @@ -171,11 +172,15 @@ export class Popover extends Component {
</div>
</div>
</PopoverDetectOutside>,
document.body
popoverTarget
) }
</span>
);
}
}

Popover.contextTypes = {
popoverTarget: noop,
};

export default Popover;
27 changes: 27 additions & 0 deletions components/popover/provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* External dependencies
*/
import { noop } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';

class PopoverProvider extends Component {
getChildContext() {
return {
popoverTarget: this.props.target,
};
}

render() {
return this.props.children;
}
}

PopoverProvider.childContextTypes = {
popoverTarget: noop,
};

export default PopoverProvider;
4 changes: 0 additions & 4 deletions components/popover/style.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
.components-popover {
&, * {
box-sizing: border-box;
}

position: fixed;
z-index: z-index( ".components-popover" );
left: 50%;
Expand Down
17 changes: 16 additions & 1 deletion components/popover/test/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/**
* External dependencies
*/
import { shallow } from 'enzyme';
import { shallow, mount } from 'enzyme';

/**
* Internal dependencies
*/
import { Popover } from '../';
import PopoverProvider from '../provider';

describe( 'Popover', () => {
describe( '#componentDidUpdate()', () => {
Expand Down Expand Up @@ -238,5 +239,19 @@ describe( 'Popover', () => {

expect( wrapper.find( '.components-popover' ).prop( 'role' ) ).toBe( 'tooltip' );
} );

it( 'should render into provider context', () => {
const element = require( '@wordpress/element' );
jest.spyOn( element, 'createPortal' );
const target = document.createElement( 'div' );

mount(
<PopoverProvider target={ target }>
<Popover isOpen>Hello</Popover>
</PopoverProvider>
);

expect( element.createPortal.mock.calls[ 0 ][ 1 ] ).toBe( target );
} );
} );
} );
99 changes: 69 additions & 30 deletions editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
import { bindActionCreators } from 'redux';
import { Provider as ReduxProvider } from 'react-redux';
import { Provider as SlotFillProvider } from 'react-slot-fill';
import { flow } from 'lodash';
import moment from 'moment-timezone';
import 'moment-timezone/moment-timezone-utils';

/**
* WordPress dependencies
*/
import { EditableProvider } from '@wordpress/blocks';
import { render } from '@wordpress/element';
import { settings } from '@wordpress/date';
import { createElement, render } from '@wordpress/element';
import { PopoverProvider } from '@wordpress/components';
import { settings as dateSettings } from '@wordpress/date';

/**
* Internal dependencies
Expand Down Expand Up @@ -40,15 +42,15 @@ const DEFAULT_SETTINGS = {
};

// Configure moment globally
moment.locale( settings.l10n.locale );
if ( settings.timezone.string ) {
moment.tz.setDefault( settings.timezone.string );
moment.locale( dateSettings.l10n.locale );
if ( dateSettings.timezone.string ) {
moment.tz.setDefault( dateSettings.timezone.string );
} else {
const momentTimezone = {
name: 'WP',
abbrs: [ 'WP' ],
untils: [ null ],
offsets: [ -settings.timezone.offset * 60 ],
offsets: [ -dateSettings.timezone.offset * 60 ],
};
const unpackedTimezone = moment.tz.pack( momentTimezone );
moment.tz.add( unpackedTimezone );
Expand All @@ -58,35 +60,72 @@ if ( settings.timezone.string ) {
/**
* Initializes and returns an instance of Editor.
*
* @param {String} id Unique identifier for editor instance
* @param {Object} post API entity for post to edit (type required)
* @param {Object} userSettings Editor settings object
* @param {String} id Unique identifier for editor instance
* @param {Object} post API entity for post to edit
* @param {?Object} settings Editor settings object
*/
export function createEditorInstance( id, post, userSettings ) {
const editorSettings = Object.assign( {}, DEFAULT_SETTINGS, userSettings );
export function createEditorInstance( id, post, settings ) {
const store = createReduxStore();
const target = document.getElementById( id );

store.dispatch( {
type: 'SETUP_EDITOR',
settings: editorSettings,
} );
settings = {
...DEFAULT_SETTINGS,
...settings,
};

store.dispatch( { type: 'SETUP_EDITOR' } );

store.dispatch( setInitialPost( post ) );

render(
<ReduxProvider store={ store }>
<SlotFillProvider>
<EditableProvider {
...bindActionCreators( {
onUndo: undo,
}, store.dispatch ) }
>
<EditorSettingsProvider settings={ editorSettings }>
<Layout />
</EditorSettingsProvider>
</EditableProvider>
</SlotFillProvider>
</ReduxProvider>,
document.getElementById( id )
const providers = [
// Redux provider:
//
// - context.store
[
ReduxProvider,
{ store },
],

// Slot / Fill provider:
//
// - context.slots
// - context.fills
[
SlotFillProvider,
],

// Editable provider:
//
// - context.onUndo
[
EditableProvider,
bindActionCreators( {
onUndo: undo,
}, store.dispatch ),
],

// Editor settings provider:
//
// - context.editor
[
EditorSettingsProvider,
{ settings },
],

// Popover provider:
//
// - context.popoverTarget
[
PopoverProvider,
{ target },
],
];

const createEditorElement = flow(
providers.map( ( [ Component, props ] ) => (
( children ) => createElement( Component, props, children )
) )
);

render( createEditorElement( <Layout /> ), target );
}

0 comments on commit 44f97ab

Please sign in to comment.