diff --git a/components/panel/body.js b/components/panel/body.js index f4243a65f837c..64aef1fae0043 100644 --- a/components/panel/body.js +++ b/components/panel/body.js @@ -26,16 +26,22 @@ class PanelBody extends Component { toggle( event ) { event.preventDefault(); - this.setState( ( state ) => ( { - opened: ! state.opened, - } ) ); + if ( this.props.opened === undefined ) { + this.setState( ( state ) => ( { + opened: ! state.opened, + } ) ); + } + + if ( this.props.onToggle ) { + this.props.onToggle(); + } } render() { - const { title, children } = this.props; - const { opened } = this.state; - const icon = `arrow-${ opened ? 'down' : 'right' }`; - const className = classnames( 'components-panel__body', { 'is-opened': opened } ); + const { title, children, opened } = this.props; + const isOpened = opened === undefined ? this.state.opened : opened; + const icon = `arrow-${ isOpened ? 'down' : 'right' }`; + const className = classnames( 'components-panel__body', { 'is-opened': isOpened } ); return (
@@ -44,7 +50,7 @@ class PanelBody extends Component { ) } - { this.state.opened && children } + { isOpened && children }
); } diff --git a/components/panel/test/body.js b/components/panel/test/body.js index 3b52f705dcbd8..d9e09ac44573e 100644 --- a/components/panel/test/body.js +++ b/components/panel/test/body.js @@ -35,6 +35,13 @@ describe( 'PanelBody', () => { expect( icon.prop( 'icon' ) ).toBe( 'arrow-right' ); } ); + it( 'should use the "opened" prop instead of state if provided', () => { + const panelBody = shallow( ); + expect( panelBody.state( 'opened' ) ).toBe( false ); + const icon = panelBody.find( 'Dashicon' ); + expect( icon.prop( 'icon' ) ).toBe( 'arrow-down' ); + } ); + it( 'should render child elements within PanelBody element', () => { const panelBody = shallow( ); expect( panelBody.instance().props.children ).toBe( 'Some Text' ); diff --git a/editor/actions.js b/editor/actions.js index a5f8ffc6c16e0..27938e8be1f1f 100644 --- a/editor/actions.js +++ b/editor/actions.js @@ -255,6 +255,19 @@ export function stopTyping() { }; } +/** + * Returns an action object used in signalling that the user toggled a sidebar panel + * + * @param {String} panel The panel name + * @return {Object} Action object + */ +export function toggleSidebarPanel( panel ) { + return { + type: 'TOGGLE_SIDEBAR_PANEL', + panel, + }; +} + /** * Returns an action object used to create a notice * diff --git a/editor/reducer.js b/editor/reducer.js index 7dd078392ca40..6a8431fbe0265 100644 --- a/editor/reducer.js +++ b/editor/reducer.js @@ -3,7 +3,7 @@ */ import optimist from 'redux-optimist'; import { combineReducers } from 'redux'; -import { reduce, keyBy, first, last, omit, without, mapValues } from 'lodash'; +import { get, reduce, keyBy, first, last, omit, without, mapValues } from 'lodash'; /** * WordPress dependencies @@ -441,13 +441,21 @@ export function mode( state = 'visual', action ) { return state; } -export function preferences( state = { isSidebarOpened: ! isMobile }, action ) { +export function preferences( state = { isSidebarOpened: ! isMobile, panels: { 'post-status': true } }, action ) { switch ( action.type ) { case 'TOGGLE_SIDEBAR': return { - ... state, + ...state, isSidebarOpened: ! state.isSidebarOpened, }; + case 'TOGGLE_SIDEBAR_PANEL': + return { + ...state, + panels: { + ...state.panels, + [ action.panel ]: ! get( state, [ 'panels', action.panel ], false ), + }, + }; } return state; diff --git a/editor/selectors.js b/editor/selectors.js index 998fa2f47ee1f..5286bc8aa4e73 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -54,7 +54,7 @@ export function getPreference( state, preferenceKey ) { } /** - * Returns true if the editor sidebar panel is open, or false otherwise. + * Returns true if the editor sidebar is open, or false otherwise. * * @param {Object} state Global application state * @return {Boolean} Whether sidebar is open @@ -63,6 +63,18 @@ export function isEditorSidebarOpened( state ) { return getPreference( state, 'isSidebarOpened' ); } +/** + * Returns true if the editor sidebar panel is open, or false otherwise. + * + * @param {Object} state Global application state + * @param {STring} panel Sidebar panel name + * @return {Boolean} Whether sidebar is open + */ +export function isEditorSidebarPanelOpened( state, panel ) { + const panels = getPreference( state, 'panels' ); + return panels ? !! panels[ panel ] : false; +} + /** * Returns true if any past editor history snapshots exist, or false otherwise. * diff --git a/editor/sidebar/discussion-panel/index.js b/editor/sidebar/discussion-panel/index.js index 1579912599eda..96e766c1377ac 100644 --- a/editor/sidebar/discussion-panel/index.js +++ b/editor/sidebar/discussion-panel/index.js @@ -12,18 +12,24 @@ import { PanelBody, PanelRow, FormToggle, withInstanceId } from '@wordpress/comp /** * Internal Dependencies */ -import { getEditedPostAttribute } from '../../selectors'; -import { editPost } from '../../actions'; +import { getEditedPostAttribute, isEditorSidebarPanelOpened } from '../../selectors'; +import { editPost, toggleSidebarPanel } from '../../actions'; -function DiscussionPanel( { pingStatus = 'open', commentStatus = 'open', instanceId, ...props } ) { +/** + * Module Constants + */ +const PANEL_NAME = 'discussion-panel'; + +function DiscussionPanel( { pingStatus = 'open', commentStatus = 'open', instanceId, isOpened, ...props } ) { const onTogglePingback = () => props.editPost( { ping_status: pingStatus === 'open' ? 'closed' : 'open' } ); const onToggleComments = () => props.editPost( { comment_status: commentStatus === 'open' ? 'closed' : 'open' } ); + const onTogglePanel = () => props.toggleSidebarPanel( PANEL_NAME ); const commentsToggleId = 'allow-comments-toggle-' + instanceId; const pingbacksToggleId = 'allow-pingbacks-toggle-' + instanceId; return ( - + props.toggleSidebarPanel( PANEL_NAME ); -function FeaturedImage( { featuredImageId, onUpdateImage, onRemoveImage, media } ) { return ( - +
{ !! featuredImageId && { return { featuredImageId: getEditedPostAttribute( state, 'featured_media' ), + isOpened: isEditorSidebarPanelOpened( state, PANEL_NAME ), }; }, - ( dispatch ) => { - return { - onUpdateImage( image ) { - dispatch( editPost( { featured_media: image.id } ) ); - }, - onRemoveImage() { - dispatch( editPost( { featured_media: null } ) ); - }, - }; + { + onUpdateImage( image ) { + return editPost( { featured_media: image.id } ); + }, + onRemoveImage() { + return editPost( { featured_media: null } ); + }, + toggleSidebarPanel, } ); diff --git a/editor/sidebar/page-attributes/index.js b/editor/sidebar/page-attributes/index.js index 456979b2ef7d2..c7382d6d7d756 100644 --- a/editor/sidebar/page-attributes/index.js +++ b/editor/sidebar/page-attributes/index.js @@ -14,14 +14,20 @@ import { PanelBody, PanelRow, withAPIData, withInstanceId } from '@wordpress/com /** * Internal dependencies */ -import { editPost } from '../../actions'; -import { getCurrentPostType, getEditedPostAttribute } from '../../selectors'; +import { editPost, toggleSidebarPanel } from '../../actions'; +import { getCurrentPostType, getEditedPostAttribute, isEditorSidebarPanelOpened } from '../../selectors'; + +/** + * Module Constants + */ +const PANEL_NAME = 'page-attributes'; export class PageAttributes extends Component { constructor() { super( ...arguments ); this.setUpdatedOrder = this.setUpdatedOrder.bind( this ); + this.onToggle = this.onToggle.bind( this ); this.state = { supportsPageAttributes: false, @@ -35,8 +41,12 @@ export class PageAttributes extends Component { } } + onToggle() { + this.props.toggleSidebarPanel( PANEL_NAME ); + } + render() { - const { instanceId, order, postType } = this.props; + const { instanceId, order, postType, isOpened } = this.props; const supportsPageAttributes = get( postType.data, [ 'supports', 'page-attributes', @@ -53,7 +63,8 @@ export class PageAttributes extends Component { return (