diff --git a/edit-post/assets/stylesheets/_z-index.scss b/edit-post/assets/stylesheets/_z-index.scss index 48ae17cbffa329..9f44e1dcb1564e 100644 --- a/edit-post/assets/stylesheets/_z-index.scss +++ b/edit-post/assets/stylesheets/_z-index.scss @@ -33,9 +33,6 @@ $z-layers: ( // Active pill button ".components-button.is-button {:focus or .is-primary}": 1, - // Should have lower index than anything else positioned inside the block containers - ".editor-block-list__block-draggable": 0, - // The draggable element should show up above the entire UI ".components-draggable__clone": 1000000000, diff --git a/packages/editor/src/components/block-draggable/index.js b/packages/editor/src/components/block-draggable/index.js new file mode 100644 index 00000000000000..7b5ec2ac74912b --- /dev/null +++ b/packages/editor/src/components/block-draggable/index.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +import { Draggable } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; + +const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, index, layout, onDragStart, onDragEnd } ) => { + const transferData = { + type: 'block', + fromIndex: index, + rootClientId, + clientId, + layout, + }; + + return ( + + { + ( { onDraggableStart, onDraggableEnd } ) => { + return children( { + onDraggableStart: onDraggableStart, + onDraggableEnd: onDraggableEnd, + } ); + } + } + + ); +}; + +export default withSelect( ( select, { clientId } ) => { + const { getBlockIndex, getBlockRootClientId } = select( 'core/editor' ); + return { + index: getBlockIndex( clientId ), + rootClientId: getBlockRootClientId( clientId ), + }; +} )( BlockDraggable ); diff --git a/packages/editor/src/components/block-list/block-draggable.js b/packages/editor/src/components/block-list/block-draggable.js deleted file mode 100644 index 0c8caa7410c081..00000000000000 --- a/packages/editor/src/components/block-list/block-draggable.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { Draggable } from '@wordpress/components'; - -const BlockDraggable = ( { clientId, rootClientId, blockElementId, layout, order, isDragging, onDragStart, onDragEnd } ) => { - const className = classnames( 'editor-block-list__block-draggable', { - 'is-visible': isDragging, - } ); - - const transferData = { - type: 'block', - fromIndex: order, - rootClientId, - clientId, - layout, - }; - - return ( - - { - ( { onDraggableStart, onDraggableEnd } ) => ( -
-
-
) - } -
- ); -}; - -export default BlockDraggable; diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js index 2ff6c3dc272cf8..e417996f4e01a7 100644 --- a/packages/editor/src/components/block-list/block.js +++ b/packages/editor/src/components/block-list/block.js @@ -43,7 +43,6 @@ import BlockContextualToolbar from './block-contextual-toolbar'; import BlockMultiControls from './multi-controls'; import BlockMobileToolbar from './block-mobile-toolbar'; import BlockInsertionPoint from './insertion-point'; -import BlockDraggable from './block-draggable'; import IgnoreNestedEvents from './ignore-nested-events'; import InserterWithShortcuts from '../inserter-with-shortcuts'; import Inserter from '../inserter'; @@ -373,6 +372,7 @@ export class BlockListBlock extends Component { hasSelectedInnerBlock, isParentOfSelectedBlock, hasMultiSelection, + isDraggable, } = this.props; const isHovered = this.state.isHovered && ! isMultiSelecting; const { name: blockName, isValid } = block; @@ -410,7 +410,7 @@ export class BlockListBlock extends Component { 'is-selected-parent': shouldAppearSelectedParent, 'is-hovered': shouldAppearHovered, 'is-reusable': isReusableBlock( blockType ), - 'is-hidden': dragging, + 'is-dragging': dragging, 'is-typing': isTypingWithinBlock, 'is-focused': isFocusMode && ( isSelected || isParentOfSelectedBlock ), 'is-focus-mode': isFocusMode, @@ -480,18 +480,6 @@ export class BlockListBlock extends Component { ] } { ...wrapperProps } > - { ! isPartOfMultiSelection && isMovable && ( - - ) } { shouldShowInsertionPoint && ( ) } { shouldShowBreadcrumb && ( diff --git a/packages/editor/src/components/block-list/index.js b/packages/editor/src/components/block-list/index.js index a1f69889e0c00b..5164e9e5a808ec 100644 --- a/packages/editor/src/components/block-list/index.js +++ b/packages/editor/src/components/block-list/index.js @@ -196,6 +196,7 @@ class BlockList extends Component { isGroupedByLayout, rootClientId, canInsertDefaultBlock, + isDraggable, } = this.props; let defaultLayout; @@ -221,6 +222,7 @@ class BlockList extends Component { layout={ defaultLayout } isFirst={ blockIndex === 0 } isLast={ blockIndex === blockClientIds.length - 1 } + isDraggable={ isDraggable } /> ) ) } { canInsertDefaultBlock && ( diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss index f8ba61e2074e69..6afce0fcbc3858 100644 --- a/packages/editor/src/components/block-list/style.scss +++ b/packages/editor/src/components/block-list/style.scss @@ -1,104 +1,35 @@ .editor-block-list__layout .components-draggable__clone { - & > .editor-block-list__block > .editor-block-list__block-draggable { - background: $white; // @todo: ensure this works with themes that invert the color - box-shadow: $shadow-popover; - - @include break-small { - left: -$block-parent-side-ui-clearance - $border-width; - right: -$block-parent-side-ui-clearance - $border-width; - - .editor-block-list__layout & { - left: -$border-width; - right: -$border-width; - } - } - } - // Hide the Block UI when dragging the block // This ensures the page scroll properly (no sticky elements) .editor-block-contextual-toolbar, - .editor-block-mover, .editor-block-settings-menu { // I think important is fine here to avoid over complexing the selector display: none !important; } } -.editor-block-list__layout .editor-block-list__block-draggable { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: z-index(".editor-block-list__block-draggable"); - - > .editor-block-list__block-draggable-inner { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - visibility: hidden; - - // Use opacity to work in various editor styles. - background-color: $dark-opacity-light-200; - - .is-dark-theme & { - background-color: $light-opacity-light-200; +.editor-block-list__layout .editor-block-list__block.is-selected { // Needs specificity to override inherited styles. + // While block is being dragged, dim the slot dragged from, and hide some UI. + &.is-dragging { + .editor-block-list__block-edit::before { + outline: none; } - @include break-small { - margin: 0 48px; + > .editor-block-list__block-edit > * { + background: $light-gray-100; } - } - - &.is-visible > .editor-block-list__block-draggable-inner { - visibility: visible; - } - @include break-small { - // use a wider available space for hovering/selecting/dragging on top level blocks - left: -$parent-block-padding - $block-padding; - right: -$parent-block-padding - $block-padding; - - // use smaller space for hovering/selecting/dragging on child blocks - .editor-block-list__layout & { - left: 0; - right: 0; + > .editor-block-list__block-edit > * > * { + visibility: hidden; } - // Full width blocks don't have the place to expand on the side - .editor-block-list__block[data-align="full"] & { - left: 0; - right: 0; + .editor-block-mover, + .editor-block-contextual-toolbar { + display: none; } } - - cursor: move; // Fallback for IE/Edge < 14 - cursor: grab; -} - - -// Allow Drag & Drop when clicking on the empty area of the mover and the settings menu -.editor-block-list__layout .editor-block-list__block .editor-block-mover, -.editor-block-list__layout .editor-block-list__block .editor-block-settings-menu { - pointer-events: none; - - // Nested blocks don't have any side affordance for drag and drop - .editor-block-list__layout &, - > * { - pointer-events: auto; - } -} - -.editor-block-list__block { - &.is-hidden *, - &.is-hidden > * { - visibility: hidden; - } - - .editor-block-list__block-edit .reusable-block-edit-panel * { + > .editor-block-list__block-edit .reusable-block-edit-panel * { z-index: z-index(".editor-block-list__block-edit .reusable-block-edit-panel *"); } @@ -438,12 +369,6 @@ float: left; } - // There is no side UI clearance on full-wide elements, so they are simply not draggable on the sides - > .editor-block-list__block-draggable { - left: 0; - right: 0; - } - // Position hover label on the right > .editor-block-list__breadcrumb { right: -$border-width; diff --git a/packages/editor/src/components/block-mover/drag-handle.js b/packages/editor/src/components/block-mover/drag-handle.js new file mode 100644 index 00000000000000..9597a6ef049743 --- /dev/null +++ b/packages/editor/src/components/block-mover/drag-handle.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import BlockDraggable from '../block-draggable'; + +export const IconDragHandle = ( { isVisible, className, icon, onDragStart, onDragEnd, blockElementId, clientId, layout } ) => { + if ( ! isVisible ) { + return null; + } + + const dragHandleClassNames = classnames( 'editor-block-mover__control-drag-handle', className ); + + return ( + + { + ( { onDraggableStart, onDraggableEnd } ) => ( + + ) } + + ); +}; diff --git a/packages/editor/src/components/block-mover/arrows.js b/packages/editor/src/components/block-mover/icons.js similarity index 51% rename from packages/editor/src/components/block-mover/arrows.js rename to packages/editor/src/components/block-mover/icons.js index 8b4438bce0ac18..d23ec6cc153897 100644 --- a/packages/editor/src/components/block-mover/arrows.js +++ b/packages/editor/src/components/block-mover/icons.js @@ -14,3 +14,11 @@ export const downArrow = ( ); + +export const dragHandle = ( + + + +); diff --git a/packages/editor/src/components/block-mover/index.js b/packages/editor/src/components/block-mover/index.js index d28d131cbdfc6c..b431c71c3e7c9f 100644 --- a/packages/editor/src/components/block-mover/index.js +++ b/packages/editor/src/components/block-mover/index.js @@ -18,7 +18,8 @@ import { withInstanceId, compose } from '@wordpress/compose'; * Internal dependencies */ import { getBlockMoverDescription } from './mover-description'; -import { upArrow, downArrow } from './arrows'; +import { upArrow, downArrow, dragHandle } from './icons'; +import { IconDragHandle } from './drag-handle'; export class BlockMover extends Component { constructor() { @@ -43,7 +44,7 @@ export class BlockMover extends Component { } render() { - const { onMoveUp, onMoveDown, isFirst, isLast, clientIds, blockType, firstIndex, isLocked, instanceId, isHidden } = this.props; + const { onMoveUp, onMoveDown, isFirst, isLast, isDraggable, onDragStart, onDragEnd, clientIds, blockElementId, layout, blockType, firstIndex, isLocked, instanceId, isHidden } = this.props; const { isFocused } = this.state; const blocksCount = castArray( clientIds ).length; if ( isLocked || ( isFirst && isLast ) ) { @@ -66,6 +67,16 @@ export class BlockMover extends Component { onFocus={ this.onFocus } onBlur={ this.onBlur } /> + { - const { getBlock, getBlockIndex, getTemplateLock } = select( 'core/editor' ); + withSelect( ( select, { clientIds } ) => { + const { getBlock, getBlockIndex, getTemplateLock, getBlockRootClientId } = select( 'core/editor' ); const firstClientId = first( castArray( clientIds ) ); const block = getBlock( firstClientId ); + const rootClientId = getBlockRootClientId( first( castArray( clientIds ) ) ); return { firstIndex: getBlockIndex( firstClientId, rootClientId ), blockType: block ? getBlockType( block.name ) : null, isLocked: getTemplateLock( rootClientId ) === 'all', + rootClientId, }; } ), withDispatch( ( dispatch, { clientIds, rootClientId } ) => { diff --git a/packages/editor/src/components/block-mover/style.scss b/packages/editor/src/components/block-mover/style.scss index c9bb11ba20298a..9c9b8148202592 100644 --- a/packages/editor/src/components/block-mover/style.scss +++ b/packages/editor/src/components/block-mover/style.scss @@ -6,9 +6,19 @@ &.is-visible { @include fade_in; } + + // 24px is the smallest size of a good pressable button. + // With 3 pieces of side UI, that comes to a total of 72px. + // To vertically center against a 56px paragraph, move upwards 72px - 56px / 2. + // Don't do this for wide, fullwide, or mobile. + @include break-small() { + .editor-block-list__block:not([data-align="wide"]):not([data-align="full"]) & { + margin-top: -8px; + } + } } -// Mover icon buttons +// Mover icon buttons. .editor-block-mover__control { display: flex; align-items: center; @@ -18,11 +28,19 @@ background: none; cursor: pointer; padding: 0; - width: $block-side-ui-width; - height: $block-side-ui-width; // the side UI can be no taller than 2 * $block-side-ui-width, which matches the height of a line of text border-radius: $radius-round-rectangle; - // use opacity to work in various editor styles + // Carefully adjust the size of the side UI to fit one paragraph of text (56px). + width: $block-side-ui-width; + height: $icon-button-size-small; + + svg { + width: $block-side-ui-width; + height: $icon-button-size-small; + padding: #{ ($block-side-ui-width - $icon-button-size-small) / 2 } #{ ($block-side-ui-width - 18px) / 2 }; // This makes the SVG fill the whole available area, without scaling the artwork. + } + + // Use opacity to work in various editor styles color: $dark-opacity-300; .is-dark-theme & { @@ -32,20 +50,14 @@ &[aria-disabled="true"] { cursor: default; pointer-events: none; - color: $dark-opacity-light-300; // use opacity to work in various editor styles + color: $dark-opacity-light-300; // Use opacity to work in various editor styles. .is-dark-theme & { color: $light-opacity-light-300; } } - svg { - width: $block-side-ui-width; - height: $block-side-ui-width; - padding: ($block-side-ui-width - 18px) / 2; // this makes the SVG fill the whole available area, without scaling the artwork - } - - // Apply a background in nested contexts, only on desktop + // Apply a background in nested contexts, only on desktop. @include break-small() { .editor-block-list__layout .editor-block-list__layout & { background: $white; @@ -78,6 +90,30 @@ } } +.editor-block-mover__control-drag-handle { + cursor: move; // Fallback for IE/Edge < 14 + cursor: grab; + + &, + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover, + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):active, + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):focus { + box-shadow: none; + background: none; + + // Use opacity to work in various editor styles. + color: $dark-opacity-500; + + .is-dark-theme & { + color: $light-opacity-500; + } + } + + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):active { + cursor: grabbing; + } +} + .editor-block-mover__description { display: none; } diff --git a/packages/editor/src/components/block-mover/test/index.js b/packages/editor/src/components/block-mover/test/index.js index e22973b71f78f8..ea13280f4d3117 100644 --- a/packages/editor/src/components/block-mover/test/index.js +++ b/packages/editor/src/components/block-mover/test/index.js @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; * Internal dependencies */ import { BlockMover } from '../'; -import { upArrow, downArrow } from '../arrows'; +import { upArrow, downArrow, dragHandle } from '../icons'; describe( 'BlockMover', () => { describe( 'basic rendering', () => { @@ -22,7 +22,7 @@ describe( 'BlockMover', () => { expect( wrapper.type() ).toBe( null ); } ); - it( 'should render two IconButton components with the following props', () => { + it( 'should render three icons with the following props', () => { const blockMover = shallow( { expect( blockMover.hasClass( 'editor-block-mover' ) ).toBe( true ); const moveUp = blockMover.childAt( 0 ); - const moveDown = blockMover.childAt( 1 ); - const moveUpDesc = blockMover.childAt( 2 ); - const moveDownDesc = blockMover.childAt( 3 ); + const drag = blockMover.childAt( 1 ); + const moveDown = blockMover.childAt( 2 ); + const moveUpDesc = blockMover.childAt( 3 ); + const moveDownDesc = blockMover.childAt( 4 ); expect( moveUp.type().name ).toBe( 'IconButton' ); + expect( drag.type().name ).toBe( 'IconDragHandle' ); expect( moveDown.type().name ).toBe( 'IconButton' ); expect( moveUp.props() ).toMatchObject( { className: 'editor-block-mover__control', @@ -46,6 +48,10 @@ describe( 'BlockMover', () => { 'aria-disabled': undefined, 'aria-describedby': 'editor-block-mover__up-description-1', } ); + expect( drag.props() ).toMatchObject( { + className: 'editor-block-mover__control', + icon: dragHandle, + } ); expect( moveDown.props() ).toMatchObject( { className: 'editor-block-mover__control', onClick: undefined, @@ -72,6 +78,23 @@ describe( 'BlockMover', () => { expect( moveUp.prop( 'onClick' ) ).toBe( onMoveUp ); } ); + it( 'should render the drag handle with onDragStart and onDragEnd callback', () => { + const onDragStart = ( event ) => event; + const onDragEnd = ( event ) => event; + const blockMover = shallow( + + ); + const dragHandler = blockMover.childAt( 1 ); + expect( dragHandler.prop( 'onDragStart' ) ).toBe( onDragStart ); + expect( dragHandler.prop( 'onDragEnd' ) ).toBe( onDragEnd ); + } ); + it( 'should render the down arrow with a onMoveDown callback', () => { const onMoveDown = ( event ) => event; const blockMover = shallow( @@ -82,26 +105,21 @@ describe( 'BlockMover', () => { firstIndex={ 0 } /> ); - const moveDown = blockMover.childAt( 1 ); + const moveDown = blockMover.childAt( 2 ); expect( moveDown.prop( 'onClick' ) ).toBe( onMoveDown ); } ); - it( 'should render with a disabled up arrow when the block isFirst', () => { - const onMoveUp = ( event ) => event; + it( 'should not render the drag handle if block is not draggable', () => { const blockMover = shallow( ); - const moveUp = blockMover.childAt( 0 ); - expect( moveUp.props() ).toMatchObject( { - onClick: null, - 'aria-disabled': true, - } ); + const dragHandler = blockMover.childAt( 1 ); + expect( dragHandler.type().name ).toBe( 'IconDragHandle' ); + expect( dragHandler.prop( 'isVisible' ) ).toBe( false ); } ); it( 'should render with a disabled down arrow when the block isLast', () => { @@ -115,7 +133,7 @@ describe( 'BlockMover', () => { firstIndex={ 0 } /> ); - const moveDown = blockMover.childAt( 1 ); + const moveDown = blockMover.childAt( 2 ); expect( moveDown.props() ).toMatchObject( { onClick: null, 'aria-disabled': true,