diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index c945e2bf99a70a..8f232cb0a35e6f 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -17,6 +17,7 @@ @import "./classic/editor.scss"; @import "./gallery/editor.scss"; @import "./group/editor.scss"; +@import "./heading/editor.scss"; @import "./html/editor.scss"; @import "./image/editor.scss"; @import "./latest-comments/editor.scss"; diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 524eee735b57c9..d4a353c1b0ead4 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -3,25 +3,23 @@ */ import classnames from 'classnames'; -/** - * Internal dependencies - */ -import HeadingToolbar from './heading-toolbar'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { PanelBody, __experimentalText as Text } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; import { AlignmentToolbar, BlockControls, - InspectorControls, RichText, __experimentalBlock as Block, } from '@wordpress/block-editor'; -import { Platform } from '@wordpress/element'; +import { ToolbarGroup } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import HeadingLevelDropdown from './heading-level-dropdown'; function HeadingEdit( { attributes, @@ -36,14 +34,14 @@ function HeadingEdit( { return ( <> - - setAttributes( { level: newLevel } ) - } - /> + + + setAttributes( { level: newLevel } ) + } + /> + { @@ -51,22 +49,6 @@ function HeadingEdit( { } } /> - { Platform.OS === 'web' && ( - - - { __( 'Level' ) } - - setAttributes( { level: newLevel } ) - } - /> - - - ) } any} onChange Callback to run when + * toolbar value is changed. + */ + +/** + * Dropdown for selecting a heading level (1 through 6). + * + * @param {WPHeadingLevelDropdownProps} props Component props. + * + * @return {WPComponent} The toolbar. + */ +export default function HeadingLevelDropdown( { selectedLevel, onChange } ) { + return ( + { + const openOnArrowDown = ( event ) => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + + return ( + } + label={ __( 'Change heading level' ) } + onClick={ onToggle } + onKeyDown={ openOnArrowDown } + showTooltip + /> + ); + } } + renderContent={ () => ( + + { + const isActive = targetLevel === selectedLevel; + return { + icon: ( + + ), + title: sprintf( + // translators: %s: heading level e.g: "1", "2", "3" + __( 'Heading %d' ), + targetLevel + ), + isActive, + onClick() { + onChange( targetLevel ); + }, + // Temporary workaround for macOS Firefox/Safari issue + // where clicking buttons in the heading level toolbar + // doesn't work. + // TODO: Replace this with a more general solution. + // https://github.com/WordPress/gutenberg/pull/20246#pullrequestreview-417338057 + onMouseDown( event ) { + event.preventDefault(); + event.currentTarget.focus(); + }, + }; + } ) } + /> + + ) } + /> + ); +} diff --git a/packages/block-library/src/heading/heading-level-dropdown.native.js b/packages/block-library/src/heading/heading-level-dropdown.native.js new file mode 100644 index 00000000000000..e24374c86b995c --- /dev/null +++ b/packages/block-library/src/heading/heading-level-dropdown.native.js @@ -0,0 +1,63 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { DropdownMenu } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import HeadingLevelIcon from './heading-level-icon'; + +const HEADING_LEVELS = [ 1, 2, 3, 4, 5, 6 ]; + +/** @typedef {import('@wordpress/element').WPComponent} WPComponent */ + +/** + * HeadingLevelDropdown props. + * + * @typedef WPHeadingLevelDropdownProps + * + * @property {number} selectedLevel The chosen heading level. + * @property {(newValue:number)=>any} onChange Callback to run when + * toolbar value is changed. + */ + +/** + * Dropdown for selecting a heading level (1 through 6). + * + * @param {WPHeadingLevelDropdownProps} props Component props. + * + * @return {WPComponent} The toolbar. + */ +export default function HeadingLevelDropdown( { selectedLevel, onChange } ) { + const createLevelControl = ( + targetLevel, + currentLevel, + onChangeCallback + ) => { + const isActive = targetLevel === currentLevel; + return { + icon: ( + + ), + // translators: %s: heading level e.g: "1", "2", "3" + title: sprintf( __( 'Heading %d' ), targetLevel ), + isActive, + onClick: () => onChangeCallback( targetLevel ), + }; + }; + + return ( + } + controls={ HEADING_LEVELS.map( ( index ) => + createLevelControl( index, selectedLevel, onChange ) + ) } + label={ __( 'Change heading level' ) } + /> + ); +} diff --git a/packages/block-library/src/heading/heading-level-icon.js b/packages/block-library/src/heading/heading-level-icon.js index eef2b3af3b5e7e..b3288d02761612 100644 --- a/packages/block-library/src/heading/heading-level-icon.js +++ b/packages/block-library/src/heading/heading-level-icon.js @@ -3,6 +3,24 @@ */ import { Path, SVG } from '@wordpress/components'; +/** @typedef {import('@wordpress/element').WPComponent} WPComponent */ + +/** + * HeadingLevelIcon props. + * + * @typedef WPHeadingLevelIconProps + * + * @property {number} level The heading level to show an icon for. + * @property {?boolean} isPressed Whether or not the icon should appear pressed; default: false. + */ + +/** + * Heading level icon. + * + * @param {WPHeadingLevelIconProps} props Component props. + * + * @return {?WPComponent} The icon. + */ export default function HeadingLevelIcon( { level, isPressed = false } ) { const levelToPath = { 1: 'M9 5h2v10H9v-4H5v4H3V5h2v4h4V5zm6.6 0c-.6.9-1.5 1.7-2.6 2v1h2v7h2V5h-1.4z', diff --git a/packages/block-library/src/heading/heading-toolbar.js b/packages/block-library/src/heading/heading-toolbar.js deleted file mode 100644 index 97cd4f12cfe23c..00000000000000 --- a/packages/block-library/src/heading/heading-toolbar.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * External dependencies - */ -import { range } from 'lodash'; - -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { ToolbarGroup } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import HeadingLevelIcon from './heading-level-icon'; - -class HeadingToolbar extends Component { - createLevelControl( targetLevel, selectedLevel, onChange ) { - const isActive = targetLevel === selectedLevel; - return { - icon: ( - - ), - // translators: %s: heading level e.g: "1", "2", "3" - title: sprintf( __( 'Heading %d' ), targetLevel ), - isActive, - onClick: () => onChange( targetLevel ), - }; - } - - render() { - const { - isCollapsed = true, - minLevel, - maxLevel, - selectedLevel, - onChange, - } = this.props; - - return ( - } - controls={ range( minLevel, maxLevel ).map( ( index ) => - this.createLevelControl( index, selectedLevel, onChange ) - ) } - label={ __( 'Change heading level' ) } - /> - ); - } -} - -export default HeadingToolbar; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index b8fb471363c60d..6f84458de4f87b 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -12,6 +12,7 @@ export { default as ColorIndicator } from './color-indicator'; export { default as ColorPalette } from './color-palette'; export { default as Dashicon } from './dashicon'; export { default as Dropdown } from './dropdown'; +export { default as DropdownMenu } from './dropdown-menu'; export { default as Toolbar } from './toolbar'; export { default as ToolbarButton } from './toolbar-button'; export { default as __experimentalToolbarContext } from './toolbar-context'; diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index 8135962e22556e..1ac2080a1631cb 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -7,7 +7,6 @@ import { insertBlock, clickBlockAppender, pressKeyWithModifier, - openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; describe( 'RichText', () => { @@ -23,7 +22,8 @@ describe( 'RichText', () => { // // See: https://github.com/WordPress/gutenberg/issues/3091 await insertBlock( 'Heading' ); - await openDocumentSettingsSidebar(); + await page.waitForSelector( '[aria-label="Change heading level"]' ); + await page.click( '[aria-label="Change heading level"]' ); await page.click( '[aria-label="Heading 3"]' ); expect( await getEditedPostContent() ).toMatchSnapshot();