Add empty state when no attributes (#41679)

* Add empty state for attributes

* Add styles

* Remove comment

* Add changelog

* Remove downshift

# Conflicts:
#	pnpm-lock.yaml

* move changelog

* Remove another comment

* Create MenuAttributeList component

* Refactor renderMenuItems

* Replace div with Fragment
This commit is contained in:
Fernando Marichal 2023-11-29 11:10:37 -03:00 committed by GitHub
parent 2ead36530d
commit e684ae348b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 77 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add empty state when no attributes #41679

View File

@ -7,6 +7,9 @@
&__add-new-icon {
margin-right: $gap-small;
}
&__no-results {
padding: $gap-small;
}
}
.woocommerce-experimental-select-control__popover-menu-container {

View File

@ -1,15 +1,12 @@
/**
* External dependencies
*/
import { sprintf, __ } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { Spinner, Icon } from '@wordpress/components';
import { plus } from '@wordpress/icons';
import { Spinner } from '@wordpress/components';
import { createElement, useMemo } from '@wordpress/element';
import {
EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME,
QueryProductAttribute,
ProductAttribute,
WCDataSelector,
ProductAttributesActions,
WPDataActions,
@ -18,43 +15,21 @@ import { recordEvent } from '@woocommerce/tracks';
import {
__experimentalSelectControl as SelectControl,
__experimentalSelectControlMenu as Menu,
__experimentalSelectControlMenuItem as MenuItem,
} from '@woocommerce/components';
/**
* Internal dependencies
*/
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
import { TRACKS_SOURCE } from '../../constants';
type NarrowedQueryAttribute = Pick< QueryProductAttribute, 'id' | 'name' > & {
slug?: string;
isDisabled?: boolean;
};
type AttributeInputFieldProps = {
value?: EnhancedProductAttribute | null;
onChange: (
value?:
| Omit< ProductAttribute, 'position' | 'visible' | 'variation' >
| string
) => void;
label?: string;
placeholder?: string;
disabled?: boolean;
disabledAttributeIds?: number[];
disabledAttributeMessage?: string;
ignoredAttributeIds?: number[];
createNewAttributesAsGlobal?: boolean;
};
function isNewAttributeListItem( attribute: NarrowedQueryAttribute ): boolean {
return attribute.id === -99;
}
function sanitizeSlugName( slug: string | undefined ): string {
return slug && slug.startsWith( 'pa_' ) ? slug.substring( 3 ) : '';
}
import { MenuAttributeList } from './menu-attribute-list';
import {
AttributeInputFieldProps,
getItemPropsType,
getMenuPropsType,
NarrowedQueryAttribute,
UseComboboxGetItemPropsOptions,
UseComboboxGetMenuPropsOptions,
} from './types';
export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
value = null,
@ -86,7 +61,7 @@ export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
const markedAttributes = useMemo(
function setDisabledAttribute() {
return (
attributes?.map( ( attribute ) => ( {
attributes?.map( ( attribute: NarrowedQueryAttribute ) => ( {
...attribute,
isDisabled: disabledAttributeIds.includes( attribute.id ),
} ) ) ?? []
@ -95,6 +70,12 @@ export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
[ attributes, disabledAttributeIds ]
);
function isNewAttributeListItem(
attribute: NarrowedQueryAttribute
): boolean {
return attribute.id === -99;
}
const getFilteredItems = (
allItems: NarrowedQueryAttribute[],
inputValue: string
@ -175,7 +156,7 @@ export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
getItemLabel={ ( item ) => item?.name || '' }
getItemValue={ ( item ) => item?.id || '' }
selected={ value }
onSelect={ ( attribute ) => {
onSelect={ ( attribute: NarrowedQueryAttribute ) => {
if ( isNewAttributeListItem( attribute ) ) {
addNewAttribute( attribute );
} else {
@ -196,51 +177,33 @@ export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
getItemProps,
getMenuProps,
isOpen,
}: {
items: NarrowedQueryAttribute[];
highlightedIndex: number;
getItemProps: (
options: UseComboboxGetItemPropsOptions< NarrowedQueryAttribute >
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => any;
getMenuProps: getMenuPropsType;
isOpen: boolean;
} ) => {
return (
<Menu getMenuProps={ getMenuProps } isOpen={ isOpen }>
{ isLoading ? (
<Spinner />
) : (
renderItems.map( ( item, index: number ) => (
<MenuItem
key={ item.id }
index={ index }
isActive={ highlightedIndex === index }
item={ item }
getItemProps={ ( options ) => ( {
...getItemProps( options ),
disabled: item.isDisabled || undefined,
} ) }
tooltipText={
item.isDisabled
? disabledAttributeMessage
: sanitizeSlugName( item.slug )
}
>
{ isNewAttributeListItem( item ) ? (
<div className="woocommerce-attribute-input-field__add-new">
<Icon
icon={ plus }
size={ 20 }
className="woocommerce-attribute-input-field__add-new-icon"
/>
<span>
{ sprintf(
/* translators: The name of the new attribute term to be created */
__(
'Create "%s"',
'woocommerce'
),
item.name
) }
</span>
</div>
) : (
item.name
) }
</MenuItem>
) )
<MenuAttributeList
renderItems={ renderItems }
highlightedIndex={ highlightedIndex }
disabledAttributeMessage={
disabledAttributeMessage
}
getItemProps={
getItemProps as (
options: UseComboboxGetMenuPropsOptions
) => getItemPropsType< NarrowedQueryAttribute >
}
/>
) }
</Menu>
);

View File

@ -0,0 +1,82 @@
/**
* External dependencies
*/
import { sprintf, __ } from '@wordpress/i18n';
import { plus } from '@wordpress/icons';
import { Icon } from '@wordpress/components';
import { createElement, Fragment } from '@wordpress/element';
import { __experimentalSelectControlMenuItem as MenuItem } from '@woocommerce/components';
/**
* Internal dependencies
*/
import {
MenuAttributeListProps,
NarrowedQueryAttribute,
UseComboboxGetMenuPropsOptions,
} from './types';
function isNewAttributeListItem( attribute: NarrowedQueryAttribute ): boolean {
return attribute.id === -99;
}
function sanitizeSlugName( slug: string | undefined ): string {
return slug && slug.startsWith( 'pa_' ) ? slug.substring( 3 ) : '';
}
export const MenuAttributeList: React.FC< MenuAttributeListProps > = ( {
disabledAttributeMessage = '',
renderItems,
highlightedIndex,
getItemProps,
} ) => {
if ( renderItems.length > 0 ) {
return (
<Fragment>
{ renderItems.map( ( item, index: number ) => (
<MenuItem
key={ item.id }
index={ index }
isActive={ highlightedIndex === index }
item={ item }
getItemProps={ (
options: UseComboboxGetMenuPropsOptions
) => ( {
...getItemProps( options ),
disabled: item.isDisabled || undefined,
} ) }
tooltipText={
item.isDisabled
? disabledAttributeMessage
: sanitizeSlugName( item.slug )
}
>
{ isNewAttributeListItem( item ) ? (
<div className="woocommerce-attribute-input-field__add-new">
<Icon
icon={ plus }
size={ 20 }
className="woocommerce-attribute-input-field__add-new-icon"
/>
<span>
{ sprintf(
/* translators: The name of the new attribute term to be created */
__( 'Create "%s"', 'woocommerce' ),
item.name
) }
</span>
</div>
) : (
item.name
) }
</MenuItem>
) ) }
</Fragment>
);
}
return (
<div className="woocommerce-attribute-input-field__no-results">
{ __( 'Nothing yet. Type to create.', 'woocommerce' ) }
</div>
);
};

View File

@ -0,0 +1,82 @@
/**
* External dependencies
*/
import { QueryProductAttribute, ProductAttribute } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
export type NarrowedQueryAttribute = Pick<
QueryProductAttribute,
'id' | 'name'
> & {
slug?: string;
isDisabled?: boolean;
};
export type AttributeInputFieldProps = {
value?: EnhancedProductAttribute | null;
onChange: (
value?:
| Omit< ProductAttribute, 'position' | 'visible' | 'variation' >
| string
) => void;
label?: string;
placeholder?: string;
disabled?: boolean;
disabledAttributeIds?: number[];
disabledAttributeMessage?: string;
ignoredAttributeIds?: number[];
createNewAttributesAsGlobal?: boolean;
};
export type MenuAttributeListProps = {
renderItems: NarrowedQueryAttribute[];
highlightedIndex: number;
disabledAttributeMessage?: string;
getItemProps: (
options: UseComboboxGetMenuPropsOptions
) => getItemPropsType< NarrowedQueryAttribute >;
};
export interface GetPropsWithRefKey {
refKey?: string;
}
export interface GetMenuPropsOptions
extends React.HTMLProps< HTMLElement >,
GetPropsWithRefKey {
[ 'aria-label' ]?: string;
}
export interface UseComboboxGetMenuPropsOptions
extends GetPropsWithRefKey,
GetMenuPropsOptions {}
export interface GetPropsCommonOptions {
suppressRefError?: boolean;
}
export interface GetItemPropsOptions< Item >
extends React.HTMLProps< HTMLElement > {
index?: number;
item: Item;
isSelected?: boolean;
disabled?: boolean;
}
export interface UseComboboxGetItemPropsOptions< Item >
extends GetItemPropsOptions< Item >,
GetPropsWithRefKey {}
export type getMenuPropsType = (
options?: UseComboboxGetMenuPropsOptions,
otherOptions?: GetPropsCommonOptions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => any;
export type getItemPropsType< ItemType > = (
options: UseComboboxGetItemPropsOptions< ItemType >
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => any;