Add an empty state to the variations table when there are no options added yet (#44163)

* Create variation empty state when no variable attributes are asigned to the product

* Add changelog file

* Remove the Add new button when a custom empty state is set

* Conditionally invalidate resolution before requesting a new variations page
This commit is contained in:
Maikel Perez 2024-02-05 18:00:37 -03:00 committed by GitHub
parent 7e7b78f943
commit 26bcbb0417
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 201 additions and 32 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Create variation empty state when no variable attributes are asigned to the product

View File

@ -22,11 +22,13 @@ import { useEntityId, useEntityProp } from '@wordpress/core-data';
*/
import { VariationsTable } from '../../../components/variations-table';
import { useValidation } from '../../../contexts/validation-context';
import useProductEntityProp from '../../../hooks/use-product-entity-prop';
import { VariationOptionsBlockAttributes } from './types';
import { VariableProductTour } from './variable-product-tour';
import { TRACKS_SOURCE } from '../../../constants';
import { handlePrompt } from '../../../utils/handle-prompt';
import { ProductEditorBlockEditProps } from '../../../types';
import { EmptyState } from './empty-state';
export function Edit( {
attributes,
@ -47,6 +49,17 @@ export function Edit( {
'product',
'status'
);
const [ productAttributes ] =
useProductEntityProp< Product[ 'attributes' ] >( 'attributes' );
const hasVariationOptions = useMemo(
function hasAttributesUsedForVariations() {
return productAttributes?.some(
( productAttribute ) => productAttribute.variation
);
},
[ productAttributes ]
);
const totalCountWithoutPriceRequestParams = useMemo(
() => ( {
@ -162,6 +175,10 @@ export function Edit( {
)
: '';
if ( ! hasVariationOptions ) {
return <EmptyState />;
}
return (
<div { ...blockProps }>
<VariationsTable

View File

@ -1,3 +1,5 @@
@import "empty-state/style.scss";
.wp-block-woocommerce-product-variation-items-field {
@media ( min-width: #{ ($break-medium) } ) {
min-height: 420px;

View File

@ -0,0 +1,54 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
export function EmptyState(
props: React.DetailedHTMLProps<
React.HTMLAttributes< HTMLDivElement >,
HTMLDivElement
>
) {
return (
<div
{ ...props }
role="none"
className="wp-block-woocommerce-product-variation-items-field__empty-state"
>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-row">
<div>{ __( 'Variation', 'woocommerce' ) }</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-name" />
</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-actions" />
</div>
</div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-row">
<div>{ __( 'Colors', 'woocommerce' ) }</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-name" />
</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-actions" />
</div>
</div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-row">
<div>{ __( 'Sizes', 'woocommerce' ) }</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-name" />
</div>
<div>
<div className="wp-block-woocommerce-product-variation-items-field__empty-state-actions" />
</div>
</div>
</div>
);
}

View File

@ -0,0 +1 @@
export * from './empty-state';

View File

@ -0,0 +1,59 @@
.wp-block-woocommerce-product-variation-items-field__empty-state {
@mixin skeleton {
@include placeholder();
background-color: $gray-200;
border-radius: $grid-unit-05;
width: $grid-unit-30;
height: $grid-unit;
}
border: 1px dashed $gray-400;
padding: 0 $grid-unit-30;
border-radius: 2px;
&-row {
display: grid;
grid-template-columns: 1.5fr 1fr 0.5fr;
height: $grid-unit-80;
align-items: center;
border-top: 1px solid $gray-100;
&:first-child {
border-top: none;
.wp-block-woocommerce-product-variation-items-field__empty-state-name {
width: 140px;
}
}
&:nth-child(2) {
opacity: 0.7;
.wp-block-woocommerce-product-variation-items-field__empty-state-name {
width: 75px;
}
}
&:nth-child(3) {
opacity: 0.5;
.wp-block-woocommerce-product-variation-items-field__empty-state-name {
width: 114px;
}
}
:last-child {
display: flex;
justify-content: flex-end;
}
}
&-name {
@include skeleton();
}
&-actions {
@include skeleton();
width: $grid-unit-60;
}
}

View File

@ -53,7 +53,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
onRemove = () => {},
onRemoveCancel = () => {},
onNoticeDismiss = () => {},
renderCustomEmptyState = () => <AttributeEmptyStateSkeleton />,
renderCustomEmptyState,
uiStrings,
createNewAttributesAsGlobal = false,
useRemoveConfirmationModal = false,
@ -203,19 +203,43 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
const isMobileViewport = useViewportMatch( 'medium', '<' );
function renderEmptyState() {
if ( isMobileViewport || value.length ) return null;
if ( renderCustomEmptyState ) {
return renderCustomEmptyState( {
addAttribute( search ) {
setDefaultAttributeSearch( search );
openNewModal();
},
} );
}
return <AttributeEmptyStateSkeleton />;
}
function renderSectionActions() {
if ( renderCustomEmptyState && value.length === 0 ) return null;
return (
<SectionActions>
{ uiStrings?.newAttributeListItemLabel && (
<Button
variant="secondary"
className="woocommerce-add-attribute-list-item__add-button"
onClick={ openNewModal }
>
{ uiStrings.newAttributeListItemLabel }
</Button>
) }
</SectionActions>
);
}
return (
<div className="woocommerce-attribute-field">
<SectionActions>
<Button
variant="secondary"
className="woocommerce-add-attribute-list-item__add-button"
onClick={ () => {
openNewModal();
} }
>
{ uiStrings.newAttributeListItemLabel }
</Button>
</SectionActions>
{ renderSectionActions() }
{ uiStrings.notice && (
<Notice
isDismissible={ true }
@ -345,14 +369,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
} }
/>
) }
{ ! isMobileViewport &&
value.length === 0 &&
renderCustomEmptyState( {
addAttribute( search ) {
setDefaultAttributeSearch( search );
openNewModal();
},
} ) }
{ renderEmptyState() }
</div>
);
};

View File

@ -37,7 +37,10 @@ export function useVariations( { productId }: UseVariationsProps ) {
const [ filters, setFilters ] = useState< AttributeFilters[] >( [] );
const perPageRef = useRef( DEFAULT_VARIATION_PER_PAGE_OPTION );
async function getCurrentVariationsPage( params: GetVariationsRequest ) {
async function getCurrentVariationsPage(
params: GetVariationsRequest,
invalidateResolutionBeforeRequest = false
) {
const requestParams: GetVariationsRequest = {
page: 1,
per_page: perPageRef.current,
@ -48,6 +51,19 @@ export function useVariations( { productId }: UseVariationsProps ) {
};
try {
const { invalidateResolution } = dispatch(
EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME
);
if ( invalidateResolutionBeforeRequest ) {
await invalidateResolution( 'getProductVariations', [
requestParams,
] );
await invalidateResolution( 'getProductVariationsTotalCount', [
requestParams,
] );
}
const { getProductVariations, getProductVariationsTotalCount } =
resolveSelect( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME );
@ -455,29 +471,28 @@ export function useVariations( { productId }: UseVariationsProps ) {
const wasGenerating = useRef( false );
useEffect( () => {
if ( ! isGenerating ) {
getCurrentVariationsPage( { product_id: productId } );
}
}, [ productId, isGenerating ] );
useEffect( () => {
if ( isGenerating ) {
clearFilters();
onClearSelection();
}
const didMount =
wasGenerating.current === false && isGenerating === false;
const didGenerate =
wasGenerating.current === true && isGenerating === false;
if ( didGenerate ) {
getCurrentVariationsPage( {
product_id: productId,
} );
if ( didMount || didGenerate ) {
getCurrentVariationsPage(
{
product_id: productId,
},
true
);
}
wasGenerating.current = Boolean( isGenerating );
}, [ isGenerating ] );
}, [ productId, isGenerating ] );
return {
isLoading,