diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php
index b0c05544d6b19..862a1bfe2b28c 100644
--- a/lib/experimental/editor-settings.php
+++ b/lib/experimental/editor-settings.php
@@ -86,6 +86,9 @@ function gutenberg_enable_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-off-canvas-navigation-editor', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableOffCanvasNavigationEditor = true', 'before' );
}
+ if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-inspector-tabs', $gutenberg_experiments ) ) {
+ wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockInspectorTabs = true', 'before' );
+ }
}
add_action( 'admin_init', 'gutenberg_enable_experiments' );
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index 309612664cb9c..1296cdd03b89f 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -52,6 +52,7 @@ function gutenberg_initialize_experiments_settings() {
'id' => 'gutenberg-zoomed-out-view',
)
);
+
add_settings_field(
'gutenberg-off-canvas-navigation-editor',
__( 'Off canvas navigation editor ', 'gutenberg' ),
@@ -63,6 +64,7 @@ function gutenberg_initialize_experiments_settings() {
'id' => 'gutenberg-off-canvas-navigation-editor',
)
);
+
add_settings_field(
'gutenberg-color-randomizer',
__( 'Color randomizer ', 'gutenberg' ),
@@ -75,6 +77,18 @@ function gutenberg_initialize_experiments_settings() {
)
);
+ add_settings_field(
+ 'gutenberg-block-inspector-tabs',
+ __( 'Block inspector tabs ', 'gutenberg' ),
+ 'gutenberg_display_experiment_field',
+ 'gutenberg-experiments',
+ 'gutenberg_experiments_section',
+ array(
+ 'label' => __( 'Test a new block inspector view splitting settings and appearance controls into tabs', 'gutenberg' ),
+ 'id' => 'gutenberg-block-inspector-tabs',
+ )
+ );
+
register_setting(
'gutenberg-experiments',
'gutenberg-experiments'
diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js
index 379d8806f2a68..f48819ec4a02b 100644
--- a/packages/block-editor/src/components/block-inspector/index.js
+++ b/packages/block-editor/src/components/block-inspector/index.js
@@ -9,9 +9,8 @@ import {
store as blocksStore,
} from '@wordpress/blocks';
import {
- PanelBody,
- __experimentalUseSlotFills as useSlotFills,
FlexItem,
+ PanelBody,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
Button,
@@ -24,20 +23,19 @@ import { useMemo, useCallback } from '@wordpress/element';
*/
import SkipToSelectedBlock from '../skip-to-selected-block';
import BlockCard from '../block-card';
-import {
- default as InspectorControls,
- InspectorAdvancedControls,
-} from '../inspector-controls';
-import BlockStyles from '../block-styles';
import MultiSelectionInspector from '../multi-selection-inspector';
-import DefaultStylePicker from '../default-style-picker';
import BlockVariationTransforms from '../block-variation-transforms';
import useBlockDisplayInformation from '../use-block-display-information';
import { store as blockEditorStore } from '../../store';
import BlockIcon from '../block-icon';
+import BlockStyles from '../block-styles';
+import DefaultStylePicker from '../default-style-picker';
+import { default as InspectorControls } from '../inspector-controls';
+import { default as InspectorControlsTabs } from '../inspector-controls-tabs';
+import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel';
function useContentBlocks( blockTypes, block ) {
- const contenBlocksObjectAux = useMemo( () => {
+ const contentBlocksObjectAux = useMemo( () => {
return blockTypes.reduce( ( result, blockType ) => {
if (
blockType.name !== 'core/list-item' &&
@@ -53,7 +51,7 @@ function useContentBlocks( blockTypes, block ) {
}, [ blockTypes ] );
const isContentBlock = useCallback(
( blockName ) => {
- return !! contenBlocksObjectAux[ blockName ];
+ return !! contentBlocksObjectAux[ blockName ];
},
[ blockTypes ]
);
@@ -166,28 +164,36 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
};
}, [] );
+ const showTabs = window?.__experimentalEnableBlockInspectorTabs;
+
if ( count > 1 ) {
return (
-
-
-
-
-
+ { showTabs ? (
+
+ ) : (
+ <>
+
+
+
+
+
+ >
+ ) }
);
}
@@ -229,6 +235,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
};
const BlockInspectorSingleBlock = ( { clientId, blockName } ) => {
+ const showTabs = window?.__experimentalEnableBlockInspectorTabs;
+
const hasBlockStyles = useSelect(
( select ) => {
const { getBlockStyles } = select( blocksStore );
@@ -238,67 +246,64 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => {
[ blockName ]
);
const blockInformation = useBlockDisplayInformation( clientId );
+
return (
- { hasBlockStyles && (
-
-
-
- { hasBlockSupport(
- blockName,
- 'defaultStylePicker',
- true
- ) && }
-
-
+ { showTabs && (
+
+ ) }
+ { ! showTabs && (
+ <>
+ { hasBlockStyles && (
+
+
+
+ { hasBlockSupport(
+ blockName,
+ 'defaultStylePicker',
+ true
+ ) && (
+
+ ) }
+
+
+ ) }
+
+
+
+
+
+
+ >
) }
-
-
-
-
-
-
);
};
-const AdvancedControls = () => {
- const fills = useSlotFills( InspectorAdvancedControls.slotName );
- const hasFills = Boolean( fills && fills.length );
-
- if ( ! hasFills ) {
- return null;
- }
-
- return (
-
-
-
- );
-};
-
/**
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-inspector/README.md
*/
diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss
index b7cfdcf46333a..08ca013629927 100644
--- a/packages/block-editor/src/components/block-inspector/style.scss
+++ b/packages/block-editor/src/components/block-inspector/style.scss
@@ -27,7 +27,8 @@
}
}
-.block-editor-block-inspector__no-blocks {
+.block-editor-block-inspector__no-blocks,
+.block-editor-block-inspector__no-block-tools {
display: block;
font-size: $default-font-size;
background: $white;
@@ -35,6 +36,13 @@
text-align: center;
}
+.block-editor-block-inspector__no-block-tools {
+ border-top: $border-width solid $gray-300;
+}
+
+.block-editor-block-inspector__tab-item {
+ flex: 1 1 0px;
+}
.block-editor-block-inspector__block-buttons-container {
border-top: $border-width solid $gray-200;
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js
new file mode 100644
index 0000000000000..83027861e9d19
--- /dev/null
+++ b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ PanelBody,
+ __experimentalUseSlotFills as useSlotFills,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import {
+ default as InspectorControls,
+ InspectorAdvancedControls,
+} from '../inspector-controls';
+
+const AdvancedControls = () => {
+ const fills = useSlotFills( InspectorAdvancedControls.slotName );
+ const hasFills = Boolean( fills && fills.length );
+
+ if ( ! hasFills ) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default AdvancedControls;
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js
new file mode 100644
index 0000000000000..deb3bccffaa16
--- /dev/null
+++ b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js
@@ -0,0 +1,75 @@
+/**
+ * WordPress dependencies
+ */
+import { hasBlockSupport } from '@wordpress/blocks';
+import {
+ PanelBody,
+ __experimentalUseSlotFills as useSlotFills,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import BlockStyles from '../block-styles';
+import DefaultStylePicker from '../default-style-picker';
+import InspectorControls from '../inspector-controls';
+import InspectorControlsGroups from '../inspector-controls/groups';
+
+const AppearanceTab = ( {
+ blockName,
+ clientId,
+ hasBlockStyles,
+ hasSingleBlockSelection = false,
+} ) => {
+ const { border, color, dimensions, typography } = InspectorControlsGroups;
+ const appearanceFills = [
+ ...( useSlotFills( border.Slot.__unstableName ) || [] ),
+ ...( useSlotFills( color.Slot.__unstableName ) || [] ),
+ ...( useSlotFills( dimensions.Slot.__unstableName ) || [] ),
+ ...( useSlotFills( typography.Slot.__unstableName ) || [] ),
+ ];
+
+ return (
+ <>
+ { ! appearanceFills.length && (
+
+ { hasSingleBlockSelection
+ ? __( 'This block has no style options.' )
+ : __( 'The selected blocks have no style options.' ) }
+
+ ) }
+ { hasBlockStyles && (
+
+
+
+ { hasBlockSupport(
+ blockName,
+ 'defaultStylePicker',
+ true
+ ) && }
+
+
+ ) }
+
+
+
+
+ >
+ );
+};
+
+export default AppearanceTab;
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js
new file mode 100644
index 0000000000000..62b87dd58e11c
--- /dev/null
+++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js
@@ -0,0 +1,42 @@
+/**
+ * WordPress dependencies
+ */
+import { TabPanel } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { TAB_SETTINGS, TAB_APPEARANCE } from './utils';
+import AppearanceTab from './appearance-tab';
+import SettingsTab from './settings-tab';
+
+const tabs = [ TAB_APPEARANCE, TAB_SETTINGS ];
+
+export default function InspectorControlsTabs( {
+ blockName,
+ clientId,
+ hasBlockStyles,
+} ) {
+ return (
+
+ { ( tab ) => {
+ if ( tab.name === TAB_SETTINGS.name ) {
+ return (
+
+ );
+ }
+
+ if ( tab.name === TAB_APPEARANCE.name ) {
+ return (
+
+ );
+ }
+ } }
+
+ );
+}
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js
new file mode 100644
index 0000000000000..a000173807c19
--- /dev/null
+++ b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js
@@ -0,0 +1,43 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import AdvancedControls from './advanced-controls-panel';
+import InspectorControlsGroups from '../inspector-controls/groups';
+import {
+ default as InspectorControls,
+ InspectorAdvancedControls,
+} from '../inspector-controls';
+
+const SettingsTab = ( { hasSingleBlockSelection = false } ) => {
+ const { default: defaultGroup } = InspectorControlsGroups;
+ const settingsFills = [
+ ...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ),
+ ...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ),
+ ];
+
+ return (
+ <>
+
+ { hasSingleBlockSelection && (
+
+ ) }
+ { ! settingsFills.length && (
+
+ { hasSingleBlockSelection
+ ? __( 'This block has no settings.' )
+ : __( 'The selected blocks have no settings.' ) }
+
+ ) }
+ >
+ );
+};
+
+export default SettingsTab;
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js
new file mode 100644
index 0000000000000..0bec1088174d3
--- /dev/null
+++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js
@@ -0,0 +1,20 @@
+/**
+ * WordPress dependencies
+ */
+import { cog, styles } from '@wordpress/icons';
+
+export const TAB_SETTINGS = {
+ name: 'settings',
+ title: 'Settings',
+ value: 'settings',
+ icon: cog,
+ className: 'block-editor-block-inspector__tab-item',
+};
+
+export const TAB_APPEARANCE = {
+ name: 'appearance',
+ title: 'Appearance',
+ value: 'appearance',
+ icon: styles,
+ className: 'block-editor-block-inspector__tab-item',
+};
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 62169314631bd..77cd8697cd866 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -7,6 +7,7 @@
### Enhancements
- `ColorPalette`, `BorderBox`, `BorderBoxControl`: polish and DRY prop types, add default values ([#45463](https://github.com/WordPress/gutenberg/pull/45463)).
+- `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)).
### Bug Fix
diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md
index a4a1c0c525370..c3e889a2bbf33 100644
--- a/packages/components/src/tab-panel/README.md
+++ b/packages/components/src/tab-panel/README.md
@@ -120,6 +120,7 @@ An array of objects containing the following properties:
- `name`: `(string)` Defines the key for the tab.
- `title`:`(string)` Defines the translated text for the tab.
- `className`:`(string)` Optional. Defines the class to put on the tab.
+- `icon`:`(ReactNode)` Optional. When set, displays the icon in place of the tab title. The title is then rendered as an aria-label and tooltip.
> > **Note:** Other fields may be added to the object and accessed from the child function if desired.
diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx
index 206c2b8aab0a7..dfd3ebd945519 100644
--- a/packages/components/src/tab-panel/index.tsx
+++ b/packages/components/src/tab-panel/index.tsx
@@ -127,8 +127,11 @@ export function TabPanel( {
selected={ tab.name === selected }
key={ tab.name }
onClick={ () => handleTabSelection( tab.name ) }
+ label={ tab.icon && tab.title }
+ icon={ tab.icon }
+ showTooltip={ !! tab.icon }
>
- { tab.title }
+ { ! tab.icon && tab.title }
) ) }
diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts
index 2a89da6a215b7..1436b8034a66a 100644
--- a/packages/components/src/tab-panel/types.ts
+++ b/packages/components/src/tab-panel/types.ts
@@ -3,6 +3,11 @@
*/
import type { ReactNode } from 'react';
+/**
+ * Internal dependencies
+ */
+import type { IconType } from '../icon';
+
type Tab = {
/**
* The key of the tab.
@@ -18,11 +23,14 @@ type Tab = {
className?: string;
} & Record< any, any >;
-export type TabButtonProps = {
+export type TabButtonProps< IconProps = unknown > = {
children: ReactNode;
className?: string;
+ icon?: IconType< IconProps >;
+ label?: string;
onClick: ( event: MouseEvent ) => void;
selected: boolean;
+ showTooltip?: boolean;
tabId: string;
};
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 c044d0b49714c..677161c6b5a38 100644
--- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
+++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js
@@ -5,10 +5,12 @@ import {
BlockInspector,
store as blockEditorStore,
} from '@wordpress/block-editor';
-import { cog } from '@wordpress/icons';
+import { useSelect } from '@wordpress/data';
import { Platform } from '@wordpress/element';
-import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
+import { isRTL, __ } from '@wordpress/i18n';
+import { drawerLeft, drawerRight } from '@wordpress/icons';
import { store as interfaceStore } from '@wordpress/interface';
+import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
/**
* Internal dependencies
@@ -25,8 +27,6 @@ import MetaBoxes from '../../meta-boxes';
import PluginDocumentSettingPanel from '../plugin-document-setting-panel';
import PluginSidebarEditPost from '../plugin-sidebar';
import TemplateSummary from '../template-summary';
-import { __ } from '@wordpress/i18n';
-import { useSelect } from '@wordpress/data';
import { store as editPostStore } from '../../../store';
const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( {
@@ -78,7 +78,7 @@ const SettingsSidebar = () => {
/* translators: button label text should, if possible, be under 16 characters. */
title={ __( 'Settings' ) }
toggleShortcut={ keyboardShortcut }
- icon={ cog }
+ icon={ isRTL() ? drawerLeft : drawerRight }
isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT }
>
{ ! isTemplateMode && sidebarName === 'edit-post/document' && (
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/index.js b/packages/edit-site/src/components/sidebar-edit-mode/index.js
index 080be432eb09d..12073a77b56e0 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/index.js
+++ b/packages/edit-site/src/components/sidebar-edit-mode/index.js
@@ -2,8 +2,8 @@
* WordPress dependencies
*/
import { createSlotFill, PanelBody } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import { cog } from '@wordpress/icons';
+import { isRTL, __ } from '@wordpress/i18n';
+import { drawerLeft, drawerRight } from '@wordpress/icons';
import { useEffect, Fragment } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';
@@ -78,7 +78,7 @@ export function SidebarComplementaryAreaFills() {
}
headerClassName="edit-site-sidebar-edit-mode__panel-tabs"
diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js
index c7f891a5292db..5870ccba9ab04 100644
--- a/packages/icons/src/index.js
+++ b/packages/icons/src/index.js
@@ -66,6 +66,8 @@ export { default as currencyPound } from './library/currency-pound';
export { default as customPostType } from './library/custom-post-type';
export { default as desktop } from './library/desktop';
export { default as dragHandle } from './library/drag-handle';
+export { default as drawerLeft } from './library/drawer-left';
+export { default as drawerRight } from './library/drawer-right';
export { default as download } from './library/download';
export { default as edit } from './library/edit';
export { default as external } from './library/external';
diff --git a/packages/icons/src/library/drawer-left.js b/packages/icons/src/library/drawer-left.js
new file mode 100644
index 0000000000000..2e1626fb1fe17
--- /dev/null
+++ b/packages/icons/src/library/drawer-left.js
@@ -0,0 +1,21 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const drawerLeft = (
+
+);
+
+export default drawerLeft;
diff --git a/packages/icons/src/library/drawer-right.js b/packages/icons/src/library/drawer-right.js
new file mode 100644
index 0000000000000..95a8e72f775fb
--- /dev/null
+++ b/packages/icons/src/library/drawer-right.js
@@ -0,0 +1,21 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const drawerRight = (
+
+);
+
+export default drawerRight;