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;