From cfbce355c2c4231d604015c8d398f1e12572df24 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Fri, 29 May 2020 12:22:17 +0100 Subject: [PATCH] Add: Block areas tabbed sidebar to the widgets screen (#22467) --- package-lock.json | 1 + .../block-list/block-contextual-toolbar.js | 20 +++ .../src/components/block-toolbar/index.js | 14 ++ .../block-library/src/widget-area/block.json | 3 +- packages/edit-widgets/package.json | 1 + .../src/components/sidebar/block-areas.js | 81 +++++++++++ .../src/components/sidebar/index.js | 129 +++++++++++++++++- .../src/components/sidebar/style.scss | 114 ++++++++++++++++ packages/edit-widgets/src/style.scss | 1 + 9 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 packages/edit-widgets/src/components/sidebar/block-areas.js create mode 100644 packages/edit-widgets/src/components/sidebar/style.scss diff --git a/package-lock.json b/package-lock.json index daa1b64944e837..de73f17229c637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10302,6 +10302,7 @@ "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", + "classnames": "^2.2.5", "lodash": "^4.17.15", "rememo": "^3.0.0" } diff --git a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js index ee8e952b51de59..839cb84e87769d 100644 --- a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js @@ -2,6 +2,8 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { hasBlockSupport } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -10,6 +12,24 @@ import NavigableToolbar from '../navigable-toolbar'; import { BlockToolbar } from '../'; function BlockContextualToolbar( { focusOnMount, ...props } ) { + const { blockType } = useSelect( ( select ) => { + const { getBlockName, getSelectedBlockClientIds } = select( + 'core/block-editor' + ); + const { getBlockType } = select( 'core/blocks' ); + const selectedBlockClientIds = getSelectedBlockClientIds(); + const selectedBlockClientId = selectedBlockClientIds[ 0 ]; + return { + blockType: + selectedBlockClientId && + getBlockType( getBlockName( selectedBlockClientId ) ), + }; + } ); + if ( blockType ) { + if ( ! hasBlockSupport( blockType, '__experimentalToolbar', true ) ) { + return null; + } + } return (
{ + const { getBlockType } = select( 'core/blocks' ); const { + getBlockName, getBlockMode, getSelectedBlockClientIds, isBlockValid, @@ -46,6 +51,9 @@ export default function BlockToolbar( { hideDragHandle } ) { return { blockClientIds: selectedBlockClientIds, blockClientId: selectedBlockClientId, + blockType: + selectedBlockClientId && + getBlockType( getBlockName( selectedBlockClientId ) ), hasFixedToolbar: getSettings().hasFixedToolbar, rootClientId: blockRootClientId, isValid: @@ -73,6 +81,12 @@ export default function BlockToolbar( { hideDragHandle } ) { const displayHeaderToolbar = useViewportMatch( 'medium', '<' ) || hasFixedToolbar; + if ( blockType ) { + if ( ! hasBlockSupport( blockType, '__experimentalToolbar', true ) ) { + return null; + } + } + const shouldShowMovers = displayHeaderToolbar || showMovers; if ( blockClientIds.length === 0 ) { diff --git a/packages/block-library/src/widget-area/block.json b/packages/block-library/src/widget-area/block.json index f3b5535f161b3b..fd3c912908bbe2 100644 --- a/packages/block-library/src/widget-area/block.json +++ b/packages/block-library/src/widget-area/block.json @@ -12,6 +12,7 @@ "supports": { "html": false, "inserter": false, - "customClassName": false + "customClassName": false, + "__experimentalToolbar": false } } diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 22047f0f332ed0..bd9f17ea69669e 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -43,6 +43,7 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", + "classnames": "^2.2.5", "lodash": "^4.17.15", "rememo": "^3.0.0" }, diff --git a/packages/edit-widgets/src/components/sidebar/block-areas.js b/packages/edit-widgets/src/components/sidebar/block-areas.js new file mode 100644 index 00000000000000..3541ebb77ed53c --- /dev/null +++ b/packages/edit-widgets/src/components/sidebar/block-areas.js @@ -0,0 +1,81 @@ +/** + * WordPress dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { blockDefault } from '@wordpress/icons'; +import { BlockIcon } from '@wordpress/block-editor'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function BlockArea( { clientId } ) { + const { name, selectedBlock } = useSelect( + ( select ) => { + const { getBlockAttributes, getBlockSelectionStart } = select( + 'core/block-editor' + ); + return { + name: getBlockAttributes( clientId ).name, + selectedBlock: getBlockSelectionStart(), + }; + }, + [ clientId ] + ); + const { selectBlock } = useDispatch( 'core/block-editor' ); + const isSelected = selectedBlock === clientId; + return ( +
  • + +
  • + ); +} + +export default function BlockAreas() { + const blockOrder = useSelect( ( select ) => { + return select( 'core/block-editor' ).getBlockOrder(); + } ); + const hasBlockAreas = blockOrder.length > 0; + return ( + <> +
    +
    + +
    +

    + { __( + 'Block Areas (also known as "Widget Areas") are global parts in your site\'s layout that can accept blocks. These vary by theme, but are typically parts like your Sidebar or Footer.' + ) } +

    + { ! hasBlockAreas && ( +

    + { __( + 'Your theme does not contain block areas.' + ) } +

    + ) } +
    +
    + { hasBlockAreas && ( +
      + { blockOrder.map( ( clientId ) => ( + + ) ) } +
    + ) } +
    + + ); +} diff --git a/packages/edit-widgets/src/components/sidebar/index.js b/packages/edit-widgets/src/components/sidebar/index.js index 581e82005f6679..87c0096e85589d 100644 --- a/packages/edit-widgets/src/components/sidebar/index.js +++ b/packages/edit-widgets/src/components/sidebar/index.js @@ -1,22 +1,141 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { useEffect } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; import { ComplementaryArea } from '@wordpress/interface'; import { BlockInspector } from '@wordpress/block-editor'; import { cog } from '@wordpress/icons'; +import { Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import BlockAreas from './block-areas'; + +const CORE_WIDGET_COMPLEMENTARY_AREAS = { + 'edit-widgets/block-areas': __( 'Block areas' ), + 'edit-widgets/block-inspector': __( 'Block' ), +}; + +function ComplementaryAreaHeader( { activeComplementaryArea } ) { + const { enableComplementaryArea } = useDispatch( 'core/interface' ); + return ( +
      + { map( CORE_WIDGET_COMPLEMENTARY_AREAS, ( label, identifier ) => { + const isActive = identifier === activeComplementaryArea; + return ( +
    • + +
    • + ); + } ) } +
    + ); +} export default function Sidebar() { + const { enableComplementaryArea } = useDispatch( 'core/interface' ); + const { + currentArea, + hasSelectedNonAreaBlock, + isGeneralSidebarOpen, + } = useSelect( ( select ) => { + let activeArea = select( 'core/interface' ).getActiveComplementaryArea( + 'core/edit-widgets' + ); + const isSidebarOpen = !! activeArea; + const { getBlockSelectionStart, getBlockRootClientId } = select( + 'core/block-editor' + ); + const selectionStart = getBlockSelectionStart(); + if ( ! CORE_WIDGET_COMPLEMENTARY_AREAS[ activeArea ] ) { + if ( ! selectionStart ) { + activeArea = 'edit-widgets/block-areas'; + } else { + activeArea = 'edit-widgets/block-inspector'; + } + } + return { + currentArea: activeArea, + hasSelectedNonAreaBlock: !! ( + selectionStart && getBlockRootClientId( selectionStart ) + ), + isGeneralSidebarOpen: isSidebarOpen, + }; + }, [] ); + + // currentArea, and isGeneralSidebarOpen are intentionally left out from the dependencies, + // because we want to run the effect when a block is selected/unselected and not when the sidebar state changes. + useEffect( () => { + if ( + hasSelectedNonAreaBlock && + currentArea === 'edit-widgets/block-areas' && + isGeneralSidebarOpen + ) { + enableComplementaryArea( + 'core/edit-widgets', + 'edit-widgets/block-inspector' + ); + } + if ( + ! hasSelectedNonAreaBlock && + currentArea === 'edit-widgets/block-inspector' && + isGeneralSidebarOpen + ) { + enableComplementaryArea( + 'core/edit-widgets', + 'edit-widgets/block-areas' + ); + } + }, [ hasSelectedNonAreaBlock, enableComplementaryArea ] ); + return ( + } + headerClassName="edit-widgets-sidebar__panel-tabs" scope="core/edit-widgets" - complementaryAreaIdentifier="edit-widgets/block-inspector" - title={ __( 'Block' ) } + identifier={ currentArea } icon={ cog } > - + { currentArea === 'edit-widgets/block-areas' && } + { currentArea === 'edit-widgets/block-inspector' && ( + + ) } ); } diff --git a/packages/edit-widgets/src/components/sidebar/style.scss b/packages/edit-widgets/src/components/sidebar/style.scss new file mode 100644 index 00000000000000..22c52fd5204e48 --- /dev/null +++ b/packages/edit-widgets/src/components/sidebar/style.scss @@ -0,0 +1,114 @@ +.components-panel__header.edit-widgets-sidebar__panel-tabs { + justify-content: flex-start; + padding-left: 0; + padding-right: $grid-unit-05; + border-top: 0; + margin-top: 0; + + ul { + display: flex; + } + li { + margin: 0; + } + .components-button.has-icon { + display: none; + margin-left: auto; + + @include break-medium() { + display: flex; + } + } +} + +.components-button.edit-widgets-sidebar__panel-tab { + border-radius: 0; + height: 50px - $border-width; + background: transparent; + border: none; + box-shadow: none; + cursor: pointer; + display: inline-block; + padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode + margin-left: 0; + font-weight: 400; + color: $dark-gray-900; + + // This pseudo-element "duplicates" the tab label and sets the text to bold. + // This ensures that the tab doesn't change width when selected. + // See: https://github.com/WordPress/gutenberg/pull/9793 + &::after { + content: attr(data-label); + display: block; + font-weight: 600; + height: 0; + overflow: hidden; + speak: none; + visibility: hidden; + } + + &.is-active { + // The transparent shadow ensures no jumpiness when focus animates on an active tab. + box-shadow: inset 0 0 0 $border-width-focus transparent, inset 0 0 -$border-width-tab 0 0 $theme-color; + font-weight: 600; + position: relative; + + // This border appears in Windows High Contrast mode instead of the box-shadow. + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 1px; + right: 0; + left: 0; + border-bottom: $border-width-tab solid transparent; + } + } + + &:focus { + box-shadow: inset 0 0 0 $border-width-focus $theme-color; + } + + &.is-active:focus { + box-shadow: inset 0 0 0 $border-width-focus $theme-color, inset 0 0 -$border-width-tab 0 0 $theme-color; + } +} + +.edit-widgets-block-areas__top-container { + display: flex; + padding: $grid-unit-20; + .block-editor-block-icon { + margin-right: $grid-unit-20; + } +} + +.edit-widgets-block-areas { + ul { + list-style: none; + margin-bottom: -$border-width; + } + + li { + margin: 0; + border-bottom: $border-width solid $light-gray-500; + &:first-child { + border-top: $border-width solid $light-gray-500; + } + + button { + display: block; + width: 100%; + font-weight: 600; + padding: $grid-unit-20; + height: auto; + text-align: left; + } + } +} + +.edit-widgets-block-areas__edit { + float: right; + /* Mimics the default link style in common.css */ + color: #0073aa; + font-weight: 400; +} diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 561fcd16680b7e..89981d7b8786d4 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -2,6 +2,7 @@ @import "./components/customizer-edit-widgets-initializer/style.scss"; @import "./components/header/style.scss"; +@import "./components/sidebar/style.scss"; @import "./components/notices/style.scss"; // In order to use mix-blend-mode, this element needs to have an explicitly set background-color