diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index 0263877b9c567c..05714b83bcda7b 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -277,10 +277,6 @@ _Returns_ Returns an action object signalling that the user closed the sidebar. -_Returns_ - -- `Object`: Action object. - # **closeModal** Returns an action object signalling that the user closed a modal. @@ -325,11 +321,7 @@ Returns an action object used in signalling that the user opened an editor sideb _Parameters_ -- _name_ `string`: Sidebar name to be opened. - -_Returns_ - -- `Object`: Action object. +- _name_ `?string`: Sidebar name to be opened. # **openModal** diff --git a/docs/manifest.json b/docs/manifest.json index b97d08f0d46cd7..31f4e3dd66065e 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1367,6 +1367,12 @@ "markdown_source": "../packages/icons/README.md", "parent": "packages" }, + { + "title": "@wordpress/interface", + "slug": "packages-interface", + "markdown_source": "../packages/interface/README.md", + "parent": "packages" + }, { "title": "@wordpress/is-shallow-equal", "slug": "packages-is-shallow-equal", diff --git a/package-lock.json b/package-lock.json index fb6b38446f5263..e1c73beeb72079 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10945,11 +10945,13 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", + "@wordpress/interface": "file:packages/interface", "@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", @@ -10981,8 +10983,10 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", + "@wordpress/interface": "file:packages/interface", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", + "@wordpress/plugins": "file:packages/plugins", "@wordpress/primitives": "file:packages/primitives", "@wordpress/url": "file:packages/url", "file-saver": "^2.0.2", @@ -11381,6 +11385,20 @@ "@wordpress/primitives": "file:packages/primitives" } }, + "@wordpress/interface": { + "version": "file:packages/interface", + "requires": { + "@babel/runtime": "^7.8.3", + "@wordpress/components": "file:packages/components", + "@wordpress/data": "file:packages/data", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/icons": "file:packages/icons", + "@wordpress/plugins": "file:packages/plugins", + "classnames": "^2.2.5", + "lodash": "^4.17.15" + } + }, "@wordpress/is-shallow-equal": { "version": "file:packages/is-shallow-equal", "requires": { @@ -20574,7 +20592,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -20593,7 +20611,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, diff --git a/package.json b/package.json index fd7f908cd46515..c95106a250cf08 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", "@wordpress/icons": "file:packages/icons", + "@wordpress/interface": "file:packages/interface", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts", "@wordpress/keycodes": "file:packages/keycodes", diff --git a/packages/dependency-extraction-webpack-plugin/util.js b/packages/dependency-extraction-webpack-plugin/util.js index 5185c24682fb6d..e515f752094c63 100644 --- a/packages/dependency-extraction-webpack-plugin/util.js +++ b/packages/dependency-extraction-webpack-plugin/util.js @@ -1,5 +1,5 @@ const WORDPRESS_NAMESPACE = '@wordpress/'; -const BUNDLED_PACKAGES = [ '@wordpress/icons' ]; +const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ]; /** * Default request to global transformation diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap index 8d2ca2c2bd9a78..4286c04f64556c 100644 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap +++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap @@ -2,6 +2,6 @@ exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`; -exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Sidebar title plugin
"`; +exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Sidebar title plugin
"`; -exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Sidebar title plugin
"`; +exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"
(no title)
Sidebar title plugin
"`; diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 641bb89209f185..5a1908b8cc05a9 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -438,10 +438,6 @@ _Parameters_ - _props.isPinnable_ `[boolean]`: Whether to allow to pin sidebar to toolbar. - _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. -_Returns_ - -- `WPComponent`: Plugin sidebar component. - # **PluginSidebarMoreMenuItem** Renders a menu item in `Plugins` group in `More Menu` drop down, diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 8b2d7f6b43bffe..231be138b53166 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -31,11 +31,13 @@ "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", + "@wordpress/data-controls": "file:../data-controls", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 347a673d0ff2f3..7dca19d07f57a5 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -6,6 +6,7 @@ import { Button } from '@wordpress/components'; import { PostPreviewButton, PostSavedState } from '@wordpress/editor'; import { useSelect, useDispatch } from '@wordpress/data'; import { cog } from '@wordpress/icons'; +import { PinnedItems } from '@wordpress/interface'; /** * Internal dependencies @@ -13,7 +14,6 @@ import { cog } from '@wordpress/icons'; import FullscreenModeClose from './fullscreen-mode-close'; import HeaderToolbar from './header-toolbar'; import MoreMenu from './more-menu'; -import PinnedPlugins from './pinned-plugins'; import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; import PreviewOptions from '../preview-options'; @@ -94,7 +94,7 @@ function Header() { aria-expanded={ isEditorSidebarOpened } shortcut={ shortcut } /> - + diff --git a/packages/edit-post/src/components/header/pinned-plugins/index.js b/packages/edit-post/src/components/header/pinned-plugins/index.js deleted file mode 100644 index 35f07ab0678d8b..00000000000000 --- a/packages/edit-post/src/components/header/pinned-plugins/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * External dependencies - */ -import { isEmpty } from 'lodash'; - -/** - * WordPress dependencies - */ -import { createSlotFill } from '@wordpress/components'; - -const { Fill: PinnedPlugins, Slot } = createSlotFill( 'PinnedPlugins' ); - -PinnedPlugins.Slot = ( props ) => ( - - { ( fills ) => - ! isEmpty( fills ) && ( -
{ fills }
- ) - } -
-); - -export default PinnedPlugins; diff --git a/packages/edit-post/src/components/header/pinned-plugins/style.scss b/packages/edit-post/src/components/header/pinned-plugins/style.scss deleted file mode 100644 index f8020c349699ad..00000000000000 --- a/packages/edit-post/src/components/header/pinned-plugins/style.scss +++ /dev/null @@ -1,45 +0,0 @@ -.edit-post-pinned-plugins { - display: none; - - @include break-small() { - display: flex; - } - - .components-button { - margin-left: 4px; - - &.is-pressed { - margin-left: 5px; - } - - svg { - max-width: 24px; - max-height: 24px; - } - } - - // Colorize plugin icons to ensure contrast and cohesion, but allow plugin developers to override. - .components-button:not(.is-pressed) svg, - .components-button:not(.is-pressed) svg * { - stroke: $dark-gray-primary; - fill: $dark-gray-primary; - stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. - } - - // Forcefully colorize hover and toggled plugin icon states to ensure legibility and consistency. - .components-button.is-pressed svg, - .components-button.is-pressed svg *, - .components-button.is-pressed:hover svg, - .components-button.is-pressed:hover svg * { - stroke: $white !important; - fill: $white !important; - stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. - } - - .components-button:hover svg, - .components-button:hover svg * { - stroke: $blue-medium-focus !important; - fill: $blue-medium-focus !important; - stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. - } -} diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 7ce59cf49882aa..b57fa8d5dbf384 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -29,6 +29,7 @@ import { import { useViewportMatch } from '@wordpress/compose'; import { PluginArea } from '@wordpress/plugins'; import { __ } from '@wordpress/i18n'; +import { ComplementaryArea } from '@wordpress/interface'; /** * Internal dependencies @@ -42,7 +43,6 @@ import OptionsModal from '../options-modal'; import BrowserURL from '../browser-url'; import Header from '../header'; import SettingsSidebar from '../sidebar/settings-sidebar'; -import Sidebar from '../sidebar'; import MetaBoxes from '../meta-boxes'; import PluginPostPublishPanel from '../sidebar/plugin-post-publish-panel'; import PluginPrePublishPanel from '../sidebar/plugin-pre-publish-panel'; @@ -145,7 +145,7 @@ function Layout() { ) } - + ) } diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js deleted file mode 100644 index e6175b72b2685a..00000000000000 --- a/packages/edit-post/src/components/sidebar/index.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { - createSlotFill, - withFocusReturn, - Animate, -} from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; -import { ifCondition, compose } from '@wordpress/compose'; - -const { Fill, Slot } = createSlotFill( 'Sidebar' ); - -/** - * Renders a sidebar with its content. - * - * @return {Object} The rendered sidebar. - */ -function Sidebar( { children, className } ) { - return ( -
- { children } -
- ); -} - -Sidebar = withFocusReturn( { - onFocusReturn() { - const button = document.querySelector( - '.edit-post-header__settings [aria-label="Settings"]' - ); - if ( button ) { - button.focus(); - return false; - } - }, -} )( Sidebar ); - -function AnimatedSidebarFill( props ) { - return ( - - - { () => } - - - ); -} - -const WrappedSidebar = compose( - withSelect( ( select, { name } ) => ( { - isActive: - select( 'core/edit-post' ).getActiveGeneralSidebarName() === name, - } ) ), - ifCondition( ( { isActive } ) => isActive ) -)( AnimatedSidebarFill ); - -WrappedSidebar.Slot = Slot; - -export default WrappedSidebar; diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index deb007a37d82dc..3cc5d2eacdd3b2 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -1,71 +1,9 @@ /** * WordPress dependencies */ -import { Button, Panel } from '@wordpress/components'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { ComplementaryArea } from '@wordpress/interface'; +import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { withPluginContext } from '@wordpress/plugins'; -import { compose } from '@wordpress/compose'; -import { starEmpty, starFilled } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import PinnedPlugins from '../../header/pinned-plugins'; -import Sidebar from '../'; -import SidebarHeader from '../sidebar-header'; - -function PluginSidebar( props ) { - const { - children, - className, - icon, - isActive, - isPinnable = true, - isPinned, - sidebarName, - title, - togglePin, - toggleSidebar, - } = props; - - return ( - <> - { isPinnable && ( - - { isPinned && ( - - -
  • - -
  • - - +
      +
    • + +
    • +
    • + +
    • +
    ); }; 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 df162808e438d5..4a408b1b63f350 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -1,15 +1,11 @@ /** * WordPress dependencies */ -import { Panel } from '@wordpress/components'; -import { compose, ifCondition } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; import { BlockInspector } from '@wordpress/block-editor'; /** * Internal dependencies */ -import Sidebar from '../'; import SettingsHeader from '../settings-header'; import PostStatus from '../post-status'; import LastRevision from '../last-revision'; @@ -21,11 +17,30 @@ import DiscussionPanel from '../discussion-panel'; import PageAttributes from '../page-attributes'; import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; +import PluginSidebarEditPost from '../../sidebar/plugin-sidebar'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; -const SettingsSidebar = ( { sidebarName } ) => ( - - - +const SettingsSidebar = () => { + const sidebarName = useSelect( + ( select ) => + select( 'core/interface' ).getActiveComplementaryArea( + 'core/edit-post' + ), + [] + ); + if ( + ! [ 'edit-post/document', 'edit-post/block' ].includes( sidebarName ) + ) { + return null; + } + return ( + } + closeLabel={ __( 'Close settings' ) } + headerClassName="edit-post-sidebar__panel-tabs" + > { sidebarName === 'edit-post/document' && ( <> @@ -41,20 +56,8 @@ const SettingsSidebar = ( { sidebarName } ) => ( ) } { sidebarName === 'edit-post/block' && } - - -); + + ); +}; -export default compose( - withSelect( ( select ) => { - const { getActiveGeneralSidebarName, isEditorSidebarOpened } = select( - 'core/edit-post' - ); - - return { - isEditorSidebarOpened: isEditorSidebarOpened(), - sidebarName: getActiveGeneralSidebarName(), - }; - } ), - ifCondition( ( { isEditorSidebarOpened } ) => isEditorSidebarOpened ) -)( SettingsSidebar ); +export default SettingsSidebar; diff --git a/packages/edit-post/src/components/sidebar/sidebar-header/index.js b/packages/edit-post/src/components/sidebar/sidebar-header/index.js deleted file mode 100644 index 748992e82b0426..00000000000000 --- a/packages/edit-post/src/components/sidebar/sidebar-header/index.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { close } from '@wordpress/icons'; - -const SidebarHeader = ( { children, className, closeLabel } ) => { - const { shortcut, title } = useSelect( - ( select ) => ( { - shortcut: select( - 'core/keyboard-shortcuts' - ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), - title: select( 'core/editor' ).getEditedPostAttribute( 'title' ), - } ), - [] - ); - const { closeGeneralSidebar } = useDispatch( 'core/edit-post' ); - - // The `tabIndex` serves the purpose of normalizing browser behavior of - // button clicks and focus. Notably, without making the header focusable, a - // Button click would not trigger a focus event in macOS Firefox. Thus, when - // the sidebar is unmounted, the corresponding "focus return" behavior to - // shift focus back to the heading toolbar would not be run. - // - // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - - return ( - <> -
    - - { title || __( '(no title)' ) } - -
    -
    - { children } -
    - - ); -}; - -export default SidebarHeader; diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index f960c6757c3033..3a6a5b1c01f232 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -1,107 +1,3 @@ -.edit-post-sidebar { - background: $white; - color: $dark-gray-500; - overflow: visible; - - @include break-small() { - z-index: auto; - height: 100%; - overflow: auto; - -webkit-overflow-scrolling: touch; - } - - @include break-medium() { - width: $sidebar-width; - } - - > .components-panel { - border-left: none; - border-right: none; - overflow: auto; - -webkit-overflow-scrolling: touch; - height: auto; - max-height: calc(100vh - #{ $admin-bar-height-big + $panel-header-height + $panel-header-height }); - margin-top: -1px; - margin-bottom: -1px; - position: relative; - - @include break-small() { - overflow: visible; - height: auto; - max-height: none; - } - } - - > .components-panel .components-panel__header { - position: fixed; - z-index: z-index(".components-panel__header"); - top: 0; - left: 0; - right: 0; - height: $panel-header-height; - - @include break-small() { - position: inherit; - top: auto; - left: auto; - right: auto; - } - } - - p { - margin-top: 0; - } - - h2, - h3 { - font-size: $default-font-size; - color: $dark-gray-500; - margin-bottom: 1.5em; - } - - hr { - border-top: none; - border-bottom: 1px solid $light-gray-500; - margin: 1.5em 0; - } - - div.components-toolbar { - box-shadow: none; - margin-bottom: 1.5em; - &:last-child { - margin-bottom: 0; - } - } - - p + div.components-toolbar { - margin-top: -1em; - } - - .block-editor-skip-to-selected-block:focus { - top: auto; - right: 10px; - bottom: 10px; - left: auto; - } -} - -/* Text Editor specific */ -.components-panel__header.edit-post-sidebar__header { - background: $white; - padding-right: $panel-padding / 2; - - .edit-post-sidebar__title { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - } - - @include break-medium() { - display: none; - } -} - .components-panel__header.edit-post-sidebar__panel-tabs { justify-content: flex-start; padding-left: 0; diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index eec8afd6b38240..33140b34f7d09d 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -3,29 +3,38 @@ */ import { castArray } from 'lodash'; +/** + * WordPress dependencies + */ +import { dispatch } from '@wordpress/data-controls'; + /** * Returns an action object used in signalling that the user opened an editor sidebar. * - * @param {string} name Sidebar name to be opened. + * @param {?string} name Sidebar name to be opened. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function openGeneralSidebar( name ) { - return { - type: 'OPEN_GENERAL_SIDEBAR', - name, - }; +export function* openGeneralSidebar( name ) { + yield dispatch( + 'core/interface', + 'enableComplementaryArea', + 'core/edit-post', + name + ); } /** * Returns an action object signalling that the user closed the sidebar. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function closeGeneralSidebar() { - return { - type: 'CLOSE_GENERAL_SIDEBAR', - }; +export function* closeGeneralSidebar() { + yield dispatch( + 'core/interface', + 'disableComplementaryArea', + 'core/edit-post' + ); } /** diff --git a/packages/edit-post/src/store/defaults.js b/packages/edit-post/src/store/defaults.js index 1b90986e222a9a..64435c05ce74c1 100644 --- a/packages/edit-post/src/store/defaults.js +++ b/packages/edit-post/src/store/defaults.js @@ -1,6 +1,5 @@ export const PREFERENCES_DEFAULTS = { editorMode: 'visual', - isGeneralSidebarDismissed: false, panels: { 'post-status': { opened: true, @@ -11,7 +10,6 @@ export const PREFERENCES_DEFAULTS = { welcomeGuide: true, fullscreenMode: true, }, - pinnedPluginItems: {}, hiddenBlockTypes: [], preferredStyleVariations: {}, localAutosaveInterval: 15, diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 4560fd12d46bbb..de84aaf3062ab0 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { registerStore } from '@wordpress/data'; +import { controls as dataControls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -17,7 +18,10 @@ const store = registerStore( STORE_KEY, { reducer, actions, selectors, - controls, + controls: { + ...dataControls, + ...controls, + }, persist: [ 'preferences' ], } ); diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 27e5c29493976a..de17f27eedde01 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -55,15 +55,6 @@ export const preferences = flow( [ combineReducers, createWithInitialState( PREFERENCES_DEFAULTS ), ] )( { - isGeneralSidebarDismissed( state, action ) { - switch ( action.type ) { - case 'OPEN_GENERAL_SIDEBAR': - case 'CLOSE_GENERAL_SIDEBAR': - return action.type === 'CLOSE_GENERAL_SIDEBAR'; - } - - return state; - }, panels( state, action ) { switch ( action.type ) { case 'TOGGLE_PANEL_ENABLED': { @@ -111,19 +102,6 @@ export const preferences = flow( [ return state; }, - pinnedPluginItems( state, action ) { - if ( action.type === 'TOGGLE_PINNED_PLUGIN_ITEM' ) { - return { - ...state, - [ action.pluginName ]: ! get( - state, - [ action.pluginName ], - true - ), - }; - } - return state; - }, hiddenBlockTypes( state, action ) { switch ( action.type ) { case 'SHOW_BLOCK_TYPES': @@ -181,27 +159,6 @@ export function removedPanels( state = [], action ) { return state; } -/** - * Reducer returning the next active general sidebar state. The active general - * sidebar is a unique name to identify either an editor or plugin sidebar. - * - * @param {?string} state Current state. - * @param {Object} action Action object. - * - * @return {?string} Updated state. - */ -export function activeGeneralSidebar( - state = DEFAULT_ACTIVE_GENERAL_SIDEBAR, - action -) { - switch ( action.type ) { - case 'OPEN_GENERAL_SIDEBAR': - return action.name; - } - - return state; -} - /** * Reducer for storing the name of the open modal, or null if no modal is open. * @@ -294,7 +251,6 @@ const metaBoxes = combineReducers( { } ); export default combineReducers( { - activeGeneralSidebar, activeModal, metaBoxes, preferences, diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index d2742fb3fe181c..fd99d43279462c 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -4,6 +4,11 @@ import createSelector from 'rememo'; import { get, includes, some, flatten, values } from 'lodash'; +/** + * WordPress dependencies + */ +import { createRegistrySelector } from '@wordpress/data'; + /** * Returns the current editing mode. * @@ -22,14 +27,17 @@ export function getEditorMode( state ) { * * @return {boolean} Whether the editor sidebar is opened. */ -export function isEditorSidebarOpened( state ) { - const activeGeneralSidebar = getActiveGeneralSidebarName( state ); - - return includes( - [ 'edit-post/document', 'edit-post/block' ], - activeGeneralSidebar - ); -} +export const isEditorSidebarOpened = createRegistrySelector( + ( select ) => () => { + const activeGeneralSidebar = select( + 'core/interface' + ).getActiveComplementaryArea( 'core/edit-post' ); + return includes( + [ 'edit-post/document', 'edit-post/block' ], + activeGeneralSidebar + ); + } +); /** * Returns true if the plugin sidebar is opened. @@ -37,10 +45,20 @@ export function isEditorSidebarOpened( state ) { * @param {Object} state Global application state * @return {boolean} Whether the plugin sidebar is opened. */ -export function isPluginSidebarOpened( state ) { - const activeGeneralSidebar = getActiveGeneralSidebarName( state ); - return !! activeGeneralSidebar && ! isEditorSidebarOpened( state ); -} +export const isPluginSidebarOpened = createRegistrySelector( + ( select ) => () => { + const activeGeneralSidebar = select( + 'core/interface' + ).getActiveComplementaryArea( 'core/edit-post' ); + return ( + !! activeGeneralSidebar && + ! includes( + [ 'edit-post/document', 'edit-post/block' ], + activeGeneralSidebar + ) + ); + } +); /** * Returns the current active general sidebar name, or null if there is no @@ -56,19 +74,13 @@ export function isPluginSidebarOpened( state ) { * * @return {?string} Active general sidebar name. */ -export function getActiveGeneralSidebarName( state ) { - // Dismissal takes precedent. - const isDismissed = getPreference( - state, - 'isGeneralSidebarDismissed', - false - ); - if ( isDismissed ) { - return null; +export const getActiveGeneralSidebarName = createRegistrySelector( + ( select ) => () => { + return select( 'core/interface' ).getActiveComplementaryArea( + 'core/edit-post' + ); } - - return state.activeGeneralSidebar; -} +); /** * Returns the preferences (these preferences are persisted locally). @@ -187,11 +199,14 @@ export function isFeatureActive( state, feature ) { * * @return {boolean} Whether the plugin item is pinned. */ -export function isPluginItemPinned( state, pluginName ) { - const pinnedPluginItems = getPreference( state, 'pinnedPluginItems', {} ); - - return get( pinnedPluginItems, [ pluginName ], true ); -} +export const isPluginItemPinned = createRegistrySelector( + ( select ) => ( pluginName ) => { + return select( 'core/interface' ).isItemPinned( + 'core/edit-post', + pluginName + ); + } +); /** * Returns an array of active meta box locations. diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 07dc4b81ece682..31877f378bb286 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -5,37 +5,16 @@ import { toggleEditorPanelEnabled, toggleEditorPanelOpened, removeEditorPanel, - openGeneralSidebar, - closeGeneralSidebar, openPublishSidebar, closePublishSidebar, togglePublishSidebar, openModal, closeModal, toggleFeature, - togglePinnedPluginItem, requestMetaBoxUpdates, } from '../actions'; describe( 'actions', () => { - describe( 'openGeneralSidebar', () => { - it( 'should return OPEN_GENERAL_SIDEBAR action', () => { - const name = 'plugin/my-name'; - expect( openGeneralSidebar( name ) ).toEqual( { - type: 'OPEN_GENERAL_SIDEBAR', - name, - } ); - } ); - } ); - - describe( 'closeGeneralSidebar', () => { - it( 'should return CLOSE_GENERAL_SIDEBAR action', () => { - expect( closeGeneralSidebar() ).toEqual( { - type: 'CLOSE_GENERAL_SIDEBAR', - } ); - } ); - } ); - describe( 'openPublishSidebar', () => { it( 'should return an OPEN_PUBLISH_SIDEBAR action', () => { expect( openPublishSidebar() ).toEqual( { @@ -115,17 +94,6 @@ describe( 'actions', () => { } ); } ); - describe( 'togglePinnedPluginItem', () => { - it( 'should return TOGGLE_PINNED_PLUGIN_ITEM action', () => { - const pluginName = 'foo/bar'; - - expect( togglePinnedPluginItem( pluginName ) ).toEqual( { - type: 'TOGGLE_PINNED_PLUGIN_ITEM', - pluginName, - } ); - } ); - } ); - describe( 'requestMetaBoxUpdates', () => { it( 'should return the REQUEST_META_BOX_UPDATES action', () => { expect( requestMetaBoxUpdates() ).toEqual( { diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index b21be5ff26d9ba..59a966024bf920 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -7,9 +7,7 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { - DEFAULT_ACTIVE_GENERAL_SIDEBAR, preferences, - activeGeneralSidebar, activeModal, isSavingMetaBoxes, metaBoxLocations, @@ -25,30 +23,6 @@ describe( 'state', () => { expect( state ).toEqual( PREFERENCES_DEFAULTS ); } ); - it( 'should set the general sidebar dismissed', () => { - const original = deepFreeze( preferences( undefined, {} ) ); - const state = preferences( original, { - type: 'OPEN_GENERAL_SIDEBAR', - name: 'edit-post/document', - } ); - - expect( state.isGeneralSidebarDismissed ).toBe( false ); - } ); - - it( 'should set the general sidebar undismissed', () => { - const original = deepFreeze( - preferences( undefined, { - type: 'OPEN_GENERAL_SIDEBAR', - name: 'edit-post/document', - } ) - ); - const state = preferences( original, { - type: 'CLOSE_GENERAL_SIDEBAR', - } ); - - expect( state.isGeneralSidebarDismissed ).toBe( true ); - } ); - it( 'should disable panels by default', () => { const original = deepFreeze( { panels: {}, @@ -186,48 +160,6 @@ describe( 'state', () => { expect( state.features ).toEqual( { chicken: false } ); } ); - describe( 'pinnedPluginItems', () => { - const initialState = deepFreeze( { - pinnedPluginItems: { - 'foo/enabled': true, - 'foo/disabled': false, - }, - } ); - - it( 'should disable a pinned plugin flag when the value does not exist', () => { - const state = preferences( initialState, { - type: 'TOGGLE_PINNED_PLUGIN_ITEM', - pluginName: 'foo/does-not-exist', - } ); - - expect( state.pinnedPluginItems[ 'foo/does-not-exist' ] ).toBe( - false - ); - } ); - - it( 'should disable a pinned plugin flag when it is enabled', () => { - const state = preferences( initialState, { - type: 'TOGGLE_PINNED_PLUGIN_ITEM', - pluginName: 'foo/enabled', - } ); - - expect( state.pinnedPluginItems[ 'foo/enabled' ] ).toBe( - false - ); - } ); - - it( 'should enable a pinned plugin flag when it is disabled', () => { - const state = preferences( initialState, { - type: 'TOGGLE_PINNED_PLUGIN_ITEM', - pluginName: 'foo/disabled', - } ); - - expect( state.pinnedPluginItems[ 'foo/disabled' ] ).toBe( - true - ); - } ); - } ); - describe( 'hiddenBlockTypes', () => { it( 'concatenates unique names on disable', () => { const original = deepFreeze( { @@ -257,24 +189,6 @@ describe( 'state', () => { } ); } ); - describe( 'activeGeneralSidebar', () => { - it( 'should default to the default active sidebar', () => { - const state = activeGeneralSidebar( undefined, {} ); - - expect( state ).toBe( DEFAULT_ACTIVE_GENERAL_SIDEBAR ); - } ); - - it( 'should set the general sidebar', () => { - const original = activeGeneralSidebar( undefined, {} ); - const state = activeGeneralSidebar( original, { - type: 'OPEN_GENERAL_SIDEBAR', - name: 'edit-post/document', - } ); - - expect( state ).toBe( 'edit-post/document' ); - } ); - } ); - describe( 'activeModal', () => { it( 'should default to null', () => { const state = activeModal( undefined, {} ); diff --git a/packages/edit-post/src/store/test/selectors.js b/packages/edit-post/src/store/test/selectors.js index 4da4686fc0379e..f2b349890602ae 100644 --- a/packages/edit-post/src/store/test/selectors.js +++ b/packages/edit-post/src/store/test/selectors.js @@ -9,13 +9,9 @@ import deepFreeze from 'deep-freeze'; import { getEditorMode, getPreference, - isEditorSidebarOpened, isEditorPanelOpened, isModalActive, isFeatureActive, - isPluginSidebarOpened, - getActiveGeneralSidebarName, - isPluginItemPinned, hasMetaBoxes, isSavingMetaBoxes, getActiveMetaBoxLocations, @@ -71,114 +67,6 @@ describe( 'selectors', () => { } ); } ); - describe( 'isEditorSidebarOpened', () => { - it( 'should return false when the editor sidebar is not opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: true, - }, - activeGeneralSidebar: null, - }; - - expect( isEditorSidebarOpened( state ) ).toBe( false ); - } ); - - it( 'should return false when the editor sidebar is assigned but not opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: true, - }, - activeGeneralSidebar: 'edit-post/document', - }; - - expect( isEditorSidebarOpened( state ) ).toBe( false ); - } ); - - it( 'should return false when the plugin sidebar is opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: false, - }, - activeGeneralSidebar: 'my-plugin/my-sidebar', - }; - - expect( isEditorSidebarOpened( state ) ).toBe( false ); - } ); - - it( 'should return true when the editor sidebar is opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: false, - }, - activeGeneralSidebar: 'edit-post/document', - }; - - expect( isEditorSidebarOpened( state ) ).toBe( true ); - } ); - } ); - - describe( 'isPluginSidebarOpened', () => { - it( 'should return false when the plugin sidebar is not opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: true, - }, - activeGeneralSidebar: null, - }; - - expect( isPluginSidebarOpened( state ) ).toBe( false ); - } ); - - it( 'should return false when the editor sidebar is opened', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: false, - }, - activeGeneralSidebar: 'edit-post/document', - }; - - expect( isPluginSidebarOpened( state ) ).toBe( false ); - } ); - - it( 'should return true when the plugin sidebar is opened', () => { - const name = 'plugin-sidebar/my-plugin/my-sidebar'; - const state = { - preferences: { - isGeneralSidebarDismissed: false, - }, - activeGeneralSidebar: name, - }; - - expect( isPluginSidebarOpened( state ) ).toBe( true ); - } ); - } ); - - describe( 'getActiveGeneralSidebarName', () => { - it( 'returns null if dismissed', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: true, - }, - activeGeneralSidebar: 'edit-post/block', - }; - - expect( getActiveGeneralSidebarName( state ) ).toBe( null ); - } ); - - it( 'returns active general sidebar', () => { - const state = { - preferences: { - isGeneralSidebarDismissed: false, - }, - activeGeneralSidebar: 'edit-post/block', - }; - - expect( getActiveGeneralSidebarName( state ) ).toBe( - 'edit-post/block' - ); - } ); - } ); - describe( 'isModalActive', () => { it( 'returns true if the provided name matches the value in the preferences activeModal property', () => { const state = { @@ -394,29 +282,6 @@ describe( 'selectors', () => { } ); } ); - describe( 'isPluginItemPinned', () => { - const state = { - preferences: { - pinnedPluginItems: { - 'foo/pinned': true, - 'foo/unpinned': false, - }, - }, - }; - - it( 'should return true if the flag is not set for the plugin item', () => { - expect( isPluginItemPinned( state, 'foo/unknown' ) ).toBe( true ); - } ); - - it( 'should return true if plugin item is not pinned', () => { - expect( isPluginItemPinned( state, 'foo/pinned' ) ).toBe( true ); - } ); - - it( 'should return false if plugin item item is unpinned', () => { - expect( isPluginItemPinned( state, 'foo/unpinned' ) ).toBe( false ); - } ); - } ); - describe( 'hasMetaBoxes', () => { it( 'should return true if there are active meta boxes', () => { const state = { diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index e9a85641e28a29..4e6fea9f88bee4 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -1,10 +1,11 @@ $footer-height: $button-size-small; +@import "../../interface/src/style.scss"; + @import "./components/header/style.scss"; @import "./components/header/fullscreen-mode-close/style.scss"; @import "./components/header/header-toolbar/style.scss"; @import "./components/header/more-menu/style.scss"; -@import "./components/header/pinned-plugins/style.scss"; @import "./components/keyboard-shortcut-help-modal/style.scss"; @import "./components/layout/style.scss"; @import "./components/manage-blocks-modal/style.scss"; @@ -19,7 +20,6 @@ $footer-height: $button-size-small; @import "./components/sidebar/post-status/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; @import "./components/sidebar/settings-header/style.scss"; -@import "./components/sidebar/sidebar-header/style.scss"; @import "./components/text-editor/style.scss"; @import "./components/visual-editor/style.scss"; @import "./components/options-modal/style.scss"; diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 4e48a66f1e54f2..67f140d977d0d3 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -33,8 +33,10 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", "@wordpress/primitives": "file:../primitives", "@wordpress/url": "file:../url", "file-saver": "^2.0.2", diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index a69b8508ee8cca..4d1c6dcddaceda 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -3,13 +3,13 @@ */ import { useCallback } from '@wordpress/element'; import { BlockNavigationDropdown, ToolSelector } from '@wordpress/block-editor'; +import { PinnedItems } from '@wordpress/interface'; /** * Internal dependencies */ import { useEditorContext } from '../editor'; import FullscreenModeClose from './fullscreen-mode-close'; -import MoreMenu from './more-menu'; import TemplateSwitcher from '../template-switcher'; import SaveButton from '../save-button'; @@ -62,7 +62,7 @@ export default function Header() {
    - +
    ); diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 060c2ddce1a510..f5ee7eda2d9a23 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -1,20 +1,35 @@ /** * WordPress dependencies */ -import { createSlotFill, Panel } from '@wordpress/components'; +import { createSlotFill } from '@wordpress/components'; +import { ComplementaryArea } from '@wordpress/interface'; import { __ } from '@wordpress/i18n'; +import { cog, pencil } from '@wordpress/icons'; const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill( 'EditSiteSidebarInspector' ); - function Sidebar() { return ( -
    - + <> + + - -
    + + +

    Global Styles area

    +
    + ); } diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index d4145ab69a71ad..a02468b9666bd0 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -1,3 +1,5 @@ +@import "../../interface/src/style.scss"; + @import "./components/block-editor/style.scss"; @import "./components/header/style.scss"; @import "./components/header/fullscreen-mode-close/style.scss"; diff --git a/packages/interface/README.md b/packages/interface/README.md new file mode 100644 index 00000000000000..06e074f841ea8c --- /dev/null +++ b/packages/interface/README.md @@ -0,0 +1,54 @@ +# (Experimental) Interface + +The Interface Package contains the basis the start a modern WordPress screen as "core/edit-post" and "core/edit-site". The package offers a store and a set of components. The store is useful to contain common data required by a screen (e.g., active areas). The information is persisted across screen reloads. The components allow one to implement functionality like a sidebar and allow a screen to be extended by third-party plugins by default. + + + +## Installation + +Install the module + +```bash +npm install @wordpress/interface --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + + +## API Usage + +### Complementary Areas + +`ComplementaryArea` and `ComplementaryArea.Slot` form a slot fill pair to render complementary areas. Multiple `ComplementaryArea` components representing different complementary areas may be rendered at the same time, but only one appears on the slot depending on which complementary area is enabled. + +It is possible to control which complementary is enabled by using the store: + +Bellow are some examples of how to control the active complementary area using the store: +```js +wp.data.select('core/interface').getActiveComplementaryArea( 'core/edit-post'); -> "edit-post/document' + +wp.data.dispatch('core/interface').enableComplementaryArea( 'core/edit-post', 'edit-post/block' ); + +wp.data.select('core/interface').getActiveComplementaryArea( 'core/edit-post'); -> "edit-post/block" + +wp.data.dispatch('core/interface').disableComplementaryArea( 'core/edit-post' ); + +wp.data.select('core/interface').getActiveComplementaryArea( 'core/edit-post'); -> null +``` + +### Pinned Areas + +`PinnedItems` and `PinnedItems.Slot`form a slot fill pair to render pinned items or areas of "favorites". `ComplementaryArea` component makes use of `PinnedItems` and automatically adds a pinned item for the complementary areas marked as favorite. + +```js +wp.data.select( 'core/interface' ).isItemPinned( 'core/edit-post', 'edit-post-block-patterns/block-patterns-sidebar' ); -> false + +wp.data.dispatch('core/interface').pinItem( 'core/edit-post', 'edit-post-block-patterns/block-patterns-sidebar' ); + +wp.data.select( 'core/interface' ).isItemPinned( 'core/edit-post', 'edit-post-block-patterns/block-patterns-sidebar' ); -> true + +wp.data.dispatch('core/interface').unpinItem( 'core/edit-post', 'edit-post-block-patterns/block-patterns-sidebar' ); + +wp.data.select( 'core/interface' ).isItemPinned( 'core/edit-post', 'edit-post-block-patterns/block-patterns-sidebar' ); -> false +``` + diff --git a/packages/interface/package.json b/packages/interface/package.json new file mode 100644 index 00000000000000..5ba6cbaf9a80f1 --- /dev/null +++ b/packages/interface/package.json @@ -0,0 +1,39 @@ +{ + "name": "@wordpress/interface", + "version": "0.0.1", + "private": true, + "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "interface", + "components" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/interface/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/interface" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.8.3", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/plugins": "file:../plugins", + "classnames": "^2.2.5", + "lodash": "^4.17.15" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/interface/src/components/complementary-area-header/index.js b/packages/interface/src/components/complementary-area-header/index.js new file mode 100644 index 00000000000000..8e284fb4f2808c --- /dev/null +++ b/packages/interface/src/components/complementary-area-header/index.js @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { close } from '@wordpress/icons'; + +const ComplementaryAreaHeader = ( { + smallScreenTitle, + toggleShortcut, + onClose, + children, + className, + closeLabel, +} ) => { + return ( + <> +
    + { smallScreenTitle && ( + + { smallScreenTitle } + + ) } +
    +
    + { children } +
    + + ); +}; + +export default ComplementaryAreaHeader; diff --git a/packages/edit-post/src/components/sidebar/sidebar-header/style.scss b/packages/interface/src/components/complementary-area-header/style.scss similarity index 71% rename from packages/edit-post/src/components/sidebar/sidebar-header/style.scss rename to packages/interface/src/components/complementary-area-header/style.scss index aac3b2630f9a73..7808bc904ad230 100644 --- a/packages/edit-post/src/components/sidebar/sidebar-header/style.scss +++ b/packages/interface/src/components/complementary-area-header/style.scss @@ -1,9 +1,8 @@ -/* Text Editor specific */ -.components-panel__header.edit-post-sidebar-header__small { +.components-panel__header.interface-complementary-area-header__small { background: $white; padding-right: $grid-unit-05; - .edit-post-sidebar__title { + .interface-complementary-area-header__small-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -15,7 +14,7 @@ } } -.components-panel__header.edit-post-sidebar-header { +.interface-complementary-area-header { padding-right: $grid-unit-05; background: $light-gray-200; diff --git a/packages/interface/src/components/complementary-area/README.md b/packages/interface/src/components/complementary-area/README.md new file mode 100644 index 00000000000000..e44aaccb6b71cd --- /dev/null +++ b/packages/interface/src/components/complementary-area/README.md @@ -0,0 +1,115 @@ +ComplementaryArea +============================= + +`ComplementaryArea` is a component to render complementary areas like sidebars. Its implementation is based on slot & fill. +Multiple areas may be added in a given time, but only one is visible; the component renders the required artifacts to control which area is visible. The component allows, for example, to have multiple plugins rendering their sidebars, the user only sees one of the sidebars, but can switch which sidebar is active. + +The contents passed to ComplementaryArea are rendered in the ComplementaryArea.Slot corresponding to their scope if the complementary area is enabled. + +Besides rendering the complimentary area, the component renders a button in `PinnedItems` that allows opening the complementary area. The button only appears if the complementary is marked as favorite. By default, the complementary area headers rendered contain a button to mark and unmark areas as favorites. + +## Props + +### children + +The content to be displayed within the complementary area. + +- Type: `Element` +- Required: Yes + +### closeLabel + +Label of the button that allows to close the complementary area. + +- Type: `String` +- Required: No +- Default: "Close plugin" + +### complementaryAreaIdentifier + +Identifier of the complementary area. The string is saved on the store and allows to identify which of the sidebars is active. + +- Type: `String` +- Required: No +- Default: Concatenation of `name` of the plugin extracted from the context (when available) with the "name" of the sidebar passed as a property. + +### header + +In cases where a custom header is desirable, the prop could be used. It can contain the contents that should be drawn on the header. + +- Type: `Element` +- Required: No + +### headerClassName + +A className passed to the header container. + +- Type: `String` +- Required: No + +### icon + +The icon to render. + +- Type: `Function|WPComponent|null` +- Required: No +- Default: `null` + +### name + +Name of the complementary area. The name of the complementarity area is concatenated with the name of the plugin to form the identifier of the complementary area. The name of the plugin is extracted from the plugin context where the sidebar is rendered. If there is no plugin context available or there is a need to specify a custom identifier, please use the `complementaryAreaIdentifier` property instead. + +- Type: `String` +- Required: No + +### panelClassName + +A className passed to the panel that contains the contents of the sidebar. + +- Type: `String` +- Required: No +- Default: `null` + +### scope + +The scope of the complementary area e.g: "core/edit-post", "core/edit-site", "myplugin/custom-screen-a", + +- Type: `String` +- Required: Yes + +### smallScreenTitle + +In small screens, the complementary area may take up the entire screen. +`smallScreenTitle` allows displaying a title above the complementary area, so the user is more aware of what the area refers to. + +- Type: `String` +- Required: No + +### title + +Human friendly title of the complementary area. + +- Type: `String` +- Required: Yes + +### toggleShortcut + +Keyboard shortcut that allows opening and closing the area. Passed to the button that allows the same action, so the user sees a visual indication that there is a keyboard shortcut. + +- Type: `String|Object` +- Required: No + +ComplementaryArea.Slot +============================= + +A slot that renders the currently active ComplementaryArea. + +## Props + +### scope + +The scope of the complementary area e.g: "core/edit-post", "core/edit-site", "myplugin/custom-screen-a", + +- Type: `String` +- Required: Yes + diff --git a/packages/interface/src/components/complementary-area/index.js b/packages/interface/src/components/complementary-area/index.js new file mode 100644 index 00000000000000..3d37e4607cb356 --- /dev/null +++ b/packages/interface/src/components/complementary-area/index.js @@ -0,0 +1,147 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Animate, Button, Panel, Slot, Fill } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { withPluginContext } from '@wordpress/plugins'; +import { starEmpty, starFilled } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import ComplementaryAreaHeader from '../complementary-area-header'; +import PinnedItems from '../pinned-items'; + +function ComplementaryAreaSlot( { scope, ...props } ) { + return ; +} + +function ComplementaryAreaFill( { scope, children, className } ) { + return ( + + + { () =>
    { children }
    } +
    +
    + ); +} + +function ComplementaryArea( { + children, + className, + closeLabel = __( 'Close plugin' ), + complementaryAreaIdentifier, + header, + headerClassName, + icon, + isPinnable = true, + panelClassName, + scope, + smallScreenTitle, + title, + toggleShortcut, +} ) { + const { isActive, isPinned } = useSelect( + ( select ) => { + const { getActiveComplementaryArea, isItemPinned } = select( + 'core/interface' + ); + return { + isActive: + getActiveComplementaryArea( scope ) === + complementaryAreaIdentifier, + isPinned: isItemPinned( scope, complementaryAreaIdentifier ), + }; + }, + [ complementaryAreaIdentifier, scope ] + ); + const { enableComplementaryArea, disableComplementaryArea } = useDispatch( + 'core/interface' + ); + const { pinItem, unpinItem } = useDispatch( 'core/interface' ); + return ( + <> + { isPinned && ( + +