diff --git a/components/index.js b/components/index.js index e29243f1bff079..8d823b582bd77a 100644 --- a/components/index.js +++ b/components/index.js @@ -13,6 +13,7 @@ export { default as NoticeList } from './notice/list'; export { default as Panel } from './panel'; export { default as PanelHeader } from './panel/header'; export { default as PanelBody } from './panel/body'; +export { default as PanelRow } from './panel/row'; export { default as Placeholder } from './placeholder'; export { default as ResponsiveWrapper } from './responsive-wrapper'; export { default as SandBox } from './sandbox'; diff --git a/components/panel/row.js b/components/panel/row.js new file mode 100644 index 00000000000000..ca9e9e4854dbf8 --- /dev/null +++ b/components/panel/row.js @@ -0,0 +1,16 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +function PanelRow( { className, children } ) { + const classes = classnames( 'components-panel__row', className ); + + return ( +
+ { children } +
+ ); +} + +export default PanelRow; diff --git a/components/panel/style.scss b/components/panel/style.scss index 0d0dbcfb6562e0..058faed1d7eb99 100644 --- a/components/panel/style.scss +++ b/components/panel/style.scss @@ -77,3 +77,14 @@ .components-panel__body-toggle-icon { margin-right: -5px; } + +.components-panel__row { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + + &:empty { + margin-top: 0; + } +} diff --git a/editor/sidebar/discussion-panel/index.js b/editor/sidebar/discussion-panel/index.js index 1801e931311ac2..97bb1972296ada 100644 --- a/editor/sidebar/discussion-panel/index.js +++ b/editor/sidebar/discussion-panel/index.js @@ -7,12 +7,11 @@ import { connect } from 'react-redux'; * WordPress dependencies */ import { __ } from 'i18n'; -import { PanelBody, FormToggle, withInstanceId } from 'components'; +import { PanelBody, PanelRow, FormToggle, withInstanceId } from 'components'; /** * Internal Dependencies */ -import './style.scss'; import { getEditedPostAttribute } from '../../selectors'; import { editPost } from '../../actions'; @@ -25,7 +24,7 @@ function DiscussionPanel( { pingStatus = 'open', commentStatus = 'open', instanc return ( -
+ -
-
+ + -
+
); } diff --git a/editor/sidebar/discussion-panel/style.scss b/editor/sidebar/discussion-panel/style.scss deleted file mode 100644 index 1e9219bed17fe7..00000000000000 --- a/editor/sidebar/discussion-panel/style.scss +++ /dev/null @@ -1,6 +0,0 @@ -.editor-discussion-panel__row { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 20px; -} diff --git a/editor/sidebar/page-attributes/index.js b/editor/sidebar/page-attributes/index.js new file mode 100644 index 00000000000000..74b922f5cf41cd --- /dev/null +++ b/editor/sidebar/page-attributes/index.js @@ -0,0 +1,109 @@ +/** + * External dependencies + */ +import { connect } from 'react-redux'; + +/** + * WordPress dependencies + */ +import { __ } from 'i18n'; +import { Component } from 'element'; +import { PanelBody, PanelRow, withInstanceId } from 'components'; + +/** + * Internal dependencies + */ +import { editPost } from '../../actions'; +import { getCurrentPostType, getEditedPostAttribute } from '../../selectors'; + +export class PageAttributes extends Component { + constructor() { + super( ...arguments ); + + this.setUpdatedOrder = this.setUpdatedOrder.bind( this ); + + this.state = { + supportsPageAttributes: false, + }; + } + + componentDidMount() { + this.fetchSupports(); + } + + fetchSupports() { + const { postTypeSlug } = this.props; + this.fetchSupportsRequest = new wp.api.models.Type( { id: postTypeSlug } ) + .fetch( { data: { context: 'edit' } } ) + .done( ( postType ) => { + const { + 'page-attributes': supportsPageAttributes = false, + } = postType.supports; + + this.setState( { supportsPageAttributes } ); + } ); + } + + componentWillUnmount() { + if ( this.fetchSupportsRequest ) { + this.fetchSupportsRequest.abort(); + } + } + + setUpdatedOrder( event ) { + const order = Number( event.target.value ); + if ( order >= 0 ) { + this.props.onUpdateOrder( order ); + } + } + + render() { + const { instanceId, order } = this.props; + const { supportsPageAttributes } = this.state; + + // Only render fields if post type supports page attributes + if ( ! supportsPageAttributes ) { + return null; + } + + // Create unique identifier for inputs + const inputId = `editor-page-attributes__order-${ instanceId }`; + + return ( + + + + + + + ); + } +} + +export default connect( + ( state ) => { + return { + postTypeSlug: getCurrentPostType( state ), + order: getEditedPostAttribute( state, 'menu_order' ), + }; + }, + ( dispatch ) => { + return { + onUpdateOrder( order ) { + dispatch( editPost( { + menu_order: order, + } ) ); + }, + }; + } +)( withInstanceId( PageAttributes ) ); diff --git a/editor/sidebar/page-attributes/test/index.js b/editor/sidebar/page-attributes/test/index.js new file mode 100644 index 00000000000000..c87d6c6435009c --- /dev/null +++ b/editor/sidebar/page-attributes/test/index.js @@ -0,0 +1,68 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import { PageAttributes } from '../'; + +describe( 'PageAttributes', () => { + let fetchSupports; + beforeEach( () => { + fetchSupports = PageAttributes.prototype.fetchSupports; + PageAttributes.prototype.fetchSupports = jest.fn(); + } ); + + afterEach( () => { + PageAttributes.prototype.fetchSupports = fetchSupports; + } ); + + it( 'should not render anything if no page attributes support', () => { + const wrapper = shallow( ); + + expect( wrapper.type() ).toBe( null ); + } ); + + it( 'should render input if page attributes support', () => { + const wrapper = shallow( ); + wrapper.setState( { supportsPageAttributes: true } ); + + expect( wrapper.type() ).not.toBe( null ); + } ); + + it( 'should reject invalid input', () => { + const onUpdateOrder = jest.fn(); + const wrapper = shallow( ); + wrapper.setState( { supportsPageAttributes: true } ); + + wrapper.find( 'input' ).simulate( 'change', { + target: { + value: -1, + }, + } ); + + wrapper.find( 'input' ).simulate( 'change', { + target: { + value: 'bad', + }, + } ); + + expect( onUpdateOrder ).not.toHaveBeenCalled(); + } ); + + it( 'should update with valid input', () => { + const onUpdateOrder = jest.fn(); + const wrapper = shallow( ); + wrapper.setState( { supportsPageAttributes: true } ); + + wrapper.find( 'input' ).simulate( 'change', { + target: { + value: 4, + }, + } ); + + expect( onUpdateOrder ).toHaveBeenCalledWith( 4 ); + } ); +} ); diff --git a/editor/sidebar/post-schedule/index.js b/editor/sidebar/post-schedule/index.js index 5e2e72c87dbb51..de6758d1a89244 100644 --- a/editor/sidebar/post-schedule/index.js +++ b/editor/sidebar/post-schedule/index.js @@ -12,6 +12,7 @@ import moment from 'moment'; import { __ } from 'i18n'; import { Component } from 'element'; import { dateI18n, settings } from 'date'; +import { PanelRow } from 'components'; /** * Internal dependencies @@ -58,7 +59,7 @@ class PostSchedule extends Component { ); return ( -
+ { __( 'Publish' ) }
} - + ); } } diff --git a/editor/sidebar/post-schedule/style.scss b/editor/sidebar/post-schedule/style.scss index d3ad68dde96e53..e179ad4d32eebf 100644 --- a/editor/sidebar/post-schedule/style.scss +++ b/editor/sidebar/post-schedule/style.scss @@ -1,9 +1,6 @@ @import '~react-datepicker/dist/react-datepicker'; .editor-post-schedule { - display: flex; - justify-content: space-between; - align-items: center; width: 100%; position: relative; } diff --git a/editor/sidebar/post-settings/index.js b/editor/sidebar/post-settings/index.js index 6621b621d5a61f..7a78cc1f8dc132 100644 --- a/editor/sidebar/post-settings/index.js +++ b/editor/sidebar/post-settings/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { connect } from 'react-redux'; - /** * WordPress dependencies */ @@ -19,24 +14,19 @@ import FeaturedImage from '../featured-image'; import DiscussionPanel from '../discussion-panel'; import LastRevision from '../last-revision'; import TableOfContents from '../table-of-contents'; +import PageAttributes from '../page-attributes'; -const PostSettings = () => { - return ( - - - - - - - - - - ); -}; +const panel = ( + + + + + + + + + + +); -export default connect( - undefined, - ( dispatch ) => ( { - toggleSidebar: () => dispatch( { type: 'TOGGLE_SIDEBAR' } ), - } ) -)( PostSettings ); +export default () => panel; diff --git a/editor/sidebar/post-status/index.js b/editor/sidebar/post-status/index.js index f367e463646d23..d91ba764a57199 100644 --- a/editor/sidebar/post-status/index.js +++ b/editor/sidebar/post-status/index.js @@ -8,12 +8,11 @@ import { connect } from 'react-redux'; */ import { __ } from 'i18n'; import { Component } from 'element'; -import { PanelBody, FormToggle, withInstanceId } from 'components'; +import { PanelBody, PanelRow, FormToggle, withInstanceId } from 'components'; /** * Internal Dependencies */ -import './style.scss'; import PostVisibility from '../post-visibility'; import PostTrash from '../post-trash'; import PostSchedule from '../post-schedule'; @@ -48,7 +47,7 @@ class PostStatus extends Component { return ( { ! isPublished && -
+ -
+ } -
- -
-
- -
-
+ + + { __( 'Post Format' ) } { format } -
+ -
- -
+
); } diff --git a/editor/sidebar/post-status/style.scss b/editor/sidebar/post-status/style.scss deleted file mode 100644 index 82e085a5b71a8a..00000000000000 --- a/editor/sidebar/post-status/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -.editor-post-status__row { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 20px; - - &:empty { - padding-top: 0; - } -} diff --git a/editor/sidebar/post-sticky/index.js b/editor/sidebar/post-sticky/index.js index 5909d6ec3f6c68..965e70828af605 100644 --- a/editor/sidebar/post-sticky/index.js +++ b/editor/sidebar/post-sticky/index.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; * WordPress dependencies */ import { __ } from 'i18n'; -import { FormToggle, withInstanceId } from 'components'; +import { PanelRow, FormToggle, withInstanceId } from 'components'; /** * Internal dependencies @@ -23,7 +23,7 @@ function PostSticky( { onUpdateSticky, postType, postSticky = false, instanceId const stickyToggleId = 'post-sticky-toggle-' + instanceId; return ( -
+ -
+ ); } diff --git a/editor/sidebar/post-trash/index.js b/editor/sidebar/post-trash/index.js index ce0ebbd4e3ae23..6e64b153c564ac 100644 --- a/editor/sidebar/post-trash/index.js +++ b/editor/sidebar/post-trash/index.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; * WordPress dependencies */ import { __ } from 'i18n'; -import { Button, Dashicon } from 'components'; +import { PanelRow, Button, Dashicon } from 'components'; /** * Internal dependencies @@ -28,10 +28,12 @@ function PostTrash( { isNew, postId, postType, ...props } ) { const onClick = () => props.trashPost( postId, postType ); return ( - + + + ); } diff --git a/editor/sidebar/post-visibility/index.js b/editor/sidebar/post-visibility/index.js index 8f663a07bd2bf2..eb857cbefa0783 100644 --- a/editor/sidebar/post-visibility/index.js +++ b/editor/sidebar/post-visibility/index.js @@ -4,13 +4,13 @@ import { connect } from 'react-redux'; import clickOutside from 'react-click-outside'; import { find } from 'lodash'; -import { withInstanceId } from 'components'; /** * WordPress dependencies */ import { __ } from 'i18n'; import { Component } from 'element'; +import { PanelRow, withInstanceId } from 'components'; /** * Internal Dependencies @@ -104,7 +104,7 @@ class PostVisibility extends Component { // Disable Reason: The input is inside the label, we shouldn't need the htmlFor /* eslint-disable jsx-a11y/label-has-for */ return ( -
+ { __( 'Visibility' ) }
+ ); /* eslint-enable jsx-a11y/label-has-for */ } diff --git a/editor/sidebar/post-visibility/style.scss b/editor/sidebar/post-visibility/style.scss index c48ffa99015eae..06c86e1fc3469b 100644 --- a/editor/sidebar/post-visibility/style.scss +++ b/editor/sidebar/post-visibility/style.scss @@ -1,7 +1,4 @@ .editor-post-visibility { - display: flex; - justify-content: space-between; - align-items: center; width: 100%; position: relative; }