From a2419d097e7fe043a9e9fcb43353a7bade56881e Mon Sep 17 00:00:00 2001 From: Andy Peatling Date: Wed, 16 Sep 2020 11:44:29 -0700 Subject: [PATCH] Inserter: Add block pattern category selection dropdown in main inserter (#24954) * Add dropdown for patterns in the main inserter to select and view one specific category at a time. * Remove the viewing: text, and increase the target size of the dropdown button. * Fix scss lint errors. * Fix the lazy loading of patterns when specific categories are selected. * Make sure quick inserter searches work for patterns and select all categories for the search. * switch to filtering patterns in component rather than hook * Improve performance of pattern search with large pattern sets * Also change filtered patterns if pattern array changes * Fix casing error * Add uncategorized back * Switch to native select control * Prevent duplicate 'uncategorized' categories being added * remove unused arg * Design cleanup. * Switch to useMemo to filter patterns instead of storing in component state * remove empty categories * Remove arguments that are no longer used. Co-authored-by: Glen Davies --- .../components/inserter/block-patterns-tab.js | 141 +++++++++++------- .../src/components/inserter/menu.js | 10 ++ .../src/components/inserter/pattern-panel.js | 65 ++++++++ .../src/components/inserter/style.scss | 16 +- 4 files changed, 179 insertions(+), 53 deletions(-) create mode 100644 packages/block-editor/src/components/inserter/pattern-panel.js diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab.js b/packages/block-editor/src/components/inserter/block-patterns-tab.js index ffdf8a33384319..ceb9a76a7cfb1f 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js @@ -6,7 +6,7 @@ import { fromPairs } from 'lodash'; /** * WordPress dependencies */ -import { useMemo, useCallback } from '@wordpress/element'; +import { useMemo, useCallback, useEffect } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; import { useAsyncList } from '@wordpress/compose'; @@ -14,20 +14,22 @@ import { useAsyncList } from '@wordpress/compose'; * Internal dependencies */ import InserterPanel from './panel'; +import PatternInserterPanel from './pattern-panel'; import { searchItems } from './search-items'; import InserterNoResults from './no-results'; import usePatternsState from './hooks/use-patterns-state'; import BlockPatternList from '../block-patterns-list'; function BlockPatternsSearchResults( { filterValue, onInsert } ) { - const [ patterns, , onClick ] = usePatternsState( onInsert ); - const currentShownPatterns = useAsyncList( patterns ); + const [ allPatterns, , onClick ] = usePatternsState( onInsert ); const filteredPatterns = useMemo( - () => searchItems( patterns, filterValue ), - [ filterValue, patterns ] + () => searchItems( allPatterns, filterValue ), + [ filterValue, allPatterns ] ); + const currentShownPatterns = useAsyncList( filteredPatterns ); + if ( filterValue ) { return !! filteredPatterns.length ? ( @@ -43,8 +45,45 @@ function BlockPatternsSearchResults( { filterValue, onInsert } ) { } } -function BlockPatternsPerCategories( { onInsert } ) { - const [ patterns, categories, onClick ] = usePatternsState( onInsert ); +function BlockPatternsCategory( { + onInsert, + selectedCategory, + onClickCategory, +} ) { + const [ allPatterns, allCategories, onClick ] = usePatternsState( + onInsert + ); + + // Remove any empty categories + const populatedCategories = useMemo( + () => + allCategories.filter( ( category ) => + allPatterns.some( ( pattern ) => + pattern.categories.includes( category.name ) + ) + ), + [ allPatterns, allCategories ] + ); + + const patternCategory = selectedCategory + ? selectedCategory + : populatedCategories[ 0 ]; + + useEffect( () => { + if ( + allPatterns.some( + ( pattern ) => getPatternIndex( pattern ) === Infinity + ) && + ! populatedCategories.find( + ( category ) => category.name === 'uncategorized' + ) + ) { + populatedCategories.push( { + name: 'uncategorized', + label: _x( 'Uncategorized' ), + } ); + } + }, [ populatedCategories, allPatterns ] ); const getPatternIndex = useCallback( ( pattern ) => { @@ -52,82 +91,80 @@ function BlockPatternsPerCategories( { onInsert } ) { return Infinity; } const indexedCategories = fromPairs( - categories.map( ( { name }, index ) => [ name, index ] ) + populatedCategories.map( ( { name }, index ) => [ + name, + index, + ] ) ); return Math.min( - ...pattern.categories.map( ( category ) => - indexedCategories[ category ] !== undefined - ? indexedCategories[ category ] + ...pattern.categories.map( ( cat ) => + indexedCategories[ cat ] !== undefined + ? indexedCategories[ cat ] : Infinity ) ); }, - [ categories ] + [ populatedCategories ] ); - // Ordering the patterns per category is important for the async rendering. + const currentCategoryPatterns = useMemo( + () => + allPatterns.filter( ( pattern ) => + patternCategory.name === 'uncategorized' + ? getPatternIndex( pattern ) === Infinity + : pattern.categories && + pattern.categories.includes( patternCategory.name ) + ), + [ allPatterns, patternCategory ] + ); + + // Ordering the patterns is important for the async rendering. const orderedPatterns = useMemo( () => { - return patterns.sort( ( a, b ) => { + return currentCategoryPatterns.sort( ( a, b ) => { return getPatternIndex( a ) - getPatternIndex( b ); } ); - }, [ patterns, getPatternIndex ] ); + }, [ currentCategoryPatterns, getPatternIndex ] ); const currentShownPatterns = useAsyncList( orderedPatterns ); - // Uncategorized Patterns - const uncategorizedPatterns = useMemo( - () => - patterns.filter( - ( pattern ) => getPatternIndex( pattern ) === Infinity - ), - [ patterns, getPatternIndex ] - ); - return ( <> - { categories.map( ( patternCategory ) => { - const categoryPatterns = patterns.filter( - ( pattern ) => - pattern.categories && - pattern.categories.includes( patternCategory.name ) - ); - return ( - !! categoryPatterns.length && ( - - - - ) - ); - } ) } - - { !! uncategorizedPatterns.length && ( - + { !! currentCategoryPatterns.length && ( + - + ) } ); } -function BlockPatternsTabs( { onInsert, filterValue } ) { +function BlockPatternsTabs( { + onInsert, + onClickCategory, + filterValue, + selectedCategory, +} ) { return filterValue ? ( ) : ( - + ); } diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index e1a026ee614acd..3d59359799397a 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -33,6 +33,10 @@ function InserterMenu( { const [ activeTab, setActiveTab ] = useState( 'blocks' ); const [ filterValue, setFilterValue ] = useState( '' ); const [ hoveredItem, setHoveredItem ] = useState( null ); + const [ selectedPatternCategory, setSelectedPatternCategory ] = useState( + null + ); + const [ destinationRootClientId, onInsertBlocks, @@ -83,6 +87,10 @@ function InserterMenu( { setHoveredItem( item ); }; + const onClickPatternCategory = ( patternCategory ) => { + setSelectedPatternCategory( patternCategory ); + }; + const blocksTab = ( <>
@@ -109,6 +117,8 @@ function InserterMenu( { ); diff --git a/packages/block-editor/src/components/inserter/pattern-panel.js b/packages/block-editor/src/components/inserter/pattern-panel.js new file mode 100644 index 00000000000000..db3cb48572f55d --- /dev/null +++ b/packages/block-editor/src/components/inserter/pattern-panel.js @@ -0,0 +1,65 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { SelectControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function PatternInserterPanel( { + selectedCategory, + patternCategories, + onClickCategory, + children, +} ) { + const categoryOptions = () => { + const options = []; + + patternCategories.map( ( patternCategory ) => { + return options.push( { + value: patternCategory.name, + label: patternCategory.label, + } ); + } ); + + return options; + }; + + const onChangeSelect = ( selected ) => { + onClickCategory( + patternCategories.find( + ( patternCategory ) => selected === patternCategory.name + ) + ); + }; + + const getPanelHeaderClassName = () => { + return classnames( + 'block-editor-inserter__panel-header', + 'block-editor-inserter__panel-header-patterns' + ); + }; + + return ( + <> +
+ +
+
+ { children } +
+ + ); +} + +export default PatternInserterPanel; diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 429e2f56851024..2cc05646f0239d 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -157,11 +157,17 @@ $block-inserter-tabs-height: 44px; padding: $grid-unit-20 $grid-unit-20 0; } +.block-editor-inserter__panel-header-patterns { + padding: $grid-unit-20 $grid-unit-20 0 $grid-unit-10; +} + .block-editor-inserter__panel-content { padding: $grid-unit-20; } -.block-editor-inserter__panel-title { +.block-editor-inserter__panel-title, +.block-editor-inserter__panel-title button, +.components-custom-select-control__menu li { margin: 0 $grid-unit-15 0 0; color: $gray-700; text-transform: uppercase; @@ -169,6 +175,14 @@ $block-inserter-tabs-height: 44px; font-weight: 500; } +.block-editor-inserter__panel-dropdown select.components-select-control__input.components-select-control__input.components-select-control__input { + line-height: 1.2; +} + +.block-editor-inserter__panel-dropdown select { + border: none; +} + .block-editor-inserter__block-list { flex-grow: 1; position: relative;