Skip to content

Commit

Permalink
Patterns: Add category selector to pattern creation modal (#55024)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: Kai Hao <kai@kaihao.dev>
  • Loading branch information
2 people authored and mikachan committed Oct 9, 2023
1 parent 89f5e33 commit 9398b4a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 48 deletions.
70 changes: 28 additions & 42 deletions packages/patterns/src/components/category-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,36 @@
import { __ } from '@wordpress/i18n';
import { useMemo, useState } from '@wordpress/element';
import { FormTokenField } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { useDebounce } from '@wordpress/compose';
import { decodeEntities } from '@wordpress/html-entities';

const unescapeString = ( arg ) => {
return decodeEntities( arg );
};

const EMPTY_ARRAY = [];
const MAX_TERMS_SUGGESTIONS = 20;
const DEFAULT_QUERY = {
per_page: MAX_TERMS_SUGGESTIONS,
_fields: 'id,name',
context: 'view',
};
export const CATEGORY_SLUG = 'wp_pattern_category';

export default function CategorySelector( { values, onChange } ) {
export default function CategorySelector( {
categoryTerms,
onChange,
categoryMap,
} ) {
const [ search, setSearch ] = useState( '' );
const debouncedSearch = useDebounce( setSearch, 500 );

const { searchResults } = useSelect(
( select ) => {
const { getEntityRecords } = select( coreStore );

return {
searchResults: !! search
? getEntityRecords( 'taxonomy', CATEGORY_SLUG, {
...DEFAULT_QUERY,
search,
} )
: EMPTY_ARRAY,
};
},
[ search ]
);

const suggestions = useMemo( () => {
return ( searchResults ?? [] ).map( ( term ) =>
unescapeString( term.name )
);
}, [ searchResults ] );
return Array.from( categoryMap.values() )
.map( ( category ) => unescapeString( category.label ) )
.filter( ( category ) => {
if ( search !== '' ) {
return category
.toLowerCase()
.includes( search.toLowerCase() );
}
return true;
} )
.sort( ( a, b ) => a.localeCompare( b ) );
}, [ search, categoryMap ] );

function handleChange( termNames ) {
const uniqueTerms = termNames.reduce( ( terms, newTerm ) => {
Expand All @@ -64,17 +51,16 @@ export default function CategorySelector( { values, onChange } ) {
}

return (
<>
<FormTokenField
className="patterns-menu-items__convert-modal-categories"
value={ values }
suggestions={ suggestions }
onChange={ handleChange }
onInputChange={ debouncedSearch }
maxSuggestions={ MAX_TERMS_SUGGESTIONS }
label={ __( 'Categories' ) }
tokenizeOnBlur={ true }
/>
</>
<FormTokenField
className="patterns-menu-items__convert-modal-categories"
value={ categoryTerms }
suggestions={ suggestions }
onChange={ handleChange }
onInputChange={ debouncedSearch }
label={ __( 'Categories' ) }
tokenizeOnBlur
__experimentalExpandOnFocus
__next40pxDefaultSize
/>
);
}
51 changes: 47 additions & 4 deletions packages/patterns/src/components/create-pattern-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
ToggleControl,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { useState, useMemo } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';

Expand Down Expand Up @@ -42,6 +42,42 @@ export default function CreatePatternModal( {
const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );
const { createErrorNotice } = useDispatch( noticesStore );

const { corePatternCategories, userPatternCategories } = useSelect(
( select ) => {
const { getUserPatternCategories, getBlockPatternCategories } =
select( coreStore );

return {
corePatternCategories: getBlockPatternCategories(),
userPatternCategories: getUserPatternCategories(),
};
}
);

const categoryMap = useMemo( () => {
// Merge the user and core pattern categories and remove any duplicates.
const uniqueCategories = new Map();
[ ...userPatternCategories, ...corePatternCategories ].forEach(
( category ) => {
if (
! uniqueCategories.has( category.label ) &&
// There are two core categories with `Post` label so explicitly remove the one with
// the `query` slug to avoid any confusion.
category.name !== 'query'
) {
// We need to store the name separately as this is used as the slug in the
// taxonomy and may vary from the label.
uniqueCategories.set( category.label, {
label: category.label,
value: category.label,
name: category.name,
} );
}
}
);
return uniqueCategories;
}, [ userPatternCategories, corePatternCategories ] );

async function onCreate( patternTitle, sync ) {
if ( ! title || isSaving ) {
return;
Expand Down Expand Up @@ -84,10 +120,16 @@ export default function CreatePatternModal( {
*/
async function findOrCreateTerm( term ) {
try {
// We need to match any existing term to the correct slug to prevent duplicates, eg.
// the core `Headers` category uses the singular `header` as the slug.
const existingTerm = categoryMap.get( term );
const termData = existingTerm
? { name: existingTerm.label, slug: existingTerm.name }
: { name: term };
const newTerm = await saveEntityRecord(
'taxonomy',
CATEGORY_SLUG,
{ name: term },
termData,
{ throwOnError: true }
);
invalidateResolution( 'getUserPatternCategories' );
Expand Down Expand Up @@ -126,8 +168,9 @@ export default function CreatePatternModal( {
className="patterns-create-modal__name-input"
/>
<CategorySelector
values={ categoryTerms }
categoryTerms={ categoryTerms }
onChange={ setCategoryTerms }
categoryMap={ categoryMap }
/>
<ToggleControl
label={ __( 'Synced' ) }
Expand Down
30 changes: 28 additions & 2 deletions packages/patterns/src/components/style.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
.patterns-menu-items__convert-modal {
z-index: z-index(".patterns-menu-items__convert-modal");

// Fix the modal width to prevent added categories from stretching the modal.
[role="dialog"] > [role="document"] {
width: 350px;
}

.patterns-menu-items__convert-modal-categories {
max-width: 300px;
width: 100%;
position: relative;
min-height: 40px;
}
.components-form-token-field__suggestions-list {
position: absolute;
box-sizing: border-box;
z-index: 1;
background-color: $white;
// Account for the border width of the token field.
width: calc(100% + 2px);
left: -1px;
min-width: initial;
border: 1px solid var(--wp-admin-theme-color);
border-top: none;
box-shadow: 0 0 0 0.5px var(--wp-admin-theme-color);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
}

.patterns-create-modal__name-input input[type="text"] {
min-height: 34px;
// Match the minimal height of the category selector.
min-height: 40px;
// Override the default 1px margin-x.
margin: 0;
}

0 comments on commit 9398b4a

Please sign in to comment.