Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heading block: use toolbar for heading level control #20246

Merged
merged 5 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this how we mark optional types, I thought it was more boolean=

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the ?xxx format because these docs said to use it for nullable types.

*/

/**
* 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