Skip to content

Commit

Permalink
feat: Inserter displays block collections (#42405)
Browse files Browse the repository at this point in the history
* feat: Inserter display block collections

Collections of blocks regsitered via `registerBlockCollection` now
appear at the end of the block inserter enabling additional organization
of blocks.

* test: Add test for createInserterSection utility

* fix: Update reusable blocks list data structure

The `BlockTypesList` now expects `sections` to support the `SectionList`
used within it for displaying block collections. Prior to this change,
an error was thrown.

* test: Fix failure by expanding selector mock

The `BlockTypesTab` now relies upon additional store selectors. The lack
of mocks for these selectors caused test failures.

* docs: Add changelog entry

* refactor: Simplify and mirror web implementation

The code contained redundant selectors against the store for the block
type list. This change refactors the native inserter to match the web
counterpart, utilizing the selector leveraged by the web.

* refactor: Remove unnecessary object spread

While verbose, passing explicit props is likely more orthodox and
straightforward to interpret.

* fix: Remove broken keyExtractor

The key for this component's sections is now `key`, not `id`, which
resulted in undefined key values returned here. However, the default
`keyExtractor` already searches for `key` and then `id`, so this can be
safely removed.

* fix: Revert utilization of onSelect from useBlockTypesState

The block created from within this callback appears to be in a format
that is incompatible with the native editor at this time. Specifically,
this appeared to break Embed block variants, e.g. Vimeo. A future
improvement would be to investigate this further in hopes of aligning
web and native.

* chore: Add background to section headers

Improve legibility of section headers whenever blocks scroll beneath the
header.

* chore: Display collection title to improve dark mode support

Leverage merely an icon with text made dark mode support more
challenging. Rendering text allows for easy swapping of color schemes.

* fix: Set collection title text color

* fix: Adjust horizontal spacing for collection header
  • Loading branch information
dcalhoun authored Jul 15, 2022
1 parent d70856b commit a7181f2
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 49 deletions.
100 changes: 76 additions & 24 deletions packages/block-editor/src/components/block-types-list/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import {
Dimensions,
FlatList,
SectionList,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
} from 'react-native';
Expand All @@ -13,7 +15,11 @@ import {
* WordPress dependencies
*/
import { useState, useEffect } from '@wordpress/element';
import { BottomSheet, InserterButton } from '@wordpress/components';
import { BottomSheet, Gradient, InserterButton } from '@wordpress/components';
import {
usePreferredColorScheme,
usePreferredColorSchemeStyle,
} from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -24,7 +30,7 @@ const MIN_COL_NUM = 3;

export default function BlockTypesList( {
name,
items,
sections,
onSelect,
listProps,
initialNumToRender = 3,
Expand Down Expand Up @@ -80,33 +86,79 @@ export default function BlockTypesList( {
listProps.contentContainerStyle
);

const renderSection = ( { item } ) => {
return (
<TouchableWithoutFeedback accessible={ false }>
<FlatList
data={ item.list }
key={ `InserterUI-${ name }-${ numberOfColumns }` } // Re-render when numberOfColumns changes.
numColumns={ numberOfColumns }
ItemSeparatorComponent={ () => (
<TouchableWithoutFeedback accessible={ false }>
<View
style={
styles[ 'block-types-list__row-separator' ]
}
/>
</TouchableWithoutFeedback>
) }
scrollEnabled={ false }
renderItem={ renderListItem }
/>
</TouchableWithoutFeedback>
);
};

const renderListItem = ( { item } ) => {
return (
<InserterButton
item={ item }
itemWidth={ itemWidth }
maxWidth={ maxWidth }
onSelect={ onSelect }
/>
);
};

const colorScheme = usePreferredColorScheme();
const sectionHeaderGradientValue =
colorScheme === 'light'
? 'linear-gradient(#fff 70%, rgba(255, 255, 255, 0))'
: 'linear-gradient(#2e2e2e 70%, rgba(46, 46, 46, 0))';
const sectionHeaderTitleStyles = usePreferredColorSchemeStyle(
styles[ 'block-types-list__section-header-title' ],
styles[ 'block-types-list__section-header-title--dark' ]
);

const renderSectionHeader = ( { section: { metadata } } ) => {
if ( ! metadata?.icon || ! metadata?.title ) {
return null;
}

return (
<TouchableWithoutFeedback accessible={ false }>
<Gradient
gradientValue={ sectionHeaderGradientValue }
style={ styles[ 'block-types-list__section-header' ] }
>
{ metadata?.icon }
<Text style={ sectionHeaderTitleStyles }>
{ metadata?.title }
</Text>
</Gradient>
</TouchableWithoutFeedback>
);
};

return (
<FlatList
<SectionList
onLayout={ onLayout }
key={ `InserterUI-${ name }-${ numberOfColumns }` } // Re-render when numberOfColumns changes.
testID={ `InserterUI-${ name }` }
keyboardShouldPersistTaps="always"
numColumns={ numberOfColumns }
data={ items }
sections={ sections }
initialNumToRender={ initialNumToRender }
ItemSeparatorComponent={ () => (
<TouchableWithoutFeedback accessible={ false }>
<View
style={ styles[ 'block-types-list__row-separator' ] }
/>
</TouchableWithoutFeedback>
) }
keyExtractor={ ( item ) => item.id }
renderItem={ ( { item } ) => (
<InserterButton
{ ...{
item,
itemWidth,
maxWidth,
onSelect,
} }
/>
) }
renderItem={ renderSection }
renderSectionHeader={ renderSectionHeader }
{ ...listProps }
contentContainerStyle={ {
...contentContainerStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,21 @@
.block-types-list__column {
padding: $grid-unit-20;
}

.block-types-list__section-header {
align-items: center;
flex-direction: row;
justify-content: center;
padding-bottom: 16;
padding-top: 32;
}

.block-types-list__section-header-title {
color: $black;
font-size: 16px;
margin-left: 10px;
}

.block-types-list__section-header-title--dark {
color: $white;
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import BlockTypesList from '../block-types-list';
import useClipboardBlock from './hooks/use-clipboard-block';
import { store as blockEditorStore } from '../../store';
import useBlockTypeImpressions from './hooks/use-block-type-impressions';
import { filterInserterItems } from './utils';
import { createInserterSection, filterInserterItems } from './utils';
import useBlockTypesState from './hooks/use-block-types-state';

function BlockTypesTab( { onSelect, rootClientId, listProps } ) {
const clipboardBlock = useClipboardBlock( rootClientId );

const { blockTypes } = useSelect(
( select ) => {
const { getInserterItems } = select( blockEditorStore );
const blockItems = filterInserterItems(
getInserterItems( rootClientId )
);
const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ];

return {
blockTypes: clipboardBlock
? [ clipboardBlock, ...blockItems ]
: blockItems,
};
},
[ rootClientId ]
function BlockTypesTab( { onSelect, rootClientId, listProps } ) {
const [ rawBlockTypes, , collections ] = useBlockTypesState(
rootClientId,
onSelect
);

const clipboardBlock = useClipboardBlock( rootClientId );
const filteredBlockTypes = filterInserterItems( rawBlockTypes );
const blockTypes = clipboardBlock
? [ clipboardBlock, ...filteredBlockTypes ]
: filteredBlockTypes;
const { items, trackBlockTypeSelected } =
useBlockTypeImpressions( blockTypes );

Expand All @@ -39,10 +32,38 @@ function BlockTypesTab( { onSelect, rootClientId, listProps } ) {
onSelect( ...args );
};

const collectionSections = useMemo( () => {
const result = [];
Object.keys( collections ).forEach( ( namespace ) => {
const data = items.filter(
( item ) => getBlockNamespace( item ) === namespace
);
if ( data.length > 0 ) {
result.push(
createInserterSection( {
key: `collection-${ namespace }`,
metadata: {
icon: collections[ namespace ].icon,
title: collections[ namespace ].title,
},
items: data,
} )
);
}
} );

return result;
}, [ items, collections ] );

const sections = [
createInserterSection( { key: 'default', items } ),
...collectionSections,
];

return (
<BlockTypesList
name="Blocks"
items={ items }
sections={ sections }
onSelect={ handleSelect }
listProps={ listProps }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useSelect } from '@wordpress/data';
*/
import BlockTypesList from '../block-types-list';
import { store as blockEditorStore } from '../../store';
import { filterInserterItems } from './utils';
import { createInserterSection, filterInserterItems } from './utils';

function ReusableBlocksTab( { onSelect, rootClientId, listProps } ) {
const { items } = useSelect(
Expand All @@ -23,10 +23,12 @@ function ReusableBlocksTab( { onSelect, rootClientId, listProps } ) {
[ rootClientId ]
);

const sections = [ createInserterSection( { key: 'reuseable', items } ) ];

return (
<BlockTypesList
name="ReusableBlocks"
items={ items }
sections={ sections }
onSelect={ onSelect }
listProps={ listProps }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import BlockTypesList from '../block-types-list';
import InserterNoResults from './no-results';
import { store as blockEditorStore } from '../../store';
import useBlockTypeImpressions from './hooks/use-block-type-impressions';
import { filterInserterItems } from './utils';
import { createInserterSection, filterInserterItems } from './utils';

function InserterSearchResults( {
filterValue,
Expand Down Expand Up @@ -51,7 +51,9 @@ function InserterSearchResults( {
<BlockTypesList
name="Blocks"
initialNumToRender={ isFullScreen ? 10 : 3 }
{ ...{ items, onSelect: handleSelect, listProps } }
sections={ [ createInserterSection( { key: 'search', items } ) ] }
onSelect={ handleSelect }
listProps={ listProps }
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jest.mock( '../hooks/use-clipboard-block' );
jest.mock( '@wordpress/data/src/components/use-select' );

const selectMock = {
getCategories: jest.fn().mockReturnValue( [] ),
getCollections: jest.fn().mockReturnValue( [] ),
getInserterItems: jest.fn().mockReturnValue( [] ),
canInsertBlockType: jest.fn(),
getBlockType: jest.fn(),
Expand Down
37 changes: 37 additions & 0 deletions packages/block-editor/src/components/inserter/test/utils.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Internal dependencies
*/
import { createInserterSection } from '../utils';

describe( 'createInserterSection', () => {
it( 'returns the expected object shape', () => {
const key = 'mock-1';
const items = [ 1, 2, 3 ];
const metadata = { icon: 'icon-mock', title: 'Title Mock' };

expect( createInserterSection( { key, metadata, items } ) ).toEqual(
expect.objectContaining( {
metadata,
data: [ { key, list: items } ],
} )
);
} );

it( 'return always includes metadata', () => {
const key = 'mock-1';
const items = [ 1, 2, 3 ];

expect( createInserterSection( { key, items } ) ).toEqual(
expect.objectContaining( {
metadata: {},
data: [ { key, list: items } ],
} )
);
} );

it( 'requires a unique key', () => {
expect( () => {
createInserterSection( { items: [ 1, 2, 3 ] } );
} ).toThrow( 'A unique key for the section must be provided.' );
} );
} );
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/inserter/utils.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,14 @@ export function filterInserterItems(
blockAllowed( block, { onlyReusable, allowReusable } )
);
}

export function createInserterSection( { key, metadata = {}, items } ) {
if ( ! key ) {
throw new Error( 'A unique key for the section must be provided.' );
}

return {
metadata,
data: [ { key, list: items } ],
};
}
1 change: 1 addition & 0 deletions packages/react-native-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i
## Unreleased

- [*] Add React Native FastImage [#42009]
- [*] Block inserter displays block collections [#42405]

## 1.79.0
- [*] Add 'Insert from URL' option to Video block [#41493]
Expand Down

0 comments on commit a7181f2

Please sign in to comment.