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

Edit Post: Add block management modal #14224

Merged
merged 40 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
86c88a1
Edit Post: Provide default preferences by higher-order reducer
aduth Mar 4, 2019
8af0287
Edit Post: Add block management modal
aduth Mar 4, 2019
f83be63
Edit Post: Rephrase block disable as block hide
aduth Mar 8, 2019
8fc50d9
Edit Post: Update "Hide All" to "Hide all blocks"
aduth Mar 8, 2019
c61ccd7
Edit Post: Remove block manager autoFocus
aduth Mar 8, 2019
9ac28ec
Edit Post: Close Block Manager panels by default
aduth Mar 8, 2019
ae02300
Edit Post: Show visible label for Block Manager search
aduth Mar 8, 2019
40ffefe
Edit Post: Generalize block search to blocks selector
aduth Mar 8, 2019
c8d5b98
Edit Post: Show hidden block types in Block Manager as labelled
aduth Mar 8, 2019
b28cbb0
Edit Post: Avoid generating unnecessary hasChildBlocksWithInserterSup…
aduth Mar 8, 2019
b681ecb
Edit Post: Split Block Manager category render to own component
aduth Mar 8, 2019
f8a1874
Edit Post: Filter Block Manager items by inserter support
aduth Mar 8, 2019
98e55eb
Edit Post: Render Block Manager item as toggle button
aduth Mar 8, 2019
32e2490
Edit Post: Render Block Manager results as labelled region
aduth Mar 8, 2019
2de9a7e
Edit Post: Constrain Block Manager width to 485px
aduth Mar 8, 2019
8671702
Components: Add className prop support for ToggleControl
aduth Mar 8, 2019
fccc796
Edit Post: Add margin surrounding Hide all checkbox
aduth Mar 8, 2019
f092ff6
Edit Post: Avoid changing label in toggle button
aduth Mar 8, 2019
25b185c
Blocks: Regenerate docs for isMatchingSearchTerm
aduth Mar 8, 2019
fbf5410
Update components CHANGELOG to use "class name" phrasing
gziolo Mar 12, 2019
9ce3def
Edit Post: Use consistent "Manage Block Visibility" label
aduth Mar 12, 2019
bfc1a38
Blocks: Add unit tests for isMatchingSearchTerm
aduth Mar 12, 2019
6171177
Edit Post: Add "No Results" text for empty Manage Blocks search
aduth Mar 12, 2019
c0b9a23
Edit Post: Render Manage Blocks Visibility as checklist
aduth Mar 12, 2019
fc978e5
Edit Post: Reverse behavior of "all" visible toggle to show
aduth Mar 12, 2019
9297f8d
Components: Add mirror prop support to CheckboxControl
aduth Mar 12, 2019
e194aa0
Edit Post: Mirror checkboxes in Manage Blocks modal
aduth Mar 12, 2019
1555f58
Edit Post: Fix Firefox scroll overflow in Manage Blocks
aduth Mar 13, 2019
2d9b379
Edit Post: Render Manage Blocks group as role=group
aduth Mar 13, 2019
6798964
Checkbox margin adjustment.
mapk Mar 13, 2019
76fb031
Merge branch 'add/block-manager' of github.com:WordPress/gutenberg in…
mapk Mar 13, 2019
ac202ff
Icon colors changed to -gray-500
mapk Mar 13, 2019
a71f799
Changed title back to Block Manager based on design feedback
mapk Mar 13, 2019
3d7de09
Edit Post: Fix Block Manager "Show All" toggle checked as all checked
aduth Mar 13, 2019
6e8072a
Edit Post: Refactor Block Manager checkbox margin as precise override
aduth Mar 13, 2019
0d985a2
Revert "Edit Post: Fix Block Manager "Show All" toggle checked as all…
mapk Mar 13, 2019
dca4049
Revert "Components: Add mirror prop support to CheckboxControl"
aduth Mar 14, 2019
40c7694
Edit Post: Display block manager entries as indented checklist
aduth Mar 14, 2019
22ad54b
Edit Post: Restore Block Manager toggle label association
aduth Mar 14, 2019
0b45148
Edit Post: Adjust mobile, margin styling for mixed checkbox
aduth Mar 14, 2019
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
15 changes: 15 additions & 0 deletions docs/designers-developers/developers/data/data-core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ Returns true if the block defines support for a feature, or false otherwise.

