Skip to content

Commit

Permalink
Collapsible panels for the block's toolbar (#9687)
Browse files Browse the repository at this point in the history
* Add collapse support to the toolbar component

* Collapse Text Alignment Toolbar for paragraphs and mobile

* Auto-collapse in nested blocks

* Collapse Block Alignment Toolbars

* Fix unit tests

* Make the active item noticeable

* Polish dropdown arrow. Now it's the same as for switcher.

* Polish focus styles.

* Address feedback.

* Small changes per review
  • Loading branch information
youknowriad committed Sep 14, 2018
1 parent 57f98b2 commit 762c717
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 62 deletions.
13 changes: 13 additions & 0 deletions edit-post/assets/stylesheets/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,16 @@
text-align: center;
font-size: $default-font-size;
}

@mixin dropdown-arrow() {
content: "";
pointer-events: none;
display: block;
position: absolute;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 5px solid currentColor;
right: 6px;
}
64 changes: 36 additions & 28 deletions packages/components/src/dropdown-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { flatMap } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -12,7 +13,6 @@ import { DOWN } from '@wordpress/keycodes';
* Internal dependencies
*/
import IconButton from '../icon-button';
import Dashicon from '../dashicon';
import Dropdown from '../dropdown';
import { NavigableMenu } from '../navigable-container';

