diff --git a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js index 8b5d5564fd818f..69dbf44422e7ac 100644 --- a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js +++ b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js @@ -103,19 +103,16 @@ describe( 'Meta boxes', () => { // Open the excerpt panel. await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelToggleButtonWithTitle( - 'Excerpt' + const summaryButton = await findSidebarPanelToggleButtonWithTitle( + 'Summary' ); - if ( excerptButton ) { - await excerptButton.click( 'button' ); + if ( summaryButton ) { + await summaryButton.click( 'button' ); } - await page.waitForSelector( '.editor-post-excerpt textarea' ); + await page.waitForSelector( '.editor-post-excerpt p' ); - await page.type( - '.editor-post-excerpt textarea', - 'Explicitly set excerpt.' - ); + await page.type( '.editor-post-excerpt p', 'Explicitly set excerpt.' ); await publishPost(); diff --git a/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js b/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js index e61927e10ff17f..11b5ceb04d09be 100644 --- a/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js +++ b/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js @@ -33,14 +33,14 @@ describe( 'new editor filtered state', () => { // open the sidebar, we want to see the excerpt. await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelToggleButtonWithTitle( - 'Excerpt' + const summaryButton = await findSidebarPanelToggleButtonWithTitle( + 'Summary' ); - if ( excerptButton ) { - await excerptButton.click( 'button' ); + if ( summaryButton ) { + await summaryButton.click( 'button' ); } const excerpt = await page.$eval( - '.editor-post-excerpt textarea', + '.editor-post-excerpt p', ( element ) => element.innerHTML ); diff --git a/packages/e2e-tests/specs/editor/various/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js index 2be81eac943e76..052348e0bec25c 100644 --- a/packages/e2e-tests/specs/editor/various/sidebar.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js @@ -16,6 +16,19 @@ const SIDEBAR_SELECTOR = '.edit-post-sidebar'; const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active'; const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Post'; +const openSidebarPanelWithTitle = async ( title ) => { + const panel = await page.waitForXPath( + `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ title }")]` + ); + const expanded = await page.evaluate( + ( element ) => element.getAttribute( 'aria-expanded' ), + panel + ); + if ( expanded === 'false' ) { + return panel.click(); + } +}; + describe( 'Sidebar', () => { afterEach( () => { disableFocusLossObservation(); @@ -123,25 +136,23 @@ describe( 'Sidebar', () => { await createNewPost(); await enableFocusLossObservation(); await openDocumentSettingsSidebar(); - - expect( await findSidebarPanelWithTitle( 'Categories' ) ).toBeDefined(); - expect( await findSidebarPanelWithTitle( 'Tags' ) ).toBeDefined(); - expect( - await findSidebarPanelWithTitle( 'Featured image' ) - ).toBeDefined(); - expect( await findSidebarPanelWithTitle( 'Excerpt' ) ).toBeDefined(); - expect( await findSidebarPanelWithTitle( 'Discussion' ) ).toBeDefined(); - expect( - await findSidebarPanelWithTitle( 'Status & visibility' ) - ).toBeDefined(); + const panelNames = [ + 'Summary', + 'Categories', + 'Tags', + 'Discussion', + 'Status & visibility', + ]; + const panels = await Promise.all( + panelNames.map( findSidebarPanelWithTitle ) + ); + panels.forEach( ( panel ) => expect( panel ).toBeDefined() ); await page.evaluate( () => { const { removeEditorPanel } = wp.data.dispatch( 'core/edit-post' ); removeEditorPanel( 'taxonomy-panel-category' ); removeEditorPanel( 'taxonomy-panel-post_tag' ); - removeEditorPanel( 'featured-image' ); - removeEditorPanel( 'post-excerpt' ); removeEditorPanel( 'discussion-panel' ); removeEditorPanel( 'post-status' ); } ); @@ -156,12 +167,6 @@ describe( 'Sidebar', () => { expect( await page.$x( getPanelToggleSelector( 'Tags' ) ) ).toEqual( [] ); - expect( - await page.$x( getPanelToggleSelector( 'Featured image' ) ) - ).toEqual( [] ); - expect( await page.$x( getPanelToggleSelector( 'Excerpt' ) ) ).toEqual( - [] - ); expect( await page.$x( getPanelToggleSelector( 'Discussion' ) ) ).toEqual( [] ); @@ -169,4 +174,29 @@ describe( 'Sidebar', () => { await page.$x( getPanelToggleSelector( 'Status & visibility' ) ) ).toEqual( [] ); } ); + describe( 'Summary panel', () => { + beforeEach( async () => { + await createNewPost(); + await enableFocusLossObservation(); + await openDocumentSettingsSidebar(); + } ); + it( 'should show all elements', async () => { + await openSidebarPanelWithTitle( 'Summary' ); + const getSelector = ( cssClass ) => + `//div[contains(@class, "edit-post-sidebar")]//div[contains(@class, "edit-post-post-summary")]//*[contains(@class, "${ cssClass }")]`; + const panelElements = await Promise.all( + [ + 'editor-post-featured-image', + 'edit-post-post-title', + 'editor-post-excerpt', + 'post-author-selector', + ].map( ( target ) => + page.waitForXPath( getSelector( target ) ) + ) + ); + panelElements.forEach( ( element ) => + expect( element ).toBeDefined() + ); + } ); + } ); } ); diff --git a/packages/edit-post/src/components/sidebar/featured-image/index.js b/packages/edit-post/src/components/sidebar/featured-image/index.js index 50e8356bb8dc41..51cec18c61493d 100644 --- a/packages/edit-post/src/components/sidebar/featured-image/index.js +++ b/packages/edit-post/src/components/sidebar/featured-image/index.js @@ -1,21 +1,8 @@ -/** - * External dependencies - */ -import { get, partial } from 'lodash'; - /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { PanelBody } from '@wordpress/components'; -import { - PostFeaturedImage, - PostFeaturedImageCheck, - store as editorStore, -} from '@wordpress/editor'; -import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; +import { PostFeaturedImage, PostFeaturedImageCheck } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -27,48 +14,18 @@ import { store as editPostStore } from '../../../store'; */ const PANEL_NAME = 'featured-image'; -function FeaturedImage( { isEnabled, isOpened, postType, onTogglePanel } ) { +export default function FeaturedImage() { + const isEnabled = useSelect( + ( select ) => + select( editPostStore ).isEditorPanelEnabled( PANEL_NAME ), + [] + ); if ( ! isEnabled ) { return null; } - return ( - - - + ); } - -const applyWithSelect = withSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ); - const { getPostType } = select( coreStore ); - const { isEditorPanelEnabled, isEditorPanelOpened } = select( - editPostStore - ); - - return { - postType: getPostType( getEditedPostAttribute( 'type' ) ), - isEnabled: isEditorPanelEnabled( PANEL_NAME ), - isOpened: isEditorPanelOpened( PANEL_NAME ), - }; -} ); - -const applyWithDispatch = withDispatch( ( dispatch ) => { - const { toggleEditorPanelOpened } = dispatch( editPostStore ); - - return { - onTogglePanel: partial( toggleEditorPanelOpened, PANEL_NAME ), - }; -} ); - -export default compose( applyWithSelect, applyWithDispatch )( FeaturedImage ); diff --git a/packages/edit-post/src/components/sidebar/post-excerpt/index.js b/packages/edit-post/src/components/sidebar/post-excerpt/index.js index 208fa733fc7289..953948de0828d9 100644 --- a/packages/edit-post/src/components/sidebar/post-excerpt/index.js +++ b/packages/edit-post/src/components/sidebar/post-excerpt/index.js @@ -1,14 +1,11 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { PanelBody } from '@wordpress/components'; import { PostExcerpt as PostExcerptForm, PostExcerptCheck, } from '@wordpress/editor'; -import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -20,38 +17,18 @@ import { store as editPostStore } from '../../../store'; */ const PANEL_NAME = 'post-excerpt'; -function PostExcerpt( { isEnabled, isOpened, onTogglePanel } ) { +export default function PostExcerpt( { isMinimal } ) { + const isEnabled = useSelect( + ( select ) => + select( editPostStore ).isEditorPanelEnabled( PANEL_NAME ), + [] + ); if ( ! isEnabled ) { return null; } - return ( - - - + ); } - -export default compose( [ - withSelect( ( select ) => { - return { - isEnabled: select( editPostStore ).isEditorPanelEnabled( - PANEL_NAME - ), - isOpened: select( editPostStore ).isEditorPanelOpened( PANEL_NAME ), - }; - } ), - withDispatch( ( dispatch ) => ( { - onTogglePanel() { - return dispatch( editPostStore ).toggleEditorPanelOpened( - PANEL_NAME - ); - }, - } ) ), -] )( PostExcerpt ); diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 2d96f3a33af911..aced8fdcdceecf 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -13,7 +13,6 @@ import PostVisibility from '../post-visibility'; import PostTrash from '../post-trash'; import PostSchedule from '../post-schedule'; import PostSticky from '../post-sticky'; -import PostAuthor from '../post-author'; import PostSlug from '../post-slug'; import PostFormat from '../post-format'; import PostPendingStatus from '../post-pending-status'; @@ -42,7 +41,6 @@ function PostStatus( { isOpened, onTogglePanel } ) { - { fills } diff --git a/packages/edit-post/src/components/sidebar/post-summary/index.js b/packages/edit-post/src/components/sidebar/post-summary/index.js new file mode 100644 index 00000000000000..fd8e2435bc8415 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-summary/index.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { PanelBody, PanelRow } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { PostAuthor, PostTypeSupportCheck } from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import PostTitle from '../post-title'; +import FeaturedImage from '../featured-image'; +import PostExcerpt from '../post-excerpt'; +import { store as editPostStore } from '../../../store'; + +/** + * Module Constants + */ +const PANEL_NAME = 'post-summary'; + +function PostSummary() { + const isOpened = useSelect( + ( select ) => select( editPostStore ).isEditorPanelOpened( PANEL_NAME ), + [] + ); + const { toggleEditorPanelOpened } = useDispatch( editPostStore ); + return ( + toggleEditorPanelOpened( PANEL_NAME ) } + > + + + + + + + + + + + + + + ); +} + +export default PostSummary; diff --git a/packages/edit-post/src/components/sidebar/post-title/index.js b/packages/edit-post/src/components/sidebar/post-title/index.js new file mode 100644 index 00000000000000..395fa6ece53394 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-title/index.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { PlainText } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect, useDispatch } from '@wordpress/data'; + +function PostTitle() { + const { editPost } = useDispatch( editorStore ); + const postTitle = useSelect( + ( select ) => select( editorStore ).getEditedPostAttribute( 'title' ), + [] + ); + return ( + editPost( { title } ) } + __experimentalVersion={ 2 } + /> + ); +} + +export default PostTitle; diff --git a/packages/edit-post/src/components/sidebar/post-title/style.scss b/packages/edit-post/src/components/sidebar/post-title/style.scss new file mode 100644 index 00000000000000..831896d629e8cf --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-title/style.scss @@ -0,0 +1,17 @@ +.edit-post-post-title { + font-size: 16px; + line-height: 1.6; + font-weight: 500; + margin: $grid-unit-15 0; + + // Focus style + width: 100%; + border-radius: $radius-block-ui - $border-width; + + &:focus-within { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } +} diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c6f5234e33cd5c..4a2c062bd38628 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -14,11 +14,10 @@ import { store as interfaceStore } from '@wordpress/interface'; * Internal dependencies */ import SettingsHeader from '../settings-header'; +import PostSummary from '../post-summary'; import PostStatus from '../post-status'; import LastRevision from '../last-revision'; import PostTaxonomies from '../post-taxonomies'; -import FeaturedImage from '../featured-image'; -import PostExcerpt from '../post-excerpt'; import PostLink from '../post-link'; import DiscussionPanel from '../discussion-panel'; import PageAttributes from '../page-attributes'; @@ -85,14 +84,13 @@ const SettingsSidebar = () => { > { ! isTemplateMode && sidebarName === 'edit-post/document' && ( <> + <PostSummary /> <PostStatus /> <Template /> <PluginDocumentSettingPanel.Slot /> <LastRevision /> <PostLink /> <PostTaxonomies /> - <FeaturedImage /> - <PostExcerpt /> <DiscussionPanel /> <PageAttributes /> <MetaBoxes location="side" /> diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index a8360e0d8be2b7..1b93f991a9ba96 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -15,6 +15,7 @@ @import "./components/sidebar/post-schedule/style.scss"; @import "./components/sidebar/post-slug/style.scss"; @import "./components/sidebar/post-status/style.scss"; +@import "./components/sidebar/post-title/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; @import "./components/sidebar/settings-header/style.scss"; @import "./components/sidebar/template/style.scss"; diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 7e734379f64f64..4fe59989d5e0ea 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -13,7 +13,7 @@ import { AUTHORS_QUERY } from './constants'; const minimumUsersForCombobox = 25; -function PostAuthor() { +function PostAuthor( { labelPosition } ) { const showCombobox = useSelect( ( select ) => { const authors = select( coreStore ).getUsers( AUTHORS_QUERY ); @@ -23,7 +23,7 @@ function PostAuthor() { if ( showCombobox ) { return <PostAuthorCombobox />; } - return <PostAuthorSelect />; + return <PostAuthorSelect labelPosition={ labelPosition } />; } export default PostAuthor; diff --git a/packages/editor/src/components/post-author/select.js b/packages/editor/src/components/post-author/select.js index fee86bf491f720..15f1e4020bd199 100644 --- a/packages/editor/src/components/post-author/select.js +++ b/packages/editor/src/components/post-author/select.js @@ -14,7 +14,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '../../store'; import { AUTHORS_QUERY } from './constants'; -function PostAuthorSelect() { +function PostAuthorSelect( { labelPosition } ) { const { editPost } = useDispatch( editorStore ); const { postAuthor, authors } = useSelect( ( select ) => { return { @@ -46,6 +46,7 @@ function PostAuthorSelect() { options={ authorOptions } onChange={ setAuthorId } value={ postAuthor } + labelPosition={ labelPosition } /> ); } diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index fcba84ec03754a..2c1a7d069a5261 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -3,21 +3,33 @@ */ import { __ } from '@wordpress/i18n'; import { ExternalLink, TextareaControl } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -function PostExcerpt( { excerpt, onUpdateExcerpt } ) { +function PostExcerptMinimal( { excerpt, onChange } ) { return ( - <div className="editor-post-excerpt"> + <RichText + aria-label={ __( 'Post excerpt text' ) } + placeholder={ __( 'Add excerpt' ) } + value={ excerpt } + onChange={ onChange } + tagName="p" + /> + ); +} + +function PostExcerptVerbose( { excerpt, onChange } ) { + return ( + <> <TextareaControl label={ __( 'Write an excerpt (optional)' ) } className="editor-post-excerpt__textarea" - onChange={ ( value ) => onUpdateExcerpt( value ) } + onChange={ onChange } value={ excerpt } /> <ExternalLink @@ -27,19 +39,25 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { > { __( 'Learn more about manual excerpts' ) } </ExternalLink> - </div> + </> ); } -export default compose( [ - withSelect( ( select ) => { - return { - excerpt: select( editorStore ).getEditedPostAttribute( 'excerpt' ), - }; - } ), - withDispatch( ( dispatch ) => ( { - onUpdateExcerpt( excerpt ) { - dispatch( editorStore ).editPost( { excerpt } ); - }, - } ) ), -] )( PostExcerpt ); +export default function PostExcerpt( { isMinimal } ) { + const { editPost } = useDispatch( editorStore ); + const excerpt = useSelect( + ( select ) => select( editorStore ).getEditedPostAttribute( 'excerpt' ), + [] + ); + const Component = isMinimal ? PostExcerptMinimal : PostExcerptVerbose; + return ( + <div className="editor-post-excerpt"> + <Component + excerpt={ excerpt } + onChange={ ( newExcerpt ) => + editPost( { excerpt: newExcerpt } ) + } + /> + </div> + ); +} diff --git a/packages/editor/src/components/post-excerpt/style.scss b/packages/editor/src/components/post-excerpt/style.scss index 056d81ad36c731..ebd21d8d797cbb 100644 --- a/packages/editor/src/components/post-excerpt/style.scss +++ b/packages/editor/src/components/post-excerpt/style.scss @@ -2,3 +2,15 @@ width: 100%; margin-bottom: 10px; } + +.editor-post-excerpt { + // Focus style + border-radius: $radius-block-ui - $border-width; + + &:focus-within { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } +} diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 48c30deed2bcb0..72f2ae0fa4bba5 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -24,6 +24,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; +import { edit, trash } from '@wordpress/icons'; /** * Internal dependencies @@ -61,7 +62,7 @@ function PostFeaturedImage( { if ( media ) { const mediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', - 'post-thumbnail', + 'large', media.id, currentPostId ); @@ -123,63 +124,8 @@ function PostFeaturedImage( { ) } </div> ) } - <MediaUploadCheck fallback={ instructions }> - <MediaUpload - title={ - postLabel.featured_image || - DEFAULT_FEATURE_IMAGE_LABEL - } - onSelect={ onUpdateImage } - unstableFeaturedImageFlow - allowedTypes={ ALLOWED_MEDIA_TYPES } - modalClass="editor-post-featured-image__media-modal" - render={ ( { open } ) => ( - <div className="editor-post-featured-image__container"> - <Button - className={ - ! featuredImageId - ? 'editor-post-featured-image__toggle' - : 'editor-post-featured-image__preview' - } - onClick={ open } - aria-label={ - ! featuredImageId - ? null - : __( 'Edit or update the image' ) - } - aria-describedby={ - ! featuredImageId - ? null - : `editor-post-featured-image-${ featuredImageId }-describedby` - } - > - { !! featuredImageId && media && ( - <ResponsiveWrapper - naturalWidth={ mediaWidth } - naturalHeight={ mediaHeight } - isInline - > - <img - src={ mediaSourceUrl } - alt="" - /> - </ResponsiveWrapper> - ) } - { !! featuredImageId && ! media && ( - <Spinner /> - ) } - { ! featuredImageId && - ( postLabel.set_featured_image || - DEFAULT_SET_FEATURE_IMAGE_LABEL ) } - </Button> - <DropZone onFilesDrop={ onDropImage } /> - </div> - ) } - value={ featuredImageId } - /> - </MediaUploadCheck> - { !! featuredImageId && media && ! media.isLoading && ( - <MediaUploadCheck> + <div className="editor-post-featured-image__container"> + <MediaUploadCheck fallback={ instructions }> <MediaUpload title={ postLabel.featured_image || @@ -190,25 +136,88 @@ function PostFeaturedImage( { allowedTypes={ ALLOWED_MEDIA_TYPES } modalClass="editor-post-featured-image__media-modal" render={ ( { open } ) => ( - <Button onClick={ open } variant="secondary"> - { __( 'Replace Image' ) } - </Button> + <> + <Button + className={ + ! featuredImageId + ? 'editor-post-featured-image__toggle' + : 'editor-post-featured-image__preview' + } + onClick={ open } + aria-label={ + ! featuredImageId + ? null + : __( + 'Edit or update the image' + ) + } + aria-describedby={ + ! featuredImageId + ? null + : `editor-post-featured-image-${ featuredImageId }-describedby` + } + > + { !! featuredImageId && media && ( + <ResponsiveWrapper + naturalWidth={ mediaWidth } + naturalHeight={ mediaHeight } + isInline + > + <img + src={ mediaSourceUrl } + alt="" + /> + </ResponsiveWrapper> + ) } + { !! featuredImageId && ! media && ( + <Spinner /> + ) } + { ! featuredImageId && + ( postLabel.set_featured_image || + DEFAULT_SET_FEATURE_IMAGE_LABEL ) } + </Button> + <DropZone onFilesDrop={ onDropImage } /> + </> ) } + value={ featuredImageId } /> </MediaUploadCheck> - ) } - { !! featuredImageId && ( - <MediaUploadCheck> - <Button - onClick={ onRemoveImage } - variant="link" - isDestructive - > - { postLabel.remove_featured_image || - DEFAULT_REMOVE_FEATURE_IMAGE_LABEL } - </Button> - </MediaUploadCheck> - ) } + <div className="editor-post-featured-image__actions"> + { !! featuredImageId && media && ! media.isLoading && ( + <MediaUploadCheck> + <MediaUpload + title={ + postLabel.featured_image || + DEFAULT_FEATURE_IMAGE_LABEL + } + onSelect={ onUpdateImage } + unstableFeaturedImageFlow + allowedTypes={ ALLOWED_MEDIA_TYPES } + modalClass="editor-post-featured-image__media-modal" + render={ ( { open } ) => ( + <Button + label={ __( 'Replace Image' ) } + icon={ edit } + onClick={ open } + /> + ) } + /> + </MediaUploadCheck> + ) } + { !! featuredImageId && ( + <MediaUploadCheck> + <Button + label={ + postLabel.remove_featured_image || + DEFAULT_REMOVE_FEATURE_IMAGE_LABEL + } + icon={ trash } + onClick={ onRemoveImage } + /> + </MediaUploadCheck> + ) } + </div> + </div> </div> </PostFeaturedImageCheck> ); diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss index 965780179e6ef9..c7797520260019 100644 --- a/packages/editor/src/components/post-featured-image/style.scss +++ b/packages/editor/src/components/post-featured-image/style.scss @@ -1,23 +1,42 @@ .editor-post-featured-image { padding: 0; + margin-left: -$grid-unit-20; + margin-right: -$grid-unit-20; - &__container { - margin-bottom: 1em; + .components-panel__body-title + & { + margin-top: -5px; + } + + // Ensure a consistent dropzone and avoid jumps when loading. + height: 140px; + overflow: hidden; + + // Thumbnail container. + .editor-post-featured-image__container { + display: flex; + align-items: center; + justify-content: center; position: relative; + height: 100%; } .components-spinner { position: absolute; top: 50%; left: 50%; - margin-top: -9px; - margin-left: -9px; + transform: translate(-50%, -50%); + margin-top: 0; + margin-left: 0; } - // Stack consecutive buttons. - .components-button + .components-button { - display: block; - margin-top: 1em; + // Always center the content inside the cropped thumbnail. + .components-responsive-wrapper { + position: absolute; + width: 100%; + height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } // This keeps images at their intrinsic size (eg. a 50px @@ -26,36 +45,66 @@ max-width: 100%; width: auto; } + + // Edit/Replace action buttons. + // @todo: container could be replaced with a flex container. + .editor-post-featured-image__actions { + position: absolute; + display: flex; + background: $white; + border-radius: $radius-block-ui; + bottom: $grid-unit-20; + right: $grid-unit-20; + opacity: 0; + transition: all 0.1s ease-out; + @include reduce-motion("transition"); + } + + &:focus-within .editor-post-featured-image__actions, + &:focus .editor-post-featured-image__actions, + &:hover .editor-post-featured-image__actions { + opacity: 1; + } } +// Button to set featured image when an image is set. .editor-post-featured-image__toggle, .editor-post-featured-image__preview { + position: relative; display: block; width: 100%; padding: 0; - transition: all 0.1s ease-out; - @include reduce-motion("transition"); - box-shadow: 0 0 0 0 var(--wp-admin-theme-color); -} - -.editor-post-featured-image__preview { - height: auto; -} - -.editor-post-featured-image__preview:not(:disabled):not([aria-disabled="true"]):focus { - box-shadow: 0 0 0 4px var(--wp-admin-theme-color); -} - -.editor-post-featured-image__toggle { - border-radius: $radius-block-ui; background-color: $gray-100; - min-height: 90px; - line-height: 20px; - padding: $grid-unit-10 0; + border-radius: 0; + height: 100%; text-align: center; &:hover { background: $gray-300; color: $gray-900; } + + // Override generic focus styles to be inset. + &:focus:not(:disabled), + &:not(:disabled):not([aria-disabled="true"]):focus { + box-shadow: none; + outline: none; + } + + &:focus:not(:disabled)::after { + content: ""; + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Shown for high contrast modes. + outline: 2px solid transparent; + } + + transition: all 5.1s ease-out; + @include reduce-motion("transition"); }