Whether block supports feature.

### isMatchingSearchTerm

Returns true if the block type by the given name or object value matches a
search term, or false otherwise.

*Parameters*

* state: Blocks state.
* nameOrType: Block name or type object.
* searchTerm: Search term by which to filter.

*Returns*

Wheter block type matches search term.

### hasChildBlocks

Returns a boolean indicating if a block has child blocks or not.
Expand Down
18 changes: 18 additions & 0 deletions docs/designers-developers/developers/data/data-core-edit-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,24 @@ Returns an action object used to toggle a plugin name flag.

* pluginName: Plugin name.

### hideBlockTypes

Returns an action object used in signalling that block types by the given
name(s) should be hidden.

*Parameters*

* blockNames: Names of block types to hide.

### showBlockTypes

Returns an action object used in signalling that block types by the given
name(s) should be shown.

*Parameters*

* blockNames: Names of block types to show.

### setAvailableMetaBoxesPerLocation

Returns an action object used in signaling
Expand Down
46 changes: 36 additions & 10 deletions packages/block-editor/src/components/block-types-list/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import { map } from 'lodash';

/**
* WordPress dependencies
*/
Expand All @@ -8,15 +13,25 @@ import { getBlockMenuDefaultClassName } from '@wordpress/blocks';
*/
import InserterListItem from '../inserter-list-item';

