diff --git a/lib/block-patterns.php b/lib/block-patterns.php
new file mode 100644
index 00000000000000..d376fb59aea1e9
--- /dev/null
+++ b/lib/block-patterns.php
@@ -0,0 +1,64 @@
+ __( 'Large', 'gutenberg' ),
+ 'scope' => array(
+ 'inserter' => false,
+ 'block' => array( 'core/query' ),
+ ),
+ 'content' => '
+
+
+
+
+
+ ',
+ )
+);
+
+register_block_pattern(
+ 'query/medium-posts',
+ array(
+ 'title' => __( 'Medium', 'gutenberg' ),
+ 'scope' => array(
+ 'inserter' => false,
+ 'block' => array( 'core/query' ),
+ ),
+ 'content' => '
+
+ ',
+ )
+);
+
+register_block_pattern(
+ 'query/small-posts',
+ array(
+ 'title' => __( 'Small', 'gutenberg' ),
+ 'scope' => array(
+ 'inserter' => false,
+ 'block' => array( 'core/query' ),
+ ),
+ 'content' => '
+
+ ',
+ )
+);
diff --git a/lib/load.php b/lib/load.php
index 8027efa785f1e7..feb404433c6e56 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -109,6 +109,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/full-site-editing/edit-site-export.php';
require __DIR__ . '/blocks.php';
+require __DIR__ . '/block-patterns.php';
require __DIR__ . '/client-assets.php';
require __DIR__ . '/demo.php';
require __DIR__ . '/widgets.php';
diff --git a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
index 5b5090e6d7b2b8..1b14286c821bf3 100644
--- a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
+++ b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
@@ -31,8 +31,13 @@ const usePatternsState = ( onInsert, rootClientId ) => {
const { __experimentalGetAllowedPatterns, getSettings } = select(
blockEditorStore
);
+ const inserterPatterns = __experimentalGetAllowedPatterns(
+ rootClientId
+ ).filter(
+ ( pattern ) => ! pattern.scope || pattern.scope.inserter
+ );
return {
- patterns: __experimentalGetAllowedPatterns( rootClientId ),
+ patterns: inserterPatterns,
patternCategories: getSettings()
.__experimentalBlockPatternCategories,
};
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 8dcac3f9cfb8c4..1e6e1a2073ae3f 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -1801,6 +1801,32 @@ export const __experimentalGetAllowedPatterns = createSelector(
]
);
+/**
+ * Returns the list of patterns based on specific `scope` and
+ * a block's name.
+ * `inserter` scope should be handled differently, probably in
+ * combination with `__experimentalGetAllowedPatterns`.
+ * For now `__experimentalGetScopedBlockPatterns` handles properly
+ * all other scopes.
+ * Since both APIs are experimental we should revisit this.
+ *
+ * @param {Object} state Editor state.
+ * @param {string} scope Block pattern scope.
+ * @param {string} blockName Block's name.
+ *
+ * @return {Array} The list of matched block patterns based on provided scope and block name.
+ */
+export const __experimentalGetScopedBlockPatterns = createSelector(
+ ( state, scope, blockName ) => {
+ if ( ! scope && ! blockName ) return EMPTY_ARRAY;
+ const patterns = state.settings.__experimentalBlockPatterns;
+ return patterns.filter( ( pattern ) =>
+ pattern.scope?.[ scope ]?.includes?.( blockName )
+ );
+ },
+ ( state ) => [ state.settings.__experimentalBlockPatterns ]
+);
+
/**
* Returns the Block List settings of a block, if any exist.
*
diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js
index 6ea84f777df21e..3d2e058dd844f6 100644
--- a/packages/block-editor/src/store/test/selectors.js
+++ b/packages/block-editor/src/store/test/selectors.js
@@ -72,6 +72,7 @@ const {
__experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames,
__experimentalGetParsedReusableBlock,
__experimentalGetAllowedPatterns,
+ __experimentalGetScopedBlockPatterns,
} = selectors;
describe( 'selectors', () => {
@@ -3402,6 +3403,53 @@ describe( 'selectors', () => {
).toHaveLength( 0 );
} );
} );
+ describe( '__experimentalGetScopedBlockPatterns', () => {
+ const state = {
+ blocks: {},
+ settings: {
+ __experimentalBlockPatterns: [
+ {
+ title: 'pattern a',
+ scope: { block: [ 'test/block-a' ] },
+ },
+ {
+ title: 'pattern b',
+ scope: { block: [ 'test/block-b' ] },
+ },
+ ],
+ },
+ };
+ it( 'should return empty array if no scope and block name is provided', () => {
+ expect( __experimentalGetScopedBlockPatterns( state ) ).toEqual(
+ []
+ );
+ expect(
+ __experimentalGetScopedBlockPatterns( state, 'block' )
+ ).toEqual( [] );
+ } );
+ it( 'shoud return empty array if no match is found', () => {
+ const patterns = __experimentalGetScopedBlockPatterns(
+ state,
+ 'block',
+ 'test/block-not-exists'
+ );
+ expect( patterns ).toEqual( [] );
+ } );
+ it( 'should return proper results when there are matched block patterns', () => {
+ const patterns = __experimentalGetScopedBlockPatterns(
+ state,
+ 'block',
+ 'test/block-a'
+ );
+ expect( patterns ).toHaveLength( 1 );
+ expect( patterns[ 0 ] ).toEqual(
+ expect.objectContaining( {
+ title: 'pattern a',
+ scope: { block: [ 'test/block-a' ] },
+ } )
+ );
+ } );
+ } );
} );
describe( '__experimentalGetParsedReusableBlock', () => {
diff --git a/packages/block-library/src/query/edit/block-setup/index.js b/packages/block-library/src/query/edit/block-setup/index.js
new file mode 100644
index 00000000000000..37008b6010ff7e
--- /dev/null
+++ b/packages/block-library/src/query/edit/block-setup/index.js
@@ -0,0 +1,49 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useBlockProps } from '@wordpress/block-editor';
+import { store as blocksStore } from '@wordpress/blocks';
+import { Placeholder } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import LayoutSetupStep from './layout-step';
+
+const BlockSetup = ( {
+ blockName,
+ useLayoutSetup,
+ onVariationSelect = () => {},
+ onBlockPatternSelect = () => {},
+ children,
+} ) => {
+ const { blockType } = useSelect(
+ ( select ) => {
+ const { getBlockType } = select( blocksStore );
+ return { blockType: getBlockType( blockName ) };
+ },
+ [ blockName ]
+ );
+ const blockProps = useBlockProps();
+ return (
+
+
+ { useLayoutSetup && (
+
+ ) }
+ { children }
+
+
+ );
+};
+
+export default BlockSetup;
diff --git a/packages/block-library/src/query/edit/block-setup/layout-step.js b/packages/block-library/src/query/edit/block-setup/layout-step.js
new file mode 100644
index 00000000000000..a2a206dbb0dde8
--- /dev/null
+++ b/packages/block-library/src/query/edit/block-setup/layout-step.js
@@ -0,0 +1,204 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useState, useMemo } from '@wordpress/element';
+import { parse, store as blocksStore } from '@wordpress/blocks';
+import { useInstanceId } from '@wordpress/compose';
+import {
+ BlockPreview,
+ store as blockEditorStore,
+} from '@wordpress/block-editor';
+import { __, isRTL } from '@wordpress/i18n';
+import {
+ Button,
+ Icon,
+ VisuallyHidden,
+ __unstableComposite as Composite,
+ __unstableUseCompositeState as useCompositeState,
+ __unstableCompositeItem as CompositeItem,
+} from '@wordpress/components';
+import { chevronRight, chevronLeft } from '@wordpress/icons';
+
+const LayoutSetupStep = ( {
+ blockType,
+ onVariationSelect,
+ onBlockPatternSelect,
+} ) => {
+ const [ showBack, setShowBack ] = useState( false );
+ const {
+ defaultVariation,
+ blockVariations,
+ patterns,
+ hasBlockVariations,
+ } = useSelect(
+ ( select ) => {
+ const { getBlockVariations, getDefaultBlockVariation } = select(
+ blocksStore
+ );
+ const { __experimentalGetScopedBlockPatterns } = select(
+ blockEditorStore
+ );
+ const { name } = blockType;
+ const _patterns = __experimentalGetScopedBlockPatterns(
+ 'block',
+ name
+ );
+ const _blockVariations = getBlockVariations( name, 'block' );
+ return {
+ defaultVariation: getDefaultBlockVariation( name, 'block' ),
+ blockVariations: _blockVariations,
+ patterns: _patterns,
+ hasBlockVariations: !! _blockVariations?.length,
+ };
+ },
+ [ blockType ]
+ );
+ const [ showBlockVariations, setShowBlockVariations ] = useState(
+ ! patterns?.length && hasBlockVariations
+ );
+ const composite = useCompositeState();
+ // Show nothing if block variations and block pattterns do not exist.
+ if ( ! hasBlockVariations && ! patterns?.length ) return null;
+
+ const showPatternsList = ! showBlockVariations && !! patterns.length;
+ return (
+ <>
+ { showBack && (
+
+ ) }
+
+ { showBlockVariations && (
+ <>
+ { blockVariations.map( ( variation ) => (
+ onVariationSelect( nextVariation ) }
+ composite={ composite }
+ />
+ ) ) }
+ >
+ ) }
+ { showPatternsList && (
+ <>
+ { patterns.map( ( pattern ) => (
+
+ ) ) }
+ >
+ ) }
+ { ! showBlockVariations && hasBlockVariations && (
+ {
+ setShowBack( true );
+ setShowBlockVariations( true );
+ } }
+ composite={ composite }
+ />
+ ) }
+
+ >
+ );
+};
+
+function BlockPattern( { pattern, onSelect, composite } ) {
+ const { content, viewportWidth } = pattern;
+ const blocks = useMemo( () => parse( content ), [ content ] );
+ const descriptionId = useInstanceId(
+ BlockPattern,
+ 'block-setup-block-layout-list__item-description'
+ );
+ return (
+
+
onSelect( blocks ) }
+ >
+
+
+
+ { pattern.title }
+
+ { !! pattern.description && (
+
+ { pattern.description }
+
+ ) }
+
+ );
+}
+
+function BlockVariation( { variation, onSelect, composite } ) {
+ const descriptionId = useInstanceId(
+ BlockVariation,
+ 'block-setup-block-layout-list__item-description'
+ );
+ return (
+
+
onSelect( variation ) }
+ label={ variation.description || variation.title }
+ >
+ { variation.icon && (
+
+
+
+ ) }
+
+
+ { variation.title }
+
+ { !! variation.description && (
+
+ { variation.description }
+
+ ) }
+
+ );
+}
+
+export default LayoutSetupStep;
diff --git a/packages/block-library/src/query/edit/index.js b/packages/block-library/src/query/edit/index.js
index 9b9c2a388604c6..5d6b59c033e2a7 100644
--- a/packages/block-library/src/query/edit/index.js
+++ b/packages/block-library/src/query/edit/index.js
@@ -7,8 +7,8 @@ import { useEffect } from '@wordpress/element';
import {
BlockControls,
useBlockProps,
- __experimentalUseInnerBlocksProps as useInnerBlocksProps,
store as blockEditorStore,
+ __experimentalUseInnerBlocksProps as useInnerBlocksProps,
} from '@wordpress/block-editor';
/**
@@ -17,7 +17,7 @@ import {
import QueryToolbar from './query-toolbar';
import QueryProvider from './query-provider';
import QueryInspectorControls from './query-inspector-controls';
-import QueryPlaceholder from './query-placeholder';
+import QueryBlockSetup from './query-block-setup';
import { DEFAULTS_POSTS_PER_PAGE } from '../constants';
const TEMPLATE = [ [ 'core/query-loop' ] ];
@@ -93,8 +93,7 @@ const QueryEdit = ( props ) => {
!! select( blockEditorStore ).getBlocks( clientId ).length,
[ clientId ]
);
- const Component = hasInnerBlocks ? QueryContent : QueryPlaceholder;
-
+ const Component = hasInnerBlocks ? QueryContent : QueryBlockSetup;
return ;
};
diff --git a/packages/block-library/src/query/edit/query-block-setup.js b/packages/block-library/src/query/edit/query-block-setup.js
new file mode 100644
index 00000000000000..2a89734275d67d
--- /dev/null
+++ b/packages/block-library/src/query/edit/query-block-setup.js
@@ -0,0 +1,87 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { __, _x } from '@wordpress/i18n';
+import { SelectControl, ToggleControl } from '@wordpress/components';
+import { createBlocksFromInnerBlocksTemplate } from '@wordpress/blocks';
+import { store as blockEditorStore } from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import BlockSetup from './block-setup';
+import { usePostTypes } from '../utils';
+
+const QueryBlockSetup = ( {
+ clientId,
+ attributes: { query },
+ setAttributes,
+ name: blockName,
+} ) => {
+ const { postType, inherit } = query;
+ const { replaceInnerBlocks } = useDispatch( blockEditorStore );
+ const { postTypesSelectOptions } = usePostTypes();
+ const updateQuery = ( newQuery ) =>
+ setAttributes( { query: { ...query, ...newQuery } } );
+ const onFinish = ( innerBlocks ) => {
+ if ( innerBlocks ) {
+ replaceInnerBlocks(
+ clientId,
+ createBlocksFromInnerBlocksTemplate( innerBlocks ),
+ false
+ );
+ }
+ };
+ const onVariationSelect = ( nextVariation ) => {
+ if ( nextVariation.attributes ) {
+ setAttributes( nextVariation.attributes );
+ }
+ if ( nextVariation.innerBlocks ) {
+ onFinish( nextVariation.innerBlocks );
+ }
+ };
+ const onBlockPatternSelect = ( blocks ) => {
+ onFinish( [ [ 'core/query-loop', {}, blocks ] ] );
+ };
+ const inheritToggleHelp = !! inherit
+ ? _x(
+ 'Inherit the global query depending on the URL.',
+ 'Query block `inherit` option helping text'
+ )
+ : _x(
+ 'Customize the query arguments.',
+ 'Query block `inherit` option helping text'
+ );
+ return (
+
+
+
+ updateQuery( { inherit: !! value } )
+ }
+ help={ inheritToggleHelp }
+ />
+ { ! inherit && (
+
+ updateQuery( { postType: newValue } )
+ }
+ />
+ ) }
+
+
+ );
+};
+
+export default QueryBlockSetup;
diff --git a/packages/block-library/src/query/edit/query-inspector-controls.js b/packages/block-library/src/query/edit/query-inspector-controls.js
index 188f5fdc9161f1..1b982e6d971f62 100644
--- a/packages/block-library/src/query/edit/query-inspector-controls.js
+++ b/packages/block-library/src/query/edit/query-inspector-controls.js
@@ -25,7 +25,6 @@ import {
useEffect,
useState,
useCallback,
- useMemo,
createInterpolateElement,
} from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';
@@ -33,7 +32,7 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
-import { getTermsInfo } from '../utils';
+import { getTermsInfo, usePostTypes } from '../utils';
import { MAX_FETCHED_TERMS } from '../constants';
const stickyOptions = [
@@ -73,43 +72,24 @@ export default function QueryInspectorControls( {
const [ showCategories, setShowCategories ] = useState( true );
const [ showTags, setShowTags ] = useState( true );
const [ showSticky, setShowSticky ] = useState( postType === 'post' );
- const { authorList, categories, tags, postTypes } = useSelect(
- ( select ) => {
- const { getEntityRecords, getPostTypes } = select( coreStore );
- const termsQuery = { per_page: MAX_FETCHED_TERMS };
- const _categories = getEntityRecords(
- 'taxonomy',
- 'category',
- termsQuery
- );
- const _tags = getEntityRecords(
- 'taxonomy',
- 'post_tag',
- termsQuery
- );
- const excludedPostTypes = [ 'attachment' ];
- const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter(
- ( { viewable, slug } ) =>
- viewable && ! excludedPostTypes.includes( slug )
- );
- return {
- categories: getTermsInfo( _categories ),
- tags: getTermsInfo( _tags ),
- authorList: getEntityRecords( 'root', 'user', {
- per_page: -1,
- } ),
- postTypes: filteredPostTypes,
- };
- },
- []
- );
- const postTypesTaxonomiesMap = useMemo( () => {
- if ( ! postTypes?.length ) return;
- return postTypes.reduce( ( accumulator, type ) => {
- accumulator[ type.slug ] = type.taxonomies;
- return accumulator;
- }, {} );
- }, [ postTypes ] );
+ const { postTypesTaxonomiesMap, postTypesSelectOptions } = usePostTypes();
+ const { authorList, categories, tags } = useSelect( ( select ) => {
+ const { getEntityRecords } = select( coreStore );
+ const termsQuery = { per_page: MAX_FETCHED_TERMS };
+ const _categories = getEntityRecords(
+ 'taxonomy',
+ 'category',
+ termsQuery
+ );
+ const _tags = getEntityRecords( 'taxonomy', 'post_tag', termsQuery );
+ return {
+ categories: getTermsInfo( _categories ),
+ tags: getTermsInfo( _tags ),
+ authorList: getEntityRecords( 'root', 'user', {
+ per_page: -1,
+ } ),
+ };
+ }, [] );
useEffect( () => {
if ( ! postTypesTaxonomiesMap ) return;
const postTypeTaxonomies = postTypesTaxonomiesMap[ postType ];
@@ -119,14 +99,6 @@ export default function QueryInspectorControls( {
useEffect( () => {
setShowSticky( postType === 'post' );
}, [ postType ] );
- const postTypesSelectOptions = useMemo(
- () =>
- ( postTypes || [] ).map( ( { labels, slug } ) => ( {
- label: labels.singular_name,
- value: slug,
- } ) ),
- [ postTypes ]
- );
const onPostTypeChange = ( newValue ) => {
const updateQuery = { postType: newValue };
if ( ! postTypesTaxonomiesMap[ newValue ].includes( 'category' ) ) {
diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss
index e28e6de5643246..3803d1b1e9fac3 100644
--- a/packages/block-library/src/query/editor.scss
+++ b/packages/block-library/src/query/editor.scss
@@ -9,3 +9,121 @@
.wp-block-query__create-new-link {
padding: 0 $grid-unit-20 $grid-unit-20 56px;
}
+
+.wp-block-query {
+ .components-placeholder {
+ .block-setup-navigation {
+ padding: $grid-unit-15 0 0;
+ }
+
+ .block-attributes-setup-container {
+ padding-top: $grid-unit-30;
+
+ .components-base-control__help {
+ margin: $grid-unit-15 auto;
+ }
+ }
+ }
+}
+
+.block-setup-block-layout-list__container {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+
+ .block-setup-block-layout-list__list-item {
+ cursor: pointer;
+ margin: 0 $grid-unit-15 $grid-unit-15 0;
+ width: 200px;
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+
+ &.is-block-variation {
+ width: 90px;
+
+ button {
+ display: inline-flex;
+ margin-right: 0;
+ height: auto;
+ }
+ }
+
+ .block-setup-block-layout-list__item-title {
+ padding: $grid-unit-05 0;
+ font-size: $helptext-font-size;
+ }
+
+ .block-setup-block-layout-list__item {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ padding: 2px;
+ transition: all 0.05s ease-in-out;
+ @include reduce-motion("transition");
+ border-radius: $radius-block-ui;
+ border: $border-width solid $gray-300;
+
+ &:hover {
+ border: $border-width solid var(--wp-admin-theme-color);
+ }
+
+ &:focus {
+ box-shadow: inset 0 0 0 1px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
+ // Windows High Contrast mode will show this outline, but not the box-shadow.
+ outline: 2px solid transparent;
+ }
+
+ .block-editor-block-preview__container {
+ margin: auto 0;
+ cursor: pointer; // This is for the BlockPreview.
+ }
+
+ .block-setup-block-layout-list__item-variation-icon {
+ color: var(--wp-admin-theme-color);
+ padding: $grid-unit-05 * 1.5;
+ border-radius: $radius-block-ui;
+ box-shadow: inset 0 0 0 1px var(--wp-admin-theme-color);
+ display: inline-flex;
+ margin: $grid-unit-15 auto auto auto;
+
+ svg {
+ fill: currentColor;
+ outline: none;
+ }
+ }
+ }
+ }
+
+ // Size consecutive block variation icons the same.
+ .block-setup-block-layout-list__list-item.is-block-variation .block-setup-block-layout-list__item {
+ height: 90px;
+ }
+
+ // Size the block variation icon same as the thumbnail on step 1.
+ .block-setup-block-layout-list__list-item:not(.is-block-variation) + .block-setup-block-layout-list__list-item.is-block-variation .block-setup-block-layout-list__item {
+ height: 100%;
+ }
+}
+
+.components-button.block-setup-block-layout-back-button.is-tertiary.has-icon {
+ color: inherit;
+ padding-left: 0;
+ display: inline-flex;
+ margin-right: auto;
+ margin-top: -$grid-unit-15;
+
+ &:hover:not(:disabled) {
+ box-shadow: none;
+ color: var(--wp-admin-theme-color);
+ }
+
+ &:active:not(:disabled) {
+ background: transparent;
+ color: $gray-300;
+ }
+
+ svg {
+ margin-right: 0;
+ }
+}
diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js
index 2e55a6b9365ced..d77961346ad15b 100644
--- a/packages/block-library/src/query/utils.js
+++ b/packages/block-library/src/query/utils.js
@@ -1,3 +1,10 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useMemo } from '@wordpress/element';
+import { store as coreStore } from '@wordpress/core-data';
+
/**
* WordPress term object from REST API.
* Categories ref: https://developer.wordpress.org/rest-api/reference/categories/
@@ -45,3 +52,40 @@ export const getTermsInfo = ( terms ) => ( {
{ mapById: {}, mapByName: {}, names: [] }
),
} );
+
+/**
+ * Returns a helper object that contains:
+ * 1. An `options` object from the available post types, to be passed to a `SelectControl`.
+ * 2. A helper map with available taxonomies per post type.
+ *
+ * @return {Object} The helper object related to post types.
+ */
+export const usePostTypes = () => {
+ const { postTypes } = useSelect( ( select ) => {
+ const { getPostTypes } = select( coreStore );
+ const excludedPostTypes = [ 'attachment' ];
+ const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter(
+ ( { viewable, slug } ) =>
+ viewable && ! excludedPostTypes.includes( slug )
+ );
+ return {
+ postTypes: filteredPostTypes,
+ };
+ }, [] );
+ const postTypesTaxonomiesMap = useMemo( () => {
+ if ( ! postTypes?.length ) return;
+ return postTypes.reduce( ( accumulator, type ) => {
+ accumulator[ type.slug ] = type.taxonomies;
+ return accumulator;
+ }, {} );
+ }, [ postTypes ] );
+ const postTypesSelectOptions = useMemo(
+ () =>
+ ( postTypes || [] ).map( ( { labels, slug } ) => ( {
+ label: labels.singular_name,
+ value: slug,
+ } ) ),
+ [ postTypes ]
+ );
+ return { postTypesTaxonomiesMap, postTypesSelectOptions };
+};