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:
parent
2ead36530d
commit
e684ae348b
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add empty state when no attributes #41679
|
|
@ -7,6 +7,9 @@
|
|||
&__add-new-icon {
|
||||
margin-right: $gap-small;
|
||||
}
|
||||
&__no-results {
|
||||
padding: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-experimental-select-control__popover-menu-container {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
Loading…
Reference in New Issue