diff --git a/edit-post/components/header/fixed-toolbar-toggle/index.js b/edit-post/components/header/feature-toggle/index.js similarity index 55% rename from edit-post/components/header/fixed-toolbar-toggle/index.js rename to edit-post/components/header/feature-toggle/index.js index c80452c12a7f3..e5ff0ca0c8a50 100644 --- a/edit-post/components/header/fixed-toolbar-toggle/index.js +++ b/edit-post/components/header/feature-toggle/index.js @@ -6,12 +6,10 @@ import { withSelect, withDispatch } from '@wordpress/data'; /** * WordPress Dependencies */ -import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { MenuItem } from '@wordpress/components'; -import { ifViewportMatches } from '@wordpress/viewport'; -function FixedToolbarToggle( { onToggle, isActive } ) { +function FeatureToggle( { onToggle, isActive, label } ) { return ( - { __( 'Fix Toolbar to Top' ) } + { label } ); } export default compose( [ - withSelect( ( select ) => ( { - isActive: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + withSelect( ( select, { feature } ) => ( { + isActive: select( 'core/edit-post' ).isFeatureActive( feature ), } ) ), withDispatch( ( dispatch, ownProps ) => ( { onToggle() { - dispatch( 'core/edit-post' ).toggleFeature( 'fixedToolbar' ); + dispatch( 'core/edit-post' ).toggleFeature( ownProps.feature ); ownProps.onToggle(); }, } ) ), - ifViewportMatches( 'medium' ), -] )( FixedToolbarToggle ); +] )( FeatureToggle ); diff --git a/edit-post/components/header/more-menu/index.js b/edit-post/components/header/more-menu/index.js index 74327c6df032c..42ab8d1756e84 100644 --- a/edit-post/components/header/more-menu/index.js +++ b/edit-post/components/header/more-menu/index.js @@ -10,10 +10,10 @@ import { Fragment } from '@wordpress/element'; */ import './style.scss'; import ModeSwitcher from '../mode-switcher'; -import FixedToolbarToggle from '../fixed-toolbar-toggle'; import PluginMoreMenuGroup from '../plugins-more-menu-group'; import TipsToggle from '../tips-toggle'; import KeyboardShortcutsHelpMenuItem from '../keyboard-shortcuts-help-menu-item'; +import WritingMenu from '../writing-menu'; const MoreMenu = () => ( ( ) } renderContent={ ( { onClose } ) => ( + - - - - + diff --git a/edit-post/components/header/writing-menu/index.js b/edit-post/components/header/writing-menu/index.js new file mode 100644 index 0000000000000..1444d2383dd54 --- /dev/null +++ b/edit-post/components/header/writing-menu/index.js @@ -0,0 +1,25 @@ +/** + * WordPress Dependencies + */ +import { MenuGroup } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { ifViewportMatches } from '@wordpress/viewport'; + +/** + * Internal dependencies + */ +import FeatureToggle from '../feature-toggle'; + +function WritingMenu( { onClose } ) { + return ( + + + + + ); +} + +export default ifViewportMatches( 'medium' )( WritingMenu ); diff --git a/edit-post/editor.js b/edit-post/editor.js index 42ff1f5da8303..1ed71dacf372e 100644 --- a/edit-post/editor.js +++ b/edit-post/editor.js @@ -10,7 +10,15 @@ import { StrictMode } from '@wordpress/element'; */ import Layout from './components/layout'; -function Editor( { settings, hasFixedToolbar, post, overridePost, onError, ...props } ) { +function Editor( { + settings, + hasFixedToolbar, + focusMode, + post, + overridePost, + onError, + ...props +} ) { if ( ! post ) { return null; } @@ -18,11 +26,16 @@ function Editor( { settings, hasFixedToolbar, post, overridePost, onError, ...pr const editorSettings = { ...settings, hasFixedToolbar, + focusMode, }; return ( - + @@ -33,5 +46,6 @@ function Editor( { settings, hasFixedToolbar, post, overridePost, onError, ...pr export default withSelect( ( select, { postId, postType } ) => ( { hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + focusMode: select( 'core/edit-post' ).isFeatureActive( 'focusMode' ), post: select( 'core' ).getEntityRecord( 'postType', postType, postId ), } ) )( Editor ); diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index e0509e56ff0a6..1001e9acb1e08 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -354,6 +354,7 @@ export class BlockListBlock extends Component { block, order, mode, + isFocusMode, hasFixedToolbar, isLocked, isFirst, @@ -367,11 +368,11 @@ export class BlockListBlock extends Component { isTypingWithinBlock, isMultiSelecting, hoverArea, - isLargeViewport, isEmptyDefaultBlock, isMovable, isPreviousBlockADefaultEmptyBlock, hasSelectedInnerBlock, + isParentOfSelectedBlock, } = this.props; const isHovered = this.state.isHovered && ! isMultiSelecting; const { name: blockName, isValid } = block; @@ -385,13 +386,14 @@ export class BlockListBlock extends Component { // Empty paragraph blocks should always show up as unselected. const showEmptyBlockSideInserter = ( isSelected || isHovered ) && isEmptyDefaultBlock; const showSideInserter = ( isSelected || isHovered ) && isEmptyDefaultBlock; - const shouldAppearSelected = ! showSideInserter && isSelected && ! isTypingWithinBlock; - const shouldAppearSelectedParent = ! showSideInserter && hasSelectedInnerBlock && ! isTypingWithinBlock; + const shouldAppearSelected = ! isFocusMode && ! hasFixedToolbar && ! showSideInserter && isSelected && ! isTypingWithinBlock; + const shouldAppearSelectedParent = ! isFocusMode && ! hasFixedToolbar && ! showSideInserter && hasSelectedInnerBlock && ! isTypingWithinBlock; + const shouldAppearHovered = ! isFocusMode && ! hasFixedToolbar && isHovered && ! isEmptyDefaultBlock; // We render block movers and block settings to keep them tabbale even if hidden - const shouldRenderMovers = ( isSelected || hoverArea === 'left' ) && ! showEmptyBlockSideInserter && ! isMultiSelecting && ! isPartOfMultiSelection && ! isTypingWithinBlock; + const shouldRenderMovers = ! isFocusMode && ( isSelected || hoverArea === 'left' ) && ! showEmptyBlockSideInserter && ! isMultiSelecting && ! isPartOfMultiSelection && ! isTypingWithinBlock; const shouldRenderBlockSettings = ( isSelected || hoverArea === 'right' ) && ! isMultiSelecting && ! isPartOfMultiSelection; - const shouldShowBreadcrumb = isHovered && ! isEmptyDefaultBlock; - const shouldShowContextualToolbar = ! showSideInserter && ( ( isSelected && ! isTypingWithinBlock && isValid ) || isFirstMultiSelected ) && ( ! hasFixedToolbar || ! isLargeViewport ); + const shouldShowBreadcrumb = ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + const shouldShowContextualToolbar = ! hasFixedToolbar && ! showSideInserter && ( ( isSelected && ! isTypingWithinBlock && isValid ) || isFirstMultiSelected ); const shouldShowMobileToolbar = shouldAppearSelected; const { error, dragging } = this.state; @@ -407,10 +409,12 @@ export class BlockListBlock extends Component { 'is-selected': shouldAppearSelected, 'is-multi-selected': isPartOfMultiSelection, 'is-selected-parent': shouldAppearSelectedParent, - 'is-hovered': isHovered && ! isEmptyDefaultBlock, + 'is-hovered': shouldAppearHovered, 'is-reusable': isReusableBlock( blockType ), 'is-hidden': dragging, 'is-typing': isTypingWithinBlock, + 'is-focused': isFocusMode && ( isSelected || isParentOfSelectedBlock ), + 'is-focus-mode': isFocusMode, } ); const { onReplace } = this.props; @@ -584,7 +588,7 @@ export class BlockListBlock extends Component { } } -const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => { +const applyWithSelect = withSelect( ( select, { clientId, rootClientId, isLargeViewport } ) => { const { isBlockSelected, getPreviousBlockClientId, @@ -605,19 +609,19 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => { getTemplateLock, } = select( 'core/editor' ); const isSelected = isBlockSelected( clientId ); - const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId ); - const { hasFixedToolbar } = getEditorSettings(); + const { hasFixedToolbar, focusMode } = getEditorSettings(); const block = getBlock( clientId ); const previousBlockClientId = getPreviousBlockClientId( clientId ); const previousBlock = getBlock( previousBlockClientId ); const templateLock = getTemplateLock( rootClientId ); + const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); return { nextBlockClientId: getNextBlockClientId( clientId ), isPartOfMultiSelection: isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ), isFirstMultiSelected: isFirstMultiSelectedBlock( clientId ), isMultiSelecting: isMultiSelecting(), - hasSelectedInnerBlock: isParentOfSelectedBlock, + hasSelectedInnerBlock: hasSelectedInnerBlock( clientId, false ), // We only care about this prop when the block is selected // Thus to avoid unnecessary rerenders we avoid updating the prop if the block is not selected. isTypingWithinBlock: ( isSelected || isParentOfSelectedBlock ) && isTyping(), @@ -630,10 +634,12 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => { isPreviousBlockADefaultEmptyBlock: previousBlock && isUnmodifiedDefaultBlock( previousBlock ), isMovable: 'all' !== templateLock, isLocked: !! templateLock, + isFocusMode: focusMode && isLargeViewport, + hasFixedToolbar: hasFixedToolbar && isLargeViewport, previousBlockClientId, block, isSelected, - hasFixedToolbar, + isParentOfSelectedBlock, }; } ); @@ -689,9 +695,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps ) => { } ); export default compose( + withViewportMatch( { isLargeViewport: 'medium' } ), applyWithSelect, applyWithDispatch, - withViewportMatch( { isLargeViewport: 'medium' } ), withFilters( 'editor.BlockListBlock' ), withHoverAreas, )( BlockListBlock ); diff --git a/packages/editor/src/components/block-list/breadcrumb.js b/packages/editor/src/components/block-list/breadcrumb.js index ca14b1d346c94..2699fb2281161 100644 --- a/packages/editor/src/components/block-list/breadcrumb.js +++ b/packages/editor/src/components/block-list/breadcrumb.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -48,10 +53,12 @@ export class BlockBreadcrumb extends Component { } render() { - const { clientId, rootClientId } = this.props; + const { clientId, rootClientId, isLight } = this.props; return ( -
+
{ rootClientId && ( @@ -68,11 +75,12 @@ export class BlockBreadcrumb extends Component { export default compose( [ withSelect( ( select, ownProps ) => { - const { getBlockRootClientId } = select( 'core/editor' ); + const { getBlockRootClientId, getEditorSettings } = select( 'core/editor' ); const { clientId } = ownProps; return { rootClientId: getBlockRootClientId( clientId ), + isLight: getEditorSettings().hasFixedToolbar, }; } ), ] )( BlockBreadcrumb ); diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index c7b82bd04c1e8..f082c5d255c69 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -101,6 +101,16 @@ .editor-block-list__block-edit .reusable-block-edit-panel * { z-index: z-index(".editor-block-list__block-edit .reusable-block-edit-panel *"); } + + &.is-focus-mode:not(.is-multi-selected) { + opacity: 0.5; + transition: opacity 0.1s linear; + + &:not(.is-focused) .editor-block-list__block, + &.is-focused { + opacity: 1; + } + } } @@ -852,7 +862,6 @@ .components-toolbar { border-top: none; border-bottom: none; - } @include break-small() { @@ -897,6 +906,10 @@ } } +.editor-block-list__block.is-focus-mode:not(.is-multi-selected) > .editor-block-contextual-toolbar { + margin-left: -$block-side-ui-width; +} + // Enable toolbar footprint collapsing .editor-block-contextual-toolbar { // Position the contextual toolbar above the block. @@ -1008,6 +1021,11 @@ @include fade_in(60ms, 0.5s); } } + + &.is-light .components-toolbar { + background: rgba($white, 0.5); + color: $dark-gray-700; + } } .editor-block-list__descendant-arrow::before { diff --git a/packages/editor/src/components/post-permalink/style.scss b/packages/editor/src/components/post-permalink/style.scss index dfe7b7270f9db..bb94a759def88 100644 --- a/packages/editor/src/components/post-permalink/style.scss +++ b/packages/editor/src/components/post-permalink/style.scss @@ -10,7 +10,6 @@ // Use opacity to work in various editor styles. border: $border-width solid $dark-opacity-light-500; - border-bottom: none; background-clip: padding-box; // Put toolbar snugly to edge on mobile. diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index af836f6c89de3..8393c2d4d67ae 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -88,9 +88,13 @@ class PostTitle extends Component { } render() { - const { title, placeholder, instanceId, isPostTypeViewable } = this.props; + const { title, placeholder, instanceId, isPostTypeViewable, isFocusMode, hasFixedToolbar } = this.props; const { isSelected } = this.state; - const className = classnames( 'editor-post-title__block', { 'is-selected': isSelected } ); + const className = classnames( 'editor-post-title__block', { + 'is-selected': isSelected, + 'is-focus-mode': isFocusMode, + 'has-fixed-toolbar': hasFixedToolbar, + } ); const decodedPlaceholder = decodeEntities( placeholder ); return ( @@ -129,12 +133,14 @@ const applyWithSelect = withSelect( ( select ) => { const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); - const { titlePlaceholder } = getEditorSettings(); + const { titlePlaceholder, focusMode, hasFixedToolbar } = getEditorSettings(); return { title: getEditedPostAttribute( 'title' ), isPostTypeViewable: get( postType, [ 'viewable' ], false ), placeholder: titlePlaceholder, + isFocusMode: focusMode, + hasFixedToolbar, }; } ); diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index 35536b6b41fa0..eebb13bf32868 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -37,17 +37,28 @@ font-weight: 600; } - &.is-selected .editor-post-title__input { - // use opacity to work in various editor styles - border-color: $dark-opacity-light-500; + &:not(.is-focus-mode):not(.has-fixed-toolbar) { + &.is-selected .editor-post-title__input { + // use opacity to work in various editor styles + border-color: $dark-opacity-light-500; - .is-dark-theme & { - border-color: $light-opacity-light-500; + .is-dark-theme & { + border-color: $light-opacity-light-500; + } + } + + .editor-post-title__input:hover { + border-color: theme(outlines); } } - .editor-post-title__input:hover { - border-color: theme(outlines); + &.is-focus-mode .editor-post-title__input { + opacity: 0.5; + transition: opacity 0.1s linear; + + &:focus { + opacity: 1; + } } } @@ -55,7 +66,7 @@ font-size: $default-font-size; color: $dark-gray-900; position: absolute; - top: -$block-toolbar-height + $border-width + $border-width; // Shift this element upward the same height as the block toolbar, minus the border size + top: -$block-toolbar-height + $border-width + $border-width + 1px; // Shift this element upward the same height as the block toolbar, minus the border size left: 0; right: 0; diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index 56a7db862be1c..bc4be6692059b 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -16,6 +16,7 @@ export const PREFERENCES_DEFAULTS = { * maxWidth number Max width to constraint resizing * blockTypes boolean|Array Allowed block types * hasFixedToolbar boolean Whether or not the editor toolbar is fixed + * focusMode boolean Whether the focus mode is enabled or not */ export const EDITOR_SETTINGS_DEFAULTS = { alignWide: false, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index a1edb8e88149a..efe607f96075c 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1101,15 +1101,19 @@ export function isBlockSelected( state, clientId ) { /** * Returns true if one of the block's inner blocks is selected. * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. + * @param {Object} state Editor state. + * @param {string} clientId Block client ID. + * @param {boolean} deep Perform a deep check. * * @return {boolean} Whether the block as an inner block selected */ -export function hasSelectedInnerBlock( state, clientId ) { +export function hasSelectedInnerBlock( state, clientId, deep = false ) { return some( getBlockOrder( state, clientId ), - ( innerClientId ) => isBlockSelected( state, innerClientId ) + ( innerClientId ) => ( + isBlockSelected( state, innerClientId ) || + ( deep && hasSelectedInnerBlock( state, innerClientId, deep ) ) + ) ); } diff --git a/test/e2e/specs/block-deletion.test.js b/test/e2e/specs/block-deletion.test.js index 8cd8b1c5fbbc1..d2d70eb6de337 100644 --- a/test/e2e/specs/block-deletion.test.js +++ b/test/e2e/specs/block-deletion.test.js @@ -42,7 +42,7 @@ describe( 'block deletion -', () => { describe( 'deleting the third block using the Remove Block shortcut', () => { it( 'results in two remaining blocks and positions the caret at the end of the second block', async () => { - await pressWithModifier( [ 'Shift', META_KEY ], 'x' ); + await pressWithModifier( [ 'Shift', META_KEY ], 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); // Type additional text and assert that caret position is correct by comparing to snapshot. diff --git a/test/e2e/specs/links.test.js b/test/e2e/specs/links.test.js index 7dd10ff5d664a..0968805fbd49d 100644 --- a/test/e2e/specs/links.test.js +++ b/test/e2e/specs/links.test.js @@ -195,9 +195,9 @@ describe( 'Links', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - const setFixedToolbar = async ( b ) => { + const toggleFixedToolbar = async ( b ) => { await page.click( '.edit-post-more-menu button' ); - const button = ( await page.$x( "//button[contains(text(), 'Fix Toolbar to Top')]" ) )[ 0 ]; + const button = ( await page.$x( "//button[contains(text(), 'Unified Toolbar')]" ) )[ 0 ]; const buttonClassNameProperty = await button.getProperty( 'className' ); const buttonClassName = await buttonClassNameProperty.jsonValue(); const isSelected = buttonClassName.indexOf( 'is-selected' ) !== -1; @@ -208,8 +208,8 @@ describe( 'Links', () => { } }; - it( 'allows Left to be pressed during creation in "Fixed to Toolbar" mode', async () => { - await setFixedToolbar( true ); + it( 'allows Left to be pressed during creation when the toolbar is fixed to top', async () => { + await toggleFixedToolbar( true ); await clickBlockAppender(); await page.keyboard.type( 'Text' ); @@ -227,7 +227,7 @@ describe( 'Links', () => { } ); it( 'allows Left to be pressed during creation in "Docked Toolbar" mode', async () => { - await setFixedToolbar( false ); + await toggleFixedToolbar( false ); await clickBlockAppender(); await page.keyboard.type( 'Text' );