diff --git a/packages/e2e-tests/specs/experiments/settings-sidebar.test.js b/packages/e2e-tests/specs/experiments/settings-sidebar.test.js new file mode 100644 index 00000000000000..457fb0ae859213 --- /dev/null +++ b/packages/e2e-tests/specs/experiments/settings-sidebar.test.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { + trashAllPosts, + activateTheme, + getAllBlocks, + selectBlockByClientId, +} from '@wordpress/e2e-test-utils'; + +/** + * Internal dependencies + */ +import { navigationPanel, siteEditor } from '../../experimental-features'; + +async function toggleSidebar() { + await page.click( + '.edit-site-header__actions button[aria-label="Settings"]' + ); +} + +async function getActiveTabLabel() { + return await page.$eval( + '.edit-site-sidebar__panel-tab.is-active', + ( element ) => element.getAttribute( 'aria-label' ) + ); +} + +async function getTemplateCard() { + return { + title: await page.$eval( + '.edit-site-template-card__title', + ( element ) => element.innerText + ), + description: await page.$eval( + '.edit-site-template-card__description', + ( element ) => element.innerText + ), + }; +} + +describe( 'Settings sidebar', () => { + beforeAll( async () => { + await activateTheme( 'tt1-blocks' ); + await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template_part' ); + } ); + afterAll( async () => { + await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template_part' ); + await activateTheme( 'twentytwentyone' ); + } ); + beforeEach( async () => { + await siteEditor.visit(); + } ); + + describe( 'Template tab', () => { + it( 'should open template tab by default if no block is selected', async () => { + await toggleSidebar(); + + expect( await getActiveTabLabel() ).toEqual( + 'Template (selected)' + ); + } ); + + it( "should show the currently selected template's title and description", async () => { + await toggleSidebar(); + + const templateCardBeforeNavigation = await getTemplateCard(); + await navigationPanel.open(); + await navigationPanel.backToRoot(); + await navigationPanel.navigate( 'Templates' ); + await navigationPanel.clickItemByText( '404' ); + await navigationPanel.close(); + const templateCardAfterNavigation = await getTemplateCard(); + + expect( templateCardBeforeNavigation ).toMatchObject( { + title: 'Index', + description: + 'The default template which is used when no other template can be found', + } ); + expect( templateCardAfterNavigation ).toMatchObject( { + title: '404', + description: 'Used when the queried content cannot be found', + } ); + } ); + } ); + + describe( 'Block tab', () => { + it( 'should open block tab by default if a block is selected', async () => { + const allBlocks = await getAllBlocks(); + await selectBlockByClientId( allBlocks[ 0 ].clientId ); + + await toggleSidebar(); + // TODO: Remove when toolbar supports text fields + expect( console ).toHaveWarnedWith( + 'Using custom components as toolbar controls is deprecated. Please use ToolbarItem or ToolbarButton components instead. See: https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols' + ); + expect( await getActiveTabLabel() ).toEqual( 'Block (selected)' ); + } ); + } ); +} ); diff --git a/packages/edit-site/src/components/sidebar/constants.js b/packages/edit-site/src/components/sidebar/constants.js new file mode 100644 index 00000000000000..6322eaeb1258a8 --- /dev/null +++ b/packages/edit-site/src/components/sidebar/constants.js @@ -0,0 +1,2 @@ +export const SIDEBAR_TEMPLATE = 'edit-site/template'; +export const SIDEBAR_BLOCK = 'edit-site/block-inspector'; diff --git a/packages/edit-site/src/components/sidebar/default-sidebar.js b/packages/edit-site/src/components/sidebar/default-sidebar.js index ad2cf7ca8284a2..d1a1d0aa09b18f 100644 --- a/packages/edit-site/src/components/sidebar/default-sidebar.js +++ b/packages/edit-site/src/components/sidebar/default-sidebar.js @@ -14,6 +14,7 @@ export default function DefaultSidebar( { children, closeLabel, header, + headerClassName, } ) { return ( <> @@ -25,6 +26,7 @@ export default function DefaultSidebar( { icon={ icon } closeLabel={ closeLabel } header={ header } + headerClassName={ headerClassName } > { children } diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index ce5776f6404a61..206d41b857b833 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -1,15 +1,22 @@ /** * WordPress dependencies */ -import { createSlotFill } from '@wordpress/components'; +import { createSlotFill, PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { cog, typography } from '@wordpress/icons'; +import { useSelect } from '@wordpress/data'; +import { store as interfaceStore } from '@wordpress/interface'; +import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies */ import DefaultSidebar from './default-sidebar'; import GlobalStylesSidebar from './global-styles-sidebar'; +import { STORE_NAME } from '../../store/constants'; +import SettingsHeader from './settings-header'; +import TemplateCard from './template-card'; +import { SIDEBAR_BLOCK, SIDEBAR_TEMPLATE } from './constants'; const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill( 'EditSiteSidebarInspector' @@ -17,15 +24,41 @@ const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill( export const SidebarInspectorFill = InspectorFill; export function SidebarComplementaryAreaFills() { + const { sidebarName } = useSelect( ( select ) => { + let sidebar = select( interfaceStore ).getActiveComplementaryArea( + STORE_NAME + ); + + if ( ! [ SIDEBAR_BLOCK, SIDEBAR_TEMPLATE ].includes( sidebar ) ) { + sidebar = SIDEBAR_TEMPLATE; + if ( select( blockEditorStore ).getBlockSelectionStart() ) { + sidebar = SIDEBAR_BLOCK; + } + } + + return { + sidebarName: sidebar, + }; + } ); + return ( <> } + headerClassName="edit-site-sidebar__panel-tabs" > - + { sidebarName === SIDEBAR_TEMPLATE && ( + + + + ) } + { sidebarName === SIDEBAR_BLOCK && ( + + ) } { + const { enableComplementaryArea } = useDispatch( interfaceStore ); + const openTemplateSettings = () => + enableComplementaryArea( STORE_NAME, SIDEBAR_TEMPLATE ); + const openBlockSettings = () => + enableComplementaryArea( STORE_NAME, SIDEBAR_BLOCK ); + + const [ templateAriaLabel, templateActiveClass ] = + sidebarName === SIDEBAR_TEMPLATE + ? // translators: ARIA label for the Template sidebar tab, selected. + [ __( 'Template (selected)' ), 'is-active' ] + : // translators: ARIA label for the Template Settings Sidebar tab, not selected. + [ __( 'Template' ), '' ]; + + const [ blockAriaLabel, blockActiveClass ] = + sidebarName === SIDEBAR_BLOCK + ? // translators: ARIA label for the Block Settings Sidebar tab, selected. + [ __( 'Block (selected)' ), 'is-active' ] + : // translators: ARIA label for the Block Settings Sidebar tab, not selected. + [ __( 'Block' ), '' ]; + + /* Use a list so screen readers will announce how many tabs there are. */ + return ( + + ); +}; + +export default SettingsHeader; diff --git a/packages/edit-site/src/components/sidebar/settings-header/style.scss b/packages/edit-site/src/components/sidebar/settings-header/style.scss new file mode 100644 index 00000000000000..e3794a4c4064ce --- /dev/null +++ b/packages/edit-site/src/components/sidebar/settings-header/style.scss @@ -0,0 +1,80 @@ +.components-panel__header.edit-site-sidebar__panel-tabs { + justify-content: flex-start; + padding-left: 0; + padding-right: $grid-unit-20; + border-top: 0; + margin-top: 0; + + ul { + display: flex; + } + li { + margin: 0; + } + + .components-button.has-icon { + display: none; + margin: 0 0 0 auto; + padding: 0; + min-width: $icon-size; + height: $icon-size; + + @include break-medium() { + display: flex; + } + } +} + +.components-button.edit-site-sidebar__panel-tab { + border-radius: 0; + height: $grid-unit-60; + 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: 500; + + // 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 var(--wp-admin-border-width-focus) transparent, inset 0 0 -$border-width-tab 0 0 var(--wp-admin-theme-color); + position: relative; + z-index: z-index(".edit-post-sidebar__panel-tab.is-active"); + + // 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 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + position: relative; + z-index: z-index(".edit-post-sidebar__panel-tab.is-active"); + } + + &.is-active:focus { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color), inset 0 0 -$border-width-tab 0 0 var(--wp-admin-theme-color); + } +} diff --git a/packages/edit-site/src/components/sidebar/template-card/index.js b/packages/edit-site/src/components/sidebar/template-card/index.js new file mode 100644 index 00000000000000..09d4354f6238d3 --- /dev/null +++ b/packages/edit-site/src/components/sidebar/template-card/index.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { Icon } from '@wordpress/components'; +import { layout } from '@wordpress/icons'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; + +export default function TemplateCard() { + const { title, description } = useSelect( ( select ) => { + const { getEditedPostType, getEditedPostId } = select( editSiteStore ); + const { getEntityRecord } = select( coreStore ); + const { __experimentalGetTemplateInfo: getTemplateInfo } = select( + editorStore + ); + + const postType = getEditedPostType(); + const postId = getEditedPostId(); + const record = getEntityRecord( 'postType', postType, postId ); + const info = record ? getTemplateInfo( record ) : {}; + + return info; + }, [] ); + + if ( ! title && ! description ) { + return null; + } + + return ( +
+ +
+

{ title }

+ + { description } + +
+
+ ); +} diff --git a/packages/edit-site/src/components/sidebar/template-card/style.scss b/packages/edit-site/src/components/sidebar/template-card/style.scss new file mode 100644 index 00000000000000..89ac0ca844af87 --- /dev/null +++ b/packages/edit-site/src/components/sidebar/template-card/style.scss @@ -0,0 +1,28 @@ +.edit-site-template-card { + display: flex; + align-items: flex-start; +} + +.edit-site-template-card__content { + flex-grow: 1; +} + +.edit-site-template-card__title { + font-weight: 500; + &.edit-site-template-card__title { + margin: 0 0 5px; + } +} + +.edit-site-template-card__description { + font-size: $default-font-size; +} + +.edit-site-template-card__icon { + flex: 0 0 $button-size; + margin-left: -2px; + margin-right: 10px; + padding: 0 3px; + width: $button-size; + height: $button-size-small; +} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 9e4dce8b54aa23..7c93a17c0b1ab7 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -8,6 +8,8 @@ @import "./components/navigation-sidebar/navigation-panel/style.scss"; @import "./components/notices/style.scss"; @import "./components/sidebar/style.scss"; +@import "./components/sidebar/settings-header/style.scss"; +@import "./components/sidebar/template-card/style.scss"; @import "./components/editor/style.scss"; @import "./components/template-details/style.scss";