Expand All @@ -21,14 +21,21 @@ function DropdownMenu( {
label,
menuLabel,
controls,
className,
} ) {
if ( ! controls || ! controls.length ) {
return null;
}

// Normalize controls to nested array of objects (sets of controls)
let controlSets = controls;
if ( ! Array.isArray( controlSets[ 0 ] ) ) {
controlSets = [ controlSets ];
}

return (
<Dropdown
className="components-dropdown-menu"
className={ classnames( 'components-dropdown-menu', className ) }
contentClassName="components-dropdown-menu__popover"
renderToggle={ ( { isOpen, onToggle } ) => {
const openOnArrowDown = ( event ) => {
Expand All @@ -40,11 +47,7 @@ function DropdownMenu( {
};
return (
<IconButton
className={
classnames( 'components-dropdown-menu__toggle', {
'is-active': isOpen,
} )
}
className="components-dropdown-menu__toggle"
icon={ icon }
onClick={ onToggle }
onKeyDown={ openOnArrowDown }
Expand All @@ -53,7 +56,7 @@ function DropdownMenu( {
label={ label }
tooltip={ label }
>
<Dashicon icon="arrow-down" />
<span className="components-dropdown-menu__indicator" />
</IconButton>
);
} }
Expand All @@ -64,26 +67,31 @@ function DropdownMenu( {
role="menu"
aria-label={ menuLabel }
>
{ controls.map( ( control, index ) => (
<IconButton
key={ index }
onClick={ ( event ) => {
if ( control.isDisabled ) {
return;
}
event.stopPropagation();
onClose();
if ( control.onClick ) {
control.onClick();
}
} }
className="components-dropdown-menu__menu-item"
icon={ control.icon }
role="menuitem"
disabled={ control.isDisabled }
>
{ control.title }
</IconButton>
{ flatMap( controlSets, ( controlSet, indexOfSet ) => (
controlSet.map( ( control, indexOfControl ) => (
<IconButton
key={ [ indexOfSet, indexOfControl ].join() }
onClick={ ( event ) => {
event.stopPropagation();
onClose();
if ( control.onClick ) {
control.onClick();
}
} }
className={ classnames(
'components-dropdown-menu__menu-item',
{
'has-separator': indexOfSet > 0 && indexOfControl === 0,
'is-active': control.isActive,
},
) }
icon={ control.icon }
role="menuitem"
disabled={ control.isDisabled }
>
{ control.title }
</IconButton>
) )
) ) }
</NavigableMenu>
);
Expand Down
66 changes: 50 additions & 16 deletions packages/components/src/dropdown-menu/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
margin: 0;
padding: 4px;
border: $border-width solid transparent;
border-radius: 0;
display: flex;
flex-direction: row;

&.is-active,
&.is-active:hover {
box-shadow: none;
background-color: $dark-gray-500;
color: $white;
}
Expand All @@ -26,17 +26,19 @@

&:hover,
&:focus,
&:not(:disabled):hover {
box-shadow: none;
outline: none;
color: $dark-gray-500;
border-color: $dark-gray-500;
&:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover {
@include formatting-button-style__hover();
}

&.is-active > svg,
&.is-active:hover > svg {
background-color: $dark-gray-500;
color: $white;
.components-dropdown-menu__indicator {
display: inline-block;
margin-left: 10px;

// Add a dropdown arrow indicator.
&::after {
@include dropdown-arrow();
top: $icon-button-size-small / 2 + 1px;
}
}
}
}
Expand All @@ -45,23 +47,55 @@
}

.components-dropdown-menu__menu {
// note that left is set by react in a style attribute
width: 100%;
padding: 3px 3px 0;
padding: 9px;
font-family: $default-font;
font-size: $default-font-size;
line-height: $default-line-height;

.components-dropdown-menu__menu-item {
width: 100%;
padding: 6px;
border-radius: 0;
outline: none;
cursor: pointer;
margin-bottom: $grid-size-small;

&.has-separator {
margin-top: 6px;
position: relative;
overflow: visible;
}

&.has-separator::before {
display: block;
content: "";
box-sizing: content-box;
background-color: $light-gray-500;
position: absolute;
top: -3px;
left: 0;
right: 0;
height: 1px;
}

// Plain menu styles.
&:focus:not(:disabled):not([aria-disabled="true"]):not(.is-default) {
@include menu-style__focus();
}

.dashicon {
margin-right: 8px;
// Formatting buttons
> svg {
border-radius: $radius-round-rectangle;

// This assumes 20x20px dashicons.
padding: 2px;
width: $icon-button-size-small;
height: $icon-button-size-small;
margin: -1px $grid-size -1px 0;
}

&:not(:disabled):not([aria-disabled="true"]):not(.is-default).is-active > svg {
@include formatting-button-style__active();
}
}
}

14 changes: 13 additions & 1 deletion packages/components/src/toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { flatMap } from 'lodash';
* Internal dependencies
*/
import IconButton from '../icon-button';
import DropdownMenu from '../dropdown-menu';
import ToolbarContainer from './toolbar-container';
import ToolbarButtonContainer from './toolbar-button-container';

Expand Down Expand Up @@ -41,7 +42,7 @@ import ToolbarButtonContainer from './toolbar-button-container';
*
* @return {ReactElement} The rendered toolbar.
*/
function Toolbar( { controls = [], children, className } ) {
function Toolbar( { controls = [], children, className, isCollapsed, icon, label } ) {
if (
( ! controls || ! controls.length ) &&
! children
Expand All @@ -55,6 +56,17 @@ function Toolbar( { controls = [], children, className } ) {
controlSets = [ controlSets ];
}

if ( isCollapsed ) {
return (
<DropdownMenu
icon={ icon }
label={ label }
controls={ controlSets }
className={ classnames( 'components-toolbar', className ) }
/>
);
}

return (
<ToolbarContainer className={ classnames( 'components-toolbar', className ) }>
{ flatMap( controlSets, ( controlSet, indexOfSet ) => (
Expand Down
38 changes: 37 additions & 1 deletion packages/editor/src/components/alignment-toolbar/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
/**
* External dependencies
*/
import { find } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Toolbar } from '@wordpress/components';
import { withViewportMatch } from '@wordpress/viewport';
import { withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';

/**
* Internal dependencies
*/
import { withBlockEditContext } from '../block-edit/context';

const ALIGNMENT_CONTROLS = [
{
Expand All @@ -22,13 +35,18 @@ const ALIGNMENT_CONTROLS = [
},
];

export default function AlignmentToolbar( { value, onChange } ) {
export function AlignmentToolbar( { isCollapsed, value, onChange } ) {
function applyOrUnset( align ) {
return () => onChange( value === align ? undefined : align );
}

const activeAlignment = find( ALIGNMENT_CONTROLS, ( control ) => control.align === value );

return (
<Toolbar
isCollapsed={ isCollapsed }
icon={ activeAlignment ? activeAlignment.icon : 'editor-alignleft' }
label={ __( 'Change Text Alignment' ) }
controls={ ALIGNMENT_CONTROLS.map( ( control ) => {
const { align } = control;
const isActive = ( value === align );
Expand All @@ -42,3 +60,21 @@ export default function AlignmentToolbar( { value, onChange } ) {
/>
);
}

export default compose(
withBlockEditContext( ( { clientId } ) => {
return {
clientId,
};
} ),
withViewportMatch( { isLargeViewport: 'medium' } ),
withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => {
const { getBlockRootClientId, getEditorSettings } = select( 'core/editor' );
return {
isCollapsed: isCollapsed || ! isLargeViewport || (
! getEditorSettings().hasFixedToolbar &&
getBlockRootClientId( clientId )
),
};
} ),
)( AlignmentToolbar );
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ exports[`AlignmentToolbar should match snapshot 1`] = `
},
]
}
icon="editor-alignleft"
label="Change Text Alignment"
/>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { shallow } from 'enzyme';
/**
* Internal dependencies
*/
import AlignmentToolbar from '../';
import { AlignmentToolbar } from '../';

describe( 'AlignmentToolbar', () => {
const alignment = 'left';
Expand Down
Loading

0 comments on commit 762c717

Please sign in to comment.