Skip to content

Commit

Permalink
Use SelectTree in Parent Category field (woocommerce#38261)
Browse files Browse the repository at this point in the history
* Increment label CSS to look the same as TextControl's label

* Migrate Parent Category field to SelectTree

* Add changelogs

* Fix unit test that was edited by mistake

* Revert css changes in select-control.scss

* Use BaseControl as label on SelectTree

* Increment changelog

* Increment changelogs

* Refactor test

* Refactor category-field functions and use them in related places

* Fix ordered list appearing empty
  • Loading branch information
nathanss authored May 19, 2023
1 parent 869b897 commit 5437ed5
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 227 deletions.
4 changes: 4 additions & 0 deletions packages/js/components/changelog/improve-selecttree-label
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Use BaseControl in the SelectTree label
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { chevronDown } from '@wordpress/icons';
import classNames from 'classnames';
import { createElement, useState } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { TextControl } from '@wordpress/components';
import { BaseControl, TextControl } from '@wordpress/components';

/**
* Internal dependencies
Expand Down Expand Up @@ -130,61 +130,55 @@ export const SelectTree = function SelectTree( {
}
) }
>
<label
htmlFor={ `${ props.id }-input` }
id={ `${ props.id }-label` }
className="woocommerce-experimental-select-control__label"
>
{ props.label }
</label>
{ props.multiple ? (
<ComboBox
comboBoxProps={ {
className:
'woocommerce-experimental-select-control__combo-box-wrapper',
role: 'combobox',
'aria-expanded': isOpen,
'aria-haspopup': 'tree',
'aria-labelledby': `${ props.id }-label`,
'aria-owns': `${ props.id }-menu`,
} }
inputProps={ inputProps }
suffix={ suffix }
>
<SelectedItems
isReadOnly={ isReadOnly }
items={ ( props.selected as Item[] ) || [] }
getItemLabel={ ( item ) => item?.label || '' }
getItemValue={ ( item ) => item?.value || '' }
onRemove={ ( item ) => {
if (
! Array.isArray( item ) &&
props.onRemove
) {
props.onRemove( item );
<BaseControl label={ props.label } id={ `${ props.id }-input` }>
{ props.multiple ? (
<ComboBox
comboBoxProps={ {
className:
'woocommerce-experimental-select-control__combo-box-wrapper',
role: 'combobox',
'aria-expanded': isOpen,
'aria-haspopup': 'tree',
'aria-owns': `${ props.id }-menu`,
} }
inputProps={ inputProps }
suffix={ suffix }
>
<SelectedItems
isReadOnly={ isReadOnly }
items={ ( props.selected as Item[] ) || [] }
getItemLabel={ ( item ) => item?.label || '' }
getItemValue={ ( item ) => item?.value || '' }
onRemove={ ( item ) => {
if (
! Array.isArray( item ) &&
props.onRemove
) {
props.onRemove( item );
}
} }
getSelectedItemProps={ () => ( {} ) }
/>
</ComboBox>
) : (
<TextControl
{ ...inputProps }
value={ props.createValue || '' }
onChange={ ( value ) => {
if ( onInputChange ) onInputChange( value );
const item = items.find(
( i ) => i.label === value
);
if ( props.onSelect && item ) {
props.onSelect( item );
}
if ( ! value && props.onRemove ) {
props.onRemove( props.selected as Item );
}
} }
getSelectedItemProps={ () => ( {} ) }
/>
</ComboBox>
) : (
<TextControl
{ ...inputProps }
value={ props.createValue || '' }
onChange={ ( value ) => {
if ( onInputChange ) onInputChange( value );
const item = items.find(
( i ) => i.label === value
);
if ( props.onSelect && item ) {
props.onSelect( item );
}
if ( ! value && props.onRemove ) {
props.onRemove( props.selected as Item );
}
} }
/>
) }
) }
</BaseControl>
</div>
<SelectTreeMenu
{ ...props }
Expand Down
58 changes: 30 additions & 28 deletions packages/js/components/src/experimental-tree-control/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,36 @@ export const Tree = forwardRef( function ForwardedTree(

return (
<>
<ol
{ ...treeProps }
className={ classNames(
treeProps.className,
'experimental-woocommerce-tree',
`experimental-woocommerce-tree--level-${ level }`
) }
>
{ items.map( ( child, index ) => (
<TreeItem
{ ...treeItemProps }
isExpanded={ props.isExpanded }
key={ child.data.value }
item={ child }
index={ index }
// Button ref is not working, so need to use CSS directly
onLastItemLoop={ () => {
(
rootListRef.current
?.closest( 'ol[role="tree"]' )
?.parentElement?.querySelector(
'.experimental-woocommerce-tree__button'
) as HTMLButtonElement
)?.focus();
} }
/>
) ) }
</ol>
{ items.length || isCreateButtonVisible ? (
<ol
{ ...treeProps }
className={ classNames(
treeProps.className,
'experimental-woocommerce-tree',
`experimental-woocommerce-tree--level-${ level }`
) }
>
{ items.map( ( child, index ) => (
<TreeItem
{ ...treeItemProps }
isExpanded={ props.isExpanded }
key={ child.data.value }
item={ child }
index={ index }
// Button ref is not working, so need to use CSS directly
onLastItemLoop={ () => {
(
rootListRef.current
?.closest( 'ol[role="tree"]' )
?.parentElement?.querySelector(
'.experimental-woocommerce-tree__button'
) as HTMLButtonElement
)?.focus();
} }
/>
) ) }
</ol>
) : null }
{ isCreateButtonVisible && (
<Button
className="experimental-woocommerce-tree__button"
Expand Down
4 changes: 4 additions & 0 deletions packages/js/product-editor/changelog/update-parent-categ
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Use SelectTree in the Parent Category field
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,41 @@ function getSelectedWithParents(
return selected;
}

function mapFromCategoryType(
export function mapFromCategoryToTreeItem(
val: ProductCategoryNode
): TreeItemType {
return val.parent
? {
value: String( val.id ),
label: val.name,
parent: String( val.parent ),
}
: {
value: String( val.id ),
label: val.name,
};
}

export function mapFromTreeItemToCategory(
val: TreeItemType
): ProductCategoryNode {
return {
id: +val.value,
name: val.label,
parent: val.parent ? +val.parent : 0,
};
}

export function mapFromCategoriesToTreeItems(
categories: ProductCategoryNode[]
): TreeItemType[] {
return categories.map( ( val ) =>
val.parent
? {
value: String( val.id ),
label: val.name,
parent: String( val.parent ),
}
: {
value: String( val.id ),
label: val.name,
}
);
return categories.map( mapFromCategoryToTreeItem );
}

function mapToCategoryType(
export function mapFromTreeItemsToCategories(
categories: TreeItemType[]
): ProductCategoryNode[] {
return categories.map( ( cat ) => ( {
id: +cat.value,
name: cat.label,
parent: cat.parent ? +cat.parent : 0,
} ) );
return categories.map( mapFromTreeItemToCategory );
}

export const CategoryField: React.FC< CategoryFieldProps > = ( {
Expand Down Expand Up @@ -129,15 +139,15 @@ export const CategoryField: React.FC< CategoryFieldProps > = ( {
) === -1
}
items={ getFilteredItemsForSelectTree(
mapFromCategoryType( categoriesSelectList ),
mapFromCategoriesToTreeItems( categoriesSelectList ),
searchValue,
mapFromCategoryType( value )
mapFromCategoriesToTreeItems( value )
) }
selected={ mapFromCategoryType( value ) }
selected={ mapFromCategoriesToTreeItems( value ) }
onSelect={ ( selectedItems ) => {
if ( Array.isArray( selectedItems ) ) {
const newItems: ProductCategoryNode[] =
mapToCategoryType(
mapFromTreeItemsToCategories(
selectedItems.filter(
( { value: selectedItemValue } ) =>
! value.some(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.woocommerce-create-new-category-modal {
min-width: 650px;
overflow: visible;

&__buttons {
margin-top: $gap-larger;
Expand Down
Loading

0 comments on commit 5437ed5

Please sign in to comment.