function BlockTypesList( { items, onSelect, onHover = () => {}, children } ) {
return (
/*
* Disable reason: The `list` ARIA role is redundant but
* Safari+VoiceOver won't announce the list otherwise.
*/
/* eslint-disable jsx-a11y/no-redundant-roles */
<ul role="list" className="editor-block-types-list">
{ items && items.map( ( item ) =>
/**
* Stateless function component which renders its received `children` prop.
*
* @param {Object} props Props object.
*
* @return {WPElement} Rendered children.
*/
const RENDER_CHILDREN = ( props ) => props.children;
gziolo marked this conversation as resolved.
Show resolved Hide resolved

function BlockTypesList( {
items,
onSelect,
onHover = () => {},
renderItem: RenderItem = RENDER_CHILDREN,
children,
} ) {
items = map( items, ( item ) => {
return (
<RenderItem key={ item.id } item={ item }>
<InserterListItem
key={ item.id }
className={ getBlockMenuDefaultClassName( item.id ) }
Expand All @@ -35,7 +50,18 @@ function BlockTypesList( { items, onSelect, onHover = () => {}, children } ) {
isDisabled={ item.isDisabled }
title={ item.title }
/>
) }
</RenderItem>
);
} );

return (
/*
* Disable reason: The `list` ARIA role is redundant but
* Safari+VoiceOver won't announce the list otherwise.
*/
/* eslint-disable jsx-a11y/no-redundant-roles */
<ul role="list" className="editor-block-types-list">
{ items }
{ children }
</ul>
/* eslint-enable jsx-a11y/no-redundant-roles */
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as BlockEdit } from './block-edit';
export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockNavigationDropdown } from './block-navigation/dropdown';
export { default as BlockIcon } from './block-icon';
export { default as __experimentalBlockTypesList } from './block-types-list';
export { default as ColorPalette } from './color-palette';
export { default as withColorContext } from './color-palette/with-color-context';
export * from './colors';
Expand Down
65 changes: 61 additions & 4 deletions packages/blocks/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@
* External dependencies
*/
import createSelector from 'rememo';
import { filter, get, includes, map, some } from 'lodash';
import { filter, get, includes, map, some, flow, deburr } from 'lodash';

/**
* Given a block name or block type object, returns the corresponding
* normalized block type object.
*
* @param {Object} state Blocks state.
* @param {(string|Object)} nameOrType Block name or type object
*
* @return {Object} Block type object.
*/
const getNormalizedBlockType = ( state, nameOrType ) => (
'string' === typeof nameOrType ?
getBlockType( state, nameOrType ) :
nameOrType
);

/**
* Returns all the available block types.
Expand Down Expand Up @@ -120,9 +135,7 @@ export const getChildBlockNames = createSelector(
* @return {?*} Block support value
*/
export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) => {
const blockType = 'string' === typeof nameOrType ?
getBlockType( state, nameOrType ) :
nameOrType;
const blockType = getNormalizedBlockType( state, nameOrType );

return get( blockType, [
'supports',
Expand All @@ -145,6 +158,50 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) {
return !! getBlockSupport( state, nameOrType, feature, defaultSupports );
}

/**
* Returns true if the block type by the given name or object value matches a
* search term, or false otherwise.
*
* @param {Object} state Blocks state.
* @param {(string|Object)} nameOrType Block name or type object.
* @param {string} searchTerm Search term by which to filter.
*
* @return {Object[]} Wheter block type matches search term.
*/
export function isMatchingSearchTerm( state, nameOrType, searchTerm ) {
Copy link
Member

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Yeah, I can get those in place today.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the follow-up PR we should cover this new selector with basic unit tests.

Yeah, I can get those in place today.

Added unit tests in bfc1a38.

const blockType = getNormalizedBlockType( state, nameOrType );

const getNormalizedSearchTerm = flow( [
// Disregard diacritics.
// Input: "média"
deburr,

// Lowercase.
// Input: "MEDIA"
( term ) => term.toLowerCase(),

// Strip leading and trailing whitespace.
// Input: " media "
( term ) => term.trim(),
] );

const normalizedSearchTerm = getNormalizedSearchTerm( searchTerm );

const isSearchMatch = flow( [
getNormalizedSearchTerm,
( normalizedCandidate ) => includes(
normalizedCandidate,
normalizedSearchTerm
),
] );

return (
isSearchMatch( blockType.title ) ||
some( blockType.keywords, isSearchMatch ) ||
isSearchMatch( blockType.category )
);
}

/**
* Returns a boolean indicating if a block has child blocks or not.
*
Expand Down
6 changes: 6 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 7.2.0 (Unreleased)

### New Features

- `ToggleControl` now accepts a `className` prop to merge with its default-assigned classname.
aduth marked this conversation as resolved.
Show resolved Hide resolved

## 7.1.0 (2019-03-06)

### New Features
Expand Down
5 changes: 3 additions & 2 deletions packages/components/src/toggle-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { isFunction } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
Expand Down Expand Up @@ -29,7 +30,7 @@ class ToggleControl extends Component {
}

render() {
const { label, checked, help, instanceId } = this.props;
const { label, checked, help, instanceId, className } = this.props;
const id = `inspector-toggle-control-${ instanceId }`;

let describedBy, helpLabel;
Expand All @@ -42,7 +43,7 @@ class ToggleControl extends Component {
<BaseControl
id={ id }
help={ helpLabel }
className="components-toggle-control"
className={ classnames( 'components-toggle-control', className ) }
>
<FormToggle
id={ id }
Expand Down
2 changes: 2 additions & 0 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import TextEditor from '../text-editor';
import VisualEditor from '../visual-editor';
import EditorModeKeyboardShortcuts from '../keyboard-shortcuts';
import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
import ManageBlocksModal from '../manage-blocks-modal';
import OptionsModal from '../options-modal';
import MetaBoxes from '../meta-boxes';
import SettingsSidebar from '../sidebar/settings-sidebar';
Expand Down Expand Up @@ -83,6 +84,7 @@ function Layout( {
<PreserveScrollInReorder />
<EditorModeKeyboardShortcuts />
<KeyboardShortcutHelpModal />
<ManageBlocksModal />
<OptionsModal />
{ ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> }
{ isRichEditingEnabled && mode === 'visual' && <VisualEditor /> }
Expand Down
97 changes: 97 additions & 0 deletions packages/edit-post/src/components/manage-blocks-modal/category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* External dependencies
*/
import { includes, map } from 'lodash';

/**
* WordPress dependencies
*/
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { cloneElement, Children } from '@wordpress/element';
import { ToggleControl, PanelBody } from '@wordpress/components';
import { __experimentalBlockTypesList as BlockTypesList } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

function BlockManagerCategory( {
category,
blockItems,
hiddenBlockTypes,
opened,
onToggle,
showBlockTypes,
hideBlockTypes,
toggleAllHidden,
} ) {
if ( ! blockItems.length ) {
return null;
}

const isAllHidden = blockItems.every( ( blockItem ) => {
return hiddenBlockTypes.includes( blockItem.id );
} );

return (
<PanelBody
key={ category.slug }
title={ category.title }
icon={ category.icon }
opened={ opened }
onToggle={ onToggle }
>
<ToggleControl
label={ __( 'Hide all blocks' ) }
checked={ isAllHidden }
onChange={ toggleAllHidden }
className="edit-post-manage-blocks-modal__hide-all"
/>
<BlockTypesList
items={ blockItems }
onSelect={ ( item ) => (
includes( hiddenBlockTypes, item.id ) ?
showBlockTypes( item.id ) :
hideBlockTypes( item.id )
) }
renderItem={ ( { children, item } ) => {
const isHidden = includes( hiddenBlockTypes, item.id );
const child = Children.only( children );
return cloneElement( child, {
'aria-pressed': isHidden,
'aria-label': __( 'Hide block: %s' ),
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this translation needs to be further processed to replace %s with a block name. It must be fixed before we proceed with merge.

By the way, VoiceOver announces this component as a toggle button. I think it should be rather a checkbox. I think I mentioned it already :)

Copy link
Member Author

@aduth aduth Mar 12, 2019

Choose a reason for hiding this comment

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

By the way, VoiceOver announces this component as a toggle button. I think it should be rather a checkbox. I think I mentioned it already :)

I had some related reflections in my comment at #14224 (comment) :

Whether we should leverage more granular role/attributes like role="checkbox" and aria-checked . Based on the WAI-ARIA example and precedent above, I assumed there may be some reason to avoid this (e.g. lack of widespread support in assistive tools?).

I had used the Color Palette component as precedent for being a similar sort of custom checkbox / toggle button. I'm also happy to update it, should the checkboxes behavior be more appropriate or supported. If we do, I wonder if we shouldn't also consider (separately) whether Color Palette is correct as well.

Copy link
Member

Choose a reason for hiding this comment

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

I read the same resource you linked in your comment. My understanding was that the toggle button is better suited for something more standalone, not sure how to phrase it to be honest. The more I read it, the more it seems to be a viable solution in this context as well. We probably should pick the final solution based on how this is going to look like.

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like this translation needs to be further processed to replace %s with a block name. It must be fixed before we proceed with merge.

This could be nice to develop as an enhancement for valid-sprintf (the lack of sprintf for a string which contains a placeholder value).

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 read the same resource you linked in your comment. My understanding was that the toggle button is better suited for something more standalone, not sure how to phrase it to be honest. The more I read it, the more it seems to be a viable solution in this context as well. We probably should pick the final solution based on how this is going to look like.

With the updated design, we're now just rendering a checkbox, which may render the point moot. That said, there's potentially an open question to whether "checked" associated with a block name makes sense. It relies on contextual information of operating within the Manage Blocks Visibility to understand a "checked block" to mean one which is "visible".

'data-hidden': isHidden ? __( 'Hidden' ) : undefined,
} );
} }
/>
</PanelBody>
);
}

export default compose( [
withSelect( ( select ) => {
const { getPreference } = select( 'core/edit-post' );

return {
hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
const { blockItems } = ownProps;
const {
showBlockTypes,
hideBlockTypes,
} = dispatch( 'core/edit-post' );

return {
showBlockTypes,
hideBlockTypes,
toggleAllHidden( isToBeDisabled ) {
const blockNames = map( blockItems, 'id' );
if ( isToBeDisabled ) {
hideBlockTypes( blockNames );
} else {
showBlockTypes( blockNames );
}
},
};
} ),
] )( BlockManagerCategory );
Loading