diff --git a/packages/e2e-tests/specs/editor/various/change-detection.test.js b/packages/e2e-tests/specs/editor/various/change-detection.test.js
index 62057c4cbb2bc..0eb673671222f 100644
--- a/packages/e2e-tests/specs/editor/various/change-detection.test.js
+++ b/packages/e2e-tests/specs/editor/various/change-detection.test.js
@@ -370,7 +370,11 @@ describe( 'Change detection', () => {
it( 'consecutive edits to the same attribute should mark the post as dirty after a save', async () => {
// Open the sidebar block settings.
await openDocumentSettingsSidebar();
- await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' );
+
+ const blockInspectorTab = await page.waitForXPath(
+ '//button[@role="tab"][contains(text(), "Block")]'
+ );
+ await blockInspectorTab.click();
// Insert a paragraph.
await clickBlockAppender();
diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js
index 81878ebf7208e..aea6536f605bb 100644
--- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js
+++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js
@@ -102,21 +102,24 @@ describe( 'Editing modes (visual/HTML)', () => {
expect( title ).toBe( 'Paragraph' );
// The Block inspector should be active.
- let blockInspectorTab = await page.$(
- '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]'
+ let [ blockInspectorTab ] = await page.$x(
+ '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]'
);
expect( blockInspectorTab ).not.toBeNull();
await switchEditorModeTo( 'Code' );
// The Block inspector should not be active anymore.
- blockInspectorTab = await page.$(
- '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]'
+ [ blockInspectorTab ] = await page.$x(
+ '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]'
);
- expect( blockInspectorTab ).toBeNull();
+ expect( blockInspectorTab ).toBeUndefined();
// No block is selected.
- await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' );
+ const inactiveBlockInspectorTab = await page.waitForXPath(
+ '//button[@role="tab"][contains(text(), "Block")]'
+ );
+ inactiveBlockInspectorTab.click();
const noBlocksElement = await page.$(
'.block-editor-block-inspector__no-blocks'
);
diff --git a/packages/e2e-tests/specs/editor/various/preferences.test.js b/packages/e2e-tests/specs/editor/various/preferences.test.js
index 98249637c7e96..54990a4004422 100644
--- a/packages/e2e-tests/specs/editor/various/preferences.test.js
+++ b/packages/e2e-tests/specs/editor/various/preferences.test.js
@@ -17,7 +17,7 @@ describe( 'preferences', () => {
async function getActiveSidebarTabText() {
try {
return await page.$eval(
- '.edit-post-sidebar__panel-tab.is-active',
+ 'div[aria-label="Editor settings"] [role="tab"][aria-selected="true"]',
( node ) => node.textContent
);
} catch ( error ) {
@@ -29,11 +29,15 @@ describe( 'preferences', () => {
}
it( 'remembers sidebar dismissal between sessions', async () => {
+ const blockTab = await page.waitForXPath(
+ `//button[@role="tab"][contains(text(), 'Block')]`
+ );
+
// Open by default.
expect( await getActiveSidebarTabText() ).toBe( 'Post' );
// Change to "Block" tab.
- await page.click( '.edit-post-sidebar__panel-tab[aria-label="Block"]' );
+ await blockTab.click();
expect( await getActiveSidebarTabText() ).toBe( 'Block' );
// Regression test: Reload resets to document tab.
@@ -46,7 +50,7 @@ describe( 'preferences', () => {
// Dismiss.
await page.click(
- '.edit-post-sidebar__panel-tabs [aria-label="Close Settings"]'
+ 'div[aria-label="Editor settings"] div[role="tablist"] + button[aria-label="Close Settings"]'
);
expect( await getActiveSidebarTabText() ).toBe( null );
diff --git a/packages/e2e-tests/specs/editor/various/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js
index 2e5d46eec2f7a..0cd39093aabb8 100644
--- a/packages/e2e-tests/specs/editor/various/sidebar.test.js
+++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js
@@ -13,7 +13,8 @@ import {
} from '@wordpress/e2e-test-utils';
const SIDEBAR_SELECTOR = '.edit-post-sidebar';
-const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active';
+const ACTIVE_SIDEBAR_TAB_SELECTOR =
+ 'div[aria-label="Editor settings"] [role="tab"][aria-selected="true"]';
const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Post';
describe( 'Sidebar', () => {
@@ -99,22 +100,24 @@ describe( 'Sidebar', () => {
// Tab lands at first (presumed selected) option "Post".
await page.keyboard.press( 'Tab' );
- const isActiveDocumentTab = await page.evaluate(
- () =>
- document.activeElement.textContent === 'Post' &&
- document.activeElement.classList.contains( 'is-active' )
+
+ // The Post tab should be focused and selected.
+ const [ documentInspectorTab ] = await page.$x(
+ '//button[@role="tab"][@aria-selected="true"][contains(text(), "Post")]'
);
- expect( isActiveDocumentTab ).toBe( true );
+ expect( documentInspectorTab ).toBeDefined();
+ expect( documentInspectorTab ).toHaveFocus();
- // Tab into and activate "Block".
- await page.keyboard.press( 'Tab' );
+ // Arrow key into and activate "Block".
+ await page.keyboard.press( 'ArrowRight' );
await page.keyboard.press( 'Space' );
- const isActiveBlockTab = await page.evaluate(
- () =>
- document.activeElement.textContent === 'Block' &&
- document.activeElement.classList.contains( 'is-active' )
+
+ // The Block tab should be focused and selected.
+ const [ blockInspectorTab ] = await page.$x(
+ '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]'
);
- expect( isActiveBlockTab ).toBe( true );
+ expect( blockInspectorTab ).toBeDefined();
+ expect( blockInspectorTab ).toHaveFocus();
} );
it( 'should be possible to programmatically remove Document Settings panels', async () => {
diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js
index ef32450e7209f..368bd3e9e50db 100644
--- a/packages/edit-post/src/components/sidebar/settings-header/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-header/index.js
@@ -1,22 +1,20 @@
/**
* WordPress dependencies
*/
-import { Button } from '@wordpress/components';
-import { __, _x, sprintf } from '@wordpress/i18n';
-import { useDispatch, useSelect } from '@wordpress/data';
+import { privateApis as componentsPrivateApis } from '@wordpress/components';
+import { __, _x } from '@wordpress/i18n';
+import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
/**
* Internal dependencies
*/
-import { store as editPostStore } from '../../../store';
+import { unlock } from '../../../lock-unlock';
+import { sidebars } from '../settings-sidebar';
-const SettingsHeader = ( { sidebarName } ) => {
- const { openGeneralSidebar } = useDispatch( editPostStore );
- const openDocumentSettings = () =>
- openGeneralSidebar( 'edit-post/document' );
- const openBlockSettings = () => openGeneralSidebar( 'edit-post/block' );
+const { Tabs } = unlock( componentsPrivateApis );
+const SettingsHeader = () => {
const { documentLabel, isTemplateMode } = useSelect( ( select ) => {
const { getPostTypeLabel, getRenderingMode } = select( editorStore );
@@ -27,66 +25,16 @@ const SettingsHeader = ( { sidebarName } ) => {
};
}, [] );
- const [ documentAriaLabel, documentActiveClass ] =
- sidebarName === 'edit-post/document'
- ? // translators: ARIA label for the Document sidebar tab, selected. %s: Document label.
- [ sprintf( __( '%s (selected)' ), documentLabel ), 'is-active' ]
- : [ documentLabel, '' ];
-
- const [ blockAriaLabel, blockActiveClass ] =
- sidebarName === 'edit-post/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' ), '' ];
-
- const [ templateAriaLabel, templateActiveClass ] =
- sidebarName === 'edit-post/document'
- ? [ __( 'Template (selected)' ), 'is-active' ]
- : [ __( 'Template' ), '' ];
-
- /* Use a list so screen readers will announce how many tabs there are. */
return (
-
- { ! isTemplateMode && (
- -
-
-
- ) }
- { isTemplateMode && (
- -
-
-
- ) }
- -
-
-
-
+
+
+ { isTemplateMode ? __( 'Template' ) : documentLabel }
+
+
+ { /* translators: Text label for the Block Settings Sidebar tab. */ }
+ { __( 'Block' ) }
+
+
);
};
diff --git a/packages/edit-post/src/components/sidebar/settings-header/style.scss b/packages/edit-post/src/components/sidebar/settings-header/style.scss
deleted file mode 100644
index aaf7698cb6ddb..0000000000000
--- a/packages/edit-post/src/components/sidebar/settings-header/style.scss
+++ /dev/null
@@ -1,74 +0,0 @@
-// This tab style CSS is duplicated verbatim in
-// /packages/components/src/tab-panel/style.scss
-.components-button.edit-post-sidebar__panel-tab {
- position: relative;
- border-radius: 0;
- height: $grid-unit-60;
- background: transparent;
- border: none;
- box-shadow: none;
- cursor: pointer;
- padding: 3px $grid-unit-20; // Use padding to offset the is-active border, this benefits Windows High Contrast mode
- margin-left: 0;
- font-weight: 500;
-
- &:focus:not(:disabled) {
- position: relative;
- box-shadow: none;
- outline: none;
- }
-
- // Tab indicator
- &::after {
- content: "";
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- pointer-events: none;
-
- // Draw the indicator.
- background: var(--wp-admin-theme-color);
- height: calc(0 * var(--wp-admin-border-width-focus));
- border-radius: 0;
-
- // Animation
- transition: all 0.1s linear;
- @include reduce-motion("transition");
- }
-
- // Active.
- &.is-active::after {
- height: calc(1 * var(--wp-admin-border-width-focus));
-
- // Windows high contrast mode.
- outline: 2px solid transparent;
- outline-offset: -1px;
- }
-
- // Focus.
- &::before {
- content: "";
- position: absolute;
- top: $grid-unit-15;
- right: $grid-unit-15;
- bottom: $grid-unit-15;
- left: $grid-unit-15;
- pointer-events: none;
-
- // Draw the indicator.
- box-shadow: 0 0 0 0 transparent;
- border-radius: $radius-block-ui;
-
- // Animation
- transition: all 0.1s linear;
- @include reduce-motion("transition");
- }
-
- &:focus-visible::before {
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
-
- // Windows high contrast mode.
- outline: 2px solid transparent;
- }
-}
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 e566ea400c12b..9fa27c6ac2ade 100644
--- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
@@ -5,8 +5,8 @@ import {
BlockInspector,
store as blockEditorStore,
} from '@wordpress/block-editor';
-import { useSelect } from '@wordpress/data';
-import { Platform } from '@wordpress/element';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { Platform, useCallback, useContext } from '@wordpress/element';
import { isRTL, __ } from '@wordpress/i18n';
import { drawerLeft, drawerRight } from '@wordpress/icons';
import { store as interfaceStore } from '@wordpress/interface';
@@ -29,54 +29,43 @@ import PluginDocumentSettingPanel from '../plugin-document-setting-panel';
import PluginSidebarEditPost from '../plugin-sidebar';
import TemplateSummary from '../template-summary';
import { store as editPostStore } from '../../../store';
+import { privateApis as componentsPrivateApis } from '@wordpress/components';
+import { unlock } from '../../../lock-unlock';
+
+const { Tabs } = unlock( componentsPrivateApis );
const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( {
web: true,
native: false,
} );
+export const sidebars = {
+ document: 'edit-post/document',
+ block: 'edit-post/block',
+};
-const SettingsSidebar = () => {
- const { sidebarName, keyboardShortcut, isTemplateMode } = useSelect(
- ( select ) => {
- // The settings sidebar is used by the edit-post/document and edit-post/block sidebars.
- // sidebarName represents the sidebar that is active or that should be active when the SettingsSidebar toggle button is pressed.
- // If one of the two sidebars is active the component will contain the content of that sidebar.
- // When neither of the two sidebars is active we can not simply return null, because the PluginSidebarEditPost
- // component, besides being used to render the sidebar, also renders the toggle button. In that case sidebarName
- // should contain the sidebar that will be active when the toggle button is pressed. If a block
- // is selected, that should be edit-post/block otherwise it's edit-post/document.
- let sidebar = select( interfaceStore ).getActiveComplementaryArea(
- editPostStore.name
- );
- if (
- ! [ 'edit-post/document', 'edit-post/block' ].includes(
- sidebar
- )
- ) {
- if ( select( blockEditorStore ).getBlockSelectionStart() ) {
- sidebar = 'edit-post/block';
- }
- sidebar = 'edit-post/document';
- }
- const shortcut = select(
- keyboardShortcutsStore
- ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' );
- return {
- sidebarName: sidebar,
- keyboardShortcut: shortcut,
- isTemplateMode:
- select( editorStore ).getRenderingMode() ===
- 'template-only',
- };
- },
- []
- );
+const SidebarContent = ( {
+ sidebarName,
+ keyboardShortcut,
+ isTemplateMode,
+} ) => {
+ // Because `PluginSidebarEditPost` renders a `ComplementaryArea`, we
+ // need to forward the `Tabs` context so it can be passed through the
+ // underlying slot/fill.
+ const tabsContextValue = useContext( Tabs.Context );
return (
}
+ header={
+
+
+
+ }
closeLabel={ __( 'Close Settings' ) }
+ // This classname is added so we can apply a corrective negative
+ // margin to the panel.
+ // see https://github.com/WordPress/gutenberg/pull/55360#pullrequestreview-1737671049
+ className="edit-post-sidebar__panel"
headerClassName="edit-post-sidebar__panel-tabs"
/* translators: button label text should, if possible, be under 16 characters. */
title={ __( 'Settings' ) }
@@ -84,25 +73,96 @@ const SettingsSidebar = () => {
icon={ isRTL() ? drawerLeft : drawerRight }
isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT }
>
- { ! isTemplateMode && sidebarName === 'edit-post/document' && (
- <>
-
-
-
-
-
-
-
-
-
- >
- ) }
- { isTemplateMode && sidebarName === 'edit-post/document' && (
-
- ) }
- { sidebarName === 'edit-post/block' && }
+
+
+ { ! isTemplateMode && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ ) }
+ { isTemplateMode && }
+
+
+
+
+
);
};
+const SettingsSidebar = () => {
+ const {
+ sidebarName,
+ isSettingsSidebarActive,
+ keyboardShortcut,
+ isTemplateMode,
+ } = useSelect( ( select ) => {
+ // The settings sidebar is used by the edit-post/document and edit-post/block sidebars.
+ // sidebarName represents the sidebar that is active or that should be active when the SettingsSidebar toggle button is pressed.
+ // If one of the two sidebars is active the component will contain the content of that sidebar.
+ // When neither of the two sidebars is active we can not simply return null, because the PluginSidebarEditPost
+ // component, besides being used to render the sidebar, also renders the toggle button. In that case sidebarName
+ // should contain the sidebar that will be active when the toggle button is pressed. If a block
+ // is selected, that should be edit-post/block otherwise it's edit-post/document.
+ let sidebar = select( interfaceStore ).getActiveComplementaryArea(
+ editPostStore.name
+ );
+ let isSettingsSidebar = true;
+ if ( ! [ sidebars.document, sidebars.block ].includes( sidebar ) ) {
+ isSettingsSidebar = false;
+ if ( select( blockEditorStore ).getBlockSelectionStart() ) {
+ sidebar = sidebars.block;
+ }
+ sidebar = sidebars.document;
+ }
+ const shortcut = select(
+ keyboardShortcutsStore
+ ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' );
+ return {
+ sidebarName: sidebar,
+ isSettingsSidebarActive: isSettingsSidebar,
+ keyboardShortcut: shortcut,
+ isTemplateMode:
+ select( editorStore ).getRenderingMode() === 'template-only',
+ };
+ }, [] );
+
+ const { openGeneralSidebar } = useDispatch( editPostStore );
+
+ const onTabSelect = useCallback(
+ ( newSelectedTabId ) => {
+ if ( !! newSelectedTabId ) {
+ openGeneralSidebar( newSelectedTabId );
+ }
+ },
+ [ openGeneralSidebar ]
+ );
+
+ return (
+
+
+
+ );
+};
+
export default SettingsSidebar;
diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss
index 7b10eaec0d224..1921c5cfd7b31 100644
--- a/packages/edit-post/src/components/sidebar/style.scss
+++ b/packages/edit-post/src/components/sidebar/style.scss
@@ -1,20 +1,8 @@
.components-panel__header.edit-post-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;
@@ -24,3 +12,7 @@
}
}
}
+
+.edit-post-sidebar__panel {
+ margin-top: -1px;
+}
diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss
index 53219bc6a3736..88916bf70f76d 100644
--- a/packages/edit-post/src/style.scss
+++ b/packages/edit-post/src/style.scss
@@ -12,7 +12,6 @@
@import "./components/sidebar/post-format/style.scss";
@import "./components/sidebar/post-slug/style.scss";
@import "./components/sidebar/post-visibility/style.scss";
-@import "./components/sidebar/settings-header/style.scss";
@import "./components/sidebar/template-summary/style.scss";
@import "./components/text-editor/style.scss";
@import "./components/visual-editor/style.scss";
diff --git a/test/e2e/specs/editor/plugins/custom-post-types.spec.js b/test/e2e/specs/editor/plugins/custom-post-types.spec.js
index 17a497f26cee0..01dde03650ef7 100644
--- a/test/e2e/specs/editor/plugins/custom-post-types.spec.js
+++ b/test/e2e/specs/editor/plugins/custom-post-types.spec.js
@@ -31,7 +31,7 @@ test.describe( 'Test Custom Post Types', () => {
await editor.openDocumentSettingsSidebar();
await page
.getByRole( 'region', { name: 'Editor settings' } )
- .getByRole( 'button', {
+ .getByRole( 'tab', {
name: 'Hierarchical No Title',
} )
.click();
diff --git a/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js b/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js
index f0bfe5bff203f..a695b0a9ead67 100644
--- a/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js
+++ b/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js
@@ -127,7 +127,7 @@ test.describe( 'Navigating the block hierarchy', () => {
await pageUtils.pressKeys( 'ctrl+`' );
// Navigate to the block settings sidebar and tweak the column count.
- await pageUtils.pressKeys( 'Tab', { times: 5 } );
+ await pageUtils.pressKeys( 'Tab', { times: 4 } );
await expect(
page.getByRole( 'slider', { name: 'Columns' } )
).toBeFocused();
diff --git a/test/e2e/specs/editor/various/footnotes.spec.js b/test/e2e/specs/editor/various/footnotes.spec.js
index 14a2fc653e387..6102f48749543 100644
--- a/test/e2e/specs/editor/various/footnotes.spec.js
+++ b/test/e2e/specs/editor/various/footnotes.spec.js
@@ -362,7 +362,7 @@ test.describe( 'Footnotes', () => {
await editor.openDocumentSettingsSidebar();
await page
.getByRole( 'region', { name: 'Editor settings' } )
- .getByRole( 'button', { name: 'Post' } )
+ .getByRole( 'tab', { name: 'Post' } )
.click();
await page.locator( 'a:text("2 Revisions")' ).click();
await page.locator( '.revisions-controls .ui-slider-handle' ).focus();
@@ -440,7 +440,7 @@ test.describe( 'Footnotes', () => {
await editor.openDocumentSettingsSidebar();
await page
.getByRole( 'region', { name: 'Editor settings' } )
- .getByRole( 'button', { name: 'Post' } )
+ .getByRole( 'tab', { name: 'Post' } )
.click();
// Visit the published post.
diff --git a/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js b/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js
index 080abe011206a..84536c88227ce 100644
--- a/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js
+++ b/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js
@@ -75,9 +75,7 @@ test.describe( 'Order of block keyboard navigation', () => {
);
await page.keyboard.press( 'Tab' );
- await KeyboardNavigableBlocks.expectLabelToHaveFocus(
- 'Post (selected)'
- );
+ await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Post' );
} );
test( 'allows tabbing in navigation mode if no block is selected (reverse)', async ( {
@@ -151,7 +149,7 @@ test.describe( 'Order of block keyboard navigation', () => {
);
await page.keyboard.press( 'Tab' );
- await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Post' );
+ await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Block' );
await pageUtils.pressKeys( 'shift+Tab' );
await KeyboardNavigableBlocks.expectLabelToHaveFocus(
@@ -233,7 +231,7 @@ class KeyboardNavigableBlocks {
await expect( activeElement ).toHaveText( paragraphText );
await this.page.keyboard.press( 'Tab' );
- await this.expectLabelToHaveFocus( 'Post' );
+ await this.expectLabelToHaveFocus( 'Block' );
// Need to shift+tab here to end back in the block. If not, we'll be in the next region and it will only require 4 region jumps instead of 5.
await this.pageUtils.pressKeys( 'shift+Tab' );