-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update/34885 category field in product editor (#36869)
* Add initial custom meta box for product categories * Make use of TreeSelectControl * Update classnames * Display selected items and sync with most used tab * Always show placeholder and remove checklist container * Reactify category metabox tabs * Add create new category logic * Remove unused markup * Fix saving of empty category list * Add callback when input is cleared as well * Some small cleanup and refactoring. * Add changelog * Fix tree creation and style enqueue * Auto fix lint errors * Fix linting errors * Fix css lint errors * Add 100 limit, and address some PR feedback * Fix some styling and warnings * Remove unused code * Address PR feedback * Fix lint error * Fix lint errors * Address PR feedback * Fix lint error * Minor fixes and add tracking * Add debounce * Fix lint error * Allow custom min filter amount and fix menu not showing after escaping input * Allow single item to be cleared out of select control * Fix bug where typed values did not show up * Fix some styling issues * Allow parents to be individually selected * Address PR feedback and add error message * Add changelogs * Fix saving issue * Add client side sorting and stop clearing field upon selection * Update changelog * Create feature flag for async product categories dropdown * Fix lint errors * Fix linting
- Loading branch information
Showing
21 changed files
with
1,063 additions
and
16 deletions.
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
...ages/js/components/changelog/update-34885_category_field_in_product_editor_select_control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: minor | ||
Type: fix | ||
|
||
Fix issue where single item can not be cleared and text can not be selected upon click. |
4 changes: 4 additions & 0 deletions
4
packages/js/components/changelog/update-34885_category_field_in_product_editor_tree_changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: minor | ||
Type: add | ||
|
||
Add minFilterQueryLength, individuallySelectParent, and clearOnSelect props. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
.../woocommerce-admin/client/wp-admin-scripts/product-category-metabox/all-category-list.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { | ||
forwardRef, | ||
useCallback, | ||
useEffect, | ||
useImperativeHandle, | ||
useState, | ||
} from '@wordpress/element'; | ||
import { addQueryArgs } from '@wordpress/url'; | ||
import { useDebounce } from '@wordpress/compose'; | ||
import { TreeSelectControl } from '@woocommerce/components'; | ||
import { getSetting } from '@woocommerce/settings'; | ||
import { recordEvent } from '@woocommerce/tracks'; | ||
import apiFetch from '@wordpress/api-fetch'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { CATEGORY_TERM_NAME } from './category-handlers'; | ||
import { CategoryTerm } from './popular-category-list'; | ||
|
||
declare const wc_product_category_metabox_params: { | ||
search_categories_nonce: string; | ||
}; | ||
|
||
type CategoryTreeItem = CategoryTerm & { | ||
children?: CategoryTreeItem[]; | ||
}; | ||
|
||
type CategoryTreeItemLabelValue = { | ||
children: CategoryTreeItemLabelValue[]; | ||
label: string; | ||
value: string; | ||
}; | ||
|
||
export const DEFAULT_DEBOUNCE_TIME = 250; | ||
|
||
const categoryLibrary: Record< number, CategoryTreeItem > = {}; | ||
function convertTreeToLabelValue( | ||
tree: CategoryTreeItem[], | ||
newTree: CategoryTreeItemLabelValue[] = [] | ||
) { | ||
for ( const child of tree ) { | ||
const newItem = { | ||
label: child.name, | ||
value: child.term_id.toString(), | ||
children: [], | ||
}; | ||
categoryLibrary[ child.term_id ] = child; | ||
newTree.push( newItem ); | ||
if ( child.children?.length ) { | ||
convertTreeToLabelValue( child.children, newItem.children ); | ||
} | ||
} | ||
newTree.sort( | ||
( a: CategoryTreeItemLabelValue, b: CategoryTreeItemLabelValue ) => { | ||
const nameA = a.label.toUpperCase(); | ||
const nameB = b.label.toUpperCase(); | ||
if ( nameA < nameB ) { | ||
return -1; | ||
} | ||
if ( nameA > nameB ) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
); | ||
return newTree; | ||
} | ||
|
||
async function getTreeItems( filter: string ) { | ||
const resp = await apiFetch< CategoryTreeItem[] >( { | ||
url: addQueryArgs( | ||
new URL( 'admin-ajax.php', getSetting( 'adminUrl' ) ).toString(), | ||
{ | ||
term: filter, | ||
action: 'woocommerce_json_search_categories_tree', | ||
// eslint-disable-next-line no-undef, camelcase | ||
security: | ||
wc_product_category_metabox_params.search_categories_nonce, | ||
} | ||
), | ||
method: 'GET', | ||
} ); | ||
if ( resp ) { | ||
return convertTreeToLabelValue( Object.values( resp ) ); | ||
} | ||
return []; | ||
} | ||
|
||
export const AllCategoryList = forwardRef< | ||
{ resetInitialValues: () => void }, | ||
{ | ||
selectedCategoryTerms: CategoryTerm[]; | ||
onChange: ( selected: CategoryTerm[] ) => void; | ||
} | ||
>( ( { selectedCategoryTerms, onChange }, ref ) => { | ||
const [ filter, setFilter ] = useState( '' ); | ||
const [ treeItems, setTreeItems ] = useState< | ||
CategoryTreeItemLabelValue[] | ||
>( [] ); | ||
|
||
const searchCategories = useCallback( | ||
( value: string ) => { | ||
if ( value && value.length > 0 ) { | ||
recordEvent( 'product_category_search', { | ||
page: 'product', | ||
async: true, | ||
search_string_length: value.length, | ||
} ); | ||
} | ||
getTreeItems( value ).then( ( res ) => { | ||
setTreeItems( Object.values( res ) ); | ||
} ); | ||
}, | ||
[ setTreeItems ] | ||
); | ||
const searchCategoriesDebounced = useDebounce( | ||
searchCategories, | ||
DEFAULT_DEBOUNCE_TIME | ||
); | ||
|
||
useEffect( () => { | ||
searchCategoriesDebounced( filter ); | ||
}, [ filter ] ); | ||
|
||
useImperativeHandle( | ||
ref, | ||
() => { | ||
return { | ||
resetInitialValues() { | ||
getTreeItems( '' ).then( ( res ) => { | ||
setTreeItems( Object.values( res ) ); | ||
} ); | ||
}, | ||
}; | ||
}, | ||
[] | ||
); | ||
|
||
return ( | ||
<> | ||
<div className="product-add-category__tree-control"> | ||
<TreeSelectControl | ||
alwaysShowPlaceholder={ true } | ||
options={ treeItems } | ||
value={ selectedCategoryTerms.map( ( category ) => | ||
category.term_id.toString() | ||
) } | ||
onChange={ ( selectedCategoryIds: number[] ) => { | ||
onChange( | ||
selectedCategoryIds.map( | ||
( id ) => categoryLibrary[ id ] | ||
) | ||
); | ||
recordEvent( 'product_category_update', { | ||
page: 'product', | ||
async: true, | ||
selected: selectedCategoryIds.length, | ||
} ); | ||
} } | ||
selectAllLabel={ false } | ||
onInputChange={ setFilter } | ||
placeholder={ __( 'Add category', 'woocommerce' ) } | ||
includeParent={ true } | ||
minFilterQueryLength={ 2 } | ||
clearOnSelect={ false } | ||
individuallySelectParent={ true } | ||
/> | ||
</div> | ||
<ul | ||
// Adding tagchecklist class to make use of already existing styling for the selected categories. | ||
className="categorychecklist form-no-clear tagchecklist" | ||
id={ CATEGORY_TERM_NAME + 'checklist' } | ||
> | ||
{ selectedCategoryTerms.map( ( selectedCategory ) => ( | ||
<li key={ selectedCategory.term_id }> | ||
<button | ||
type="button" | ||
className="ntdelbutton" | ||
onClick={ () => { | ||
const newSelectedItems = | ||
selectedCategoryTerms.filter( | ||
( category ) => | ||
category.term_id !== | ||
selectedCategory.term_id | ||
); | ||
onChange( newSelectedItems ); | ||
} } | ||
> | ||
<span | ||
className="remove-tag-icon" | ||
aria-hidden="true" | ||
></span> | ||
<span className="screen-reader-text"> | ||
{ sprintf( | ||
__( 'Remove term: %s', 'woocommerce' ), | ||
selectedCategory.name | ||
) } | ||
</span> | ||
</button> | ||
{ selectedCategory.name } | ||
</li> | ||
) ) } | ||
</ul> | ||
</> | ||
); | ||
} ); |
Oops, something went wrong.