Skip to content

Commit

Permalink
Heading block: use toolbar exclusively for heading level control (#20246
Browse files Browse the repository at this point in the history
)

* Heading block: use toolbar only for level control.

* Fix the heading toolbar for RN mobile.

Fix lint errors.

Change the native side to use a dropDown menu directly.

* Fix Safari horizontal scrollbar issue.

* Simplify to use ToolbarButton.

* Workaround for Firefox/Safari button focus issue.

Co-authored-by: Sergio Estevao <sergioestevao@gmail.com>
  • Loading branch information
ZebulanStanphill and SergioEstevao authored May 26, 2020
1 parent 3c9deb4 commit 47ae410
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 91 deletions.
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
46 changes: 14 additions & 32 deletions packages/block-library/src/heading/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,37 +34,21 @@ function HeadingEdit( {
return (
<>
<BlockControls>
<HeadingToolbar
minLevel={ Platform.OS === 'web' ? 2 : 1 }
maxLevel={ Platform.OS === 'web' ? 5 : 7 }
selectedLevel={ level }
onChange={ ( newLevel ) =>
setAttributes( { level: newLevel } )
}
/>
<ToolbarGroup>
<HeadingLevelDropdown
selectedLevel={ level }
onChange={ ( newLevel ) =>
setAttributes( { level: newLevel } )
}
/>
</ToolbarGroup>
<AlignmentToolbar
value={ align }
onChange={ ( nextAlign ) => {
setAttributes( { align: nextAlign } );
} }
/>
</BlockControls>
{ Platform.OS === 'web' && (
<InspectorControls>
<PanelBody title={ __( 'Heading settings' ) }>
<Text variant="label">{ __( 'Level' ) }</Text>
<HeadingToolbar
isCollapsed={ false }
minLevel={ 1 }
maxLevel={ 7 }
selectedLevel={ level }
onChange={ ( newLevel ) =>
setAttributes( { level: newLevel } )
}
/>
</PanelBody>
</InspectorControls>
) }
<RichText
identifier="content"
tagName={ Block[ tagName ] }
Expand Down
14 changes: 14 additions & 0 deletions packages/block-library/src/heading/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Remove padding in heading level control popover since the toolbar buttons already have padding.
.block-library-heading-level-dropdown .components-popover__content {
padding: 0;
// TODO: Find a less hardcoded way of doing this. `max-content` works on
// Chromium, but it results in a scrollbar on Safari, and isn't supported
// at all in IE11.
min-width: 230px;
}

// The dropdown already has a border, so we can remove the one on the heading
// level toolbar.
.block-library-heading-level-toolbar {
border: none;
}
112 changes: 112 additions & 0 deletions packages/block-library/src/heading/heading-level-dropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* WordPress dependencies
*/
import {
Dropdown,
Toolbar,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { DOWN } from '@wordpress/keycodes';

/**
* Internal dependencies
*/
import HeadingLevelIcon from './heading-level-icon';

const HEADING_LEVELS = [ 1, 2, 3, 4, 5, 6 ];

const POPOVER_PROPS = {
className: 'block-library-heading-level-dropdown',
isAlternate: true,
};

/** @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 } ) {
return (
<Dropdown
popoverProps={ POPOVER_PROPS }
renderToggle={ ( { onToggle, isOpen } ) => {
const openOnArrowDown = ( event ) => {
if ( ! isOpen && event.keyCode === DOWN ) {
event.preventDefault();
event.stopPropagation();
onToggle();
}
};

return (
<ToolbarButton
aria-expanded={ isOpen }
aria-haspopup="true"
icon={ <HeadingLevelIcon level={ selectedLevel } /> }
label={ __( 'Change heading level' ) }
onClick={ onToggle }
onKeyDown={ openOnArrowDown }
showTooltip
/>
);
} }
renderContent={ () => (
<Toolbar
className="block-library-heading-level-toolbar"
__experimentalAccessibilityLabel={ __(
'Change heading level'
) }
>
<ToolbarGroup
isCollapsed={ false }
controls={ HEADING_LEVELS.map( ( targetLevel ) => {
const isActive = targetLevel === selectedLevel;
return {
icon: (
<HeadingLevelIcon
level={ targetLevel }
isPressed={ isActive }
/>
),
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();
},
};
} ) }
/>
</Toolbar>
) }
/>
);
}
Original file line number Diff line number Diff line change
@@ -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: (
<HeadingLevelIcon
level={ targetLevel }
isPressed={ isActive }
/>
),
// translators: %s: heading level e.g: "1", "2", "3"
title: sprintf( __( 'Heading %d' ), targetLevel ),
isActive,
onClick: () => onChangeCallback( targetLevel ),
};
};

return (
<DropdownMenu
icon={ <HeadingLevelIcon level={ selectedLevel } /> }
controls={ HEADING_LEVELS.map( ( index ) =>
createLevelControl( index, selectedLevel, onChange )
) }
label={ __( 'Change heading level' ) }
/>
);
}
18 changes: 18 additions & 0 deletions packages/block-library/src/heading/heading-level-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
57 changes: 0 additions & 57 deletions packages/block-library/src/heading/heading-toolbar.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/components/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions packages/e2e-tests/specs/editor/various/rich-text.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
insertBlock,
clickBlockAppender,
pressKeyWithModifier,
openDocumentSettingsSidebar,
} from '@wordpress/e2e-test-utils';

describe( 'RichText', () => {
Expand All @@ -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();
Expand Down

0 comments on commit 47ae410

Please sign in to comment.