Add slotFill for variation menus (#43441)
* Add slotFills to main variation actions * Add slotFills to groups * Create variation-actions component * Add references * Remove file and change reference * Add Dropdown * Add styles * Add changelog * split variation-actions-menu * Add tests * Remove variation-actions-menu and variations-actions-menu * Rename variation-actions and add tests * Rename VariationSingleUpdateMenuItem * Rename single-update * Add supportsMultipleSelection and slotFill rename * Add component export * Rename groups * Show multipleSelection items in both components * Take fills as MenuItems * Remove __experimental from slotFill * Fix lint * Add onClick to slotFill * Rename constant QUICK_UPDATE, add new constant * Add tests for getGroupName and getMenuItem * Rename variation-actions tests * fix test description * Add onChange, onClose and selection to slots * Refactor slotFill * Refactor slotFill file * Rename QuickUpdateMenu to MultipleUpdateMenu * Modify tests * Fix test name * Improve import * Always return array of selected items * Create MenuGroup for every extension menu items * Group fills
This commit is contained in:
parent
1a4ebebf6a
commit
2acbb3d3fb
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add slotFill for variation menus #43441
|
|
@ -32,6 +32,7 @@ export { AttributeControl as __experimentalAttributeControl } from './attribute-
|
|||
export { Attributes as __experimentalAttributes } from './attributes';
|
||||
export * from './add-new-shipping-class-modal';
|
||||
export { VariationSwitcherFooter as __experimentalVariationSwitcherFooter } from './variation-switcher-footer';
|
||||
export { VariationQuickUpdateMenuItem } from './variations-table/variation-actions-menus';
|
||||
|
||||
export * from './remove-confirmation-modal';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Dropdown, MenuItem } from '@wordpress/components';
|
||||
import { Dropdown, MenuItem, MenuGroup } from '@wordpress/components';
|
||||
import { createElement, useEffect, useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronRight } from '@wordpress/icons';
|
||||
|
@ -15,6 +15,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { VariationActionsMenuItemProps } from '../types';
|
||||
import { handlePrompt } from '../../../utils/handle-prompt';
|
||||
import { VariationQuickUpdateMenuItem } from '../variation-actions-menus';
|
||||
|
||||
const MODAL_CLASS_NAME = 'downloads_menu_item__upload_files_modal';
|
||||
const MODAL_WRAPPER_CLASS_NAME =
|
||||
|
@ -28,6 +29,7 @@ export function DownloadsMenuItem( {
|
|||
selection,
|
||||
onChange,
|
||||
onClose,
|
||||
supportsMultipleSelection = false,
|
||||
}: VariationActionsMenuItemProps ) {
|
||||
const ids = Array.isArray( selection )
|
||||
? selection.map( ( { id } ) => id )
|
||||
|
@ -161,44 +163,53 @@ export function DownloadsMenuItem( {
|
|||
) }
|
||||
renderContent={ () => (
|
||||
<div className="components-dropdown-menu__menu">
|
||||
<MediaUpload
|
||||
modalClass={ MODAL_CLASS_NAME }
|
||||
// @ts-expect-error multiple also accepts string.
|
||||
multiple={ 'add' }
|
||||
value={ downloadsIds }
|
||||
onSelect={ handleMediaUploadSelect }
|
||||
render={ ( { open } ) => (
|
||||
<MenuItem
|
||||
onClick={ uploadFilesClickHandler( open ) }
|
||||
>
|
||||
{ __( 'Upload files', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
) }
|
||||
<MenuGroup>
|
||||
<MediaUpload
|
||||
modalClass={ MODAL_CLASS_NAME }
|
||||
// @ts-expect-error multiple also accepts string.
|
||||
multiple={ 'add' }
|
||||
value={ downloadsIds }
|
||||
onSelect={ handleMediaUploadSelect }
|
||||
render={ ( { open } ) => (
|
||||
<MenuItem
|
||||
onClick={ uploadFilesClickHandler( open ) }
|
||||
>
|
||||
{ __( 'Upload files', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
) }
|
||||
/>
|
||||
|
||||
<MenuItem
|
||||
onClick={ menuItemClickHandler(
|
||||
'download_limit',
|
||||
__(
|
||||
'Leave blank for unlimited re-downloads',
|
||||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
>
|
||||
{ __( 'Set download limit', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
onClick={ menuItemClickHandler(
|
||||
'download_expiry',
|
||||
__(
|
||||
'Enter the number of days before a download link expires, or leave blank',
|
||||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
>
|
||||
{ __( 'Set download expiry', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'downloads' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
|
||||
<MenuItem
|
||||
onClick={ menuItemClickHandler(
|
||||
'download_limit',
|
||||
__(
|
||||
'Leave blank for unlimited re-downloads',
|
||||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
>
|
||||
{ __( 'Set download limit', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
onClick={ menuItemClickHandler(
|
||||
'download_expiry',
|
||||
__(
|
||||
'Enter the number of days before a download link expires, or leave blank',
|
||||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
>
|
||||
{ __( 'Set download expiry', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './variations-table';
|
||||
export * from './variation-actions-menus';
|
||||
export * from './types';
|
||||
|
|
|
@ -15,11 +15,13 @@ import { PRODUCT_STOCK_STATUS_KEYS } from '../../../utils/get-product-stock-stat
|
|||
import { UpdateStockMenuItem } from '../update-stock-menu-item';
|
||||
import { VariationActionsMenuItemProps } from '../types';
|
||||
import { handlePrompt } from '../../../utils/handle-prompt';
|
||||
import { VariationQuickUpdateMenuItem } from '../variation-actions-menus';
|
||||
|
||||
export function InventoryMenuItem( {
|
||||
selection,
|
||||
onChange,
|
||||
onClose,
|
||||
supportsMultipleSelection = false,
|
||||
}: VariationActionsMenuItemProps ) {
|
||||
const ids = Array.isArray( selection )
|
||||
? selection.map( ( { id } ) => id )
|
||||
|
@ -234,6 +236,13 @@ export function InventoryMenuItem( {
|
|||
{ __( 'Edit low stock threshold', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'inventory' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { TRACKS_SOURCE } from '../../../constants';
|
|||
import { handlePrompt } from '../../../utils/handle-prompt';
|
||||
import { VariationActionsMenuItemProps } from '../types';
|
||||
import { SetListPriceMenuItem } from '../set-list-price-menu-item';
|
||||
import { VariationQuickUpdateMenuItem } from '../variation-actions-menus';
|
||||
|
||||
function isPercentage( value: string ) {
|
||||
return value.endsWith( '%' );
|
||||
|
@ -58,6 +59,7 @@ export function PricingMenuItem( {
|
|||
selection,
|
||||
onChange,
|
||||
onClose,
|
||||
supportsMultipleSelection = false,
|
||||
}: VariationActionsMenuItemProps ) {
|
||||
const ids = Array.isArray( selection )
|
||||
? selection.map( ( { id } ) => id )
|
||||
|
@ -435,6 +437,13 @@ export function PricingMenuItem( {
|
|||
{ __( 'Schedule sale', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'pricing' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Dropdown, MenuItem } from '@wordpress/components';
|
||||
import { Dropdown, MenuItem, MenuGroup } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronRight } from '@wordpress/icons';
|
||||
|
@ -14,11 +14,13 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { VariationActionsMenuItemProps } from '../types';
|
||||
import { handlePrompt } from '../../../utils/handle-prompt';
|
||||
import { VariationQuickUpdateMenuItem } from '../variation-actions-menus/variation-quick-update-menu-item';
|
||||
|
||||
export function ShippingMenuItem( {
|
||||
selection,
|
||||
onChange,
|
||||
onClose,
|
||||
supportsMultipleSelection = false,
|
||||
}: VariationActionsMenuItemProps ) {
|
||||
const ids = Array.isArray( selection )
|
||||
? selection.map( ( { id } ) => id )
|
||||
|
@ -71,174 +73,183 @@ export function ShippingMenuItem( {
|
|||
) }
|
||||
renderContent={ () => (
|
||||
<div className="components-dropdown-menu__menu">
|
||||
{ window.wcAdminFeatures[
|
||||
'product-virtual-downloadable'
|
||||
] && (
|
||||
<MenuGroup>
|
||||
{ window.wcAdminFeatures[
|
||||
'product-virtual-downloadable'
|
||||
] && (
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'toggle_shipping',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
if ( Array.isArray( selection ) ) {
|
||||
onChange(
|
||||
selection.map(
|
||||
( { id, virtual } ) => ( {
|
||||
id,
|
||||
virtual: ! virtual,
|
||||
} )
|
||||
)
|
||||
);
|
||||
} else {
|
||||
onChange( {
|
||||
virtual: ! selection.virtual,
|
||||
} );
|
||||
}
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'toggle_shipping',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Toggle shipping', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
) }
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'toggle_shipping',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
if ( Array.isArray( selection ) ) {
|
||||
onChange(
|
||||
selection.map(
|
||||
( { id, virtual } ) => ( {
|
||||
id,
|
||||
virtual: ! virtual,
|
||||
} )
|
||||
)
|
||||
);
|
||||
} else {
|
||||
onChange( {
|
||||
virtual: ! selection.virtual,
|
||||
} );
|
||||
}
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'toggle_shipping',
|
||||
action: 'dimensions_length_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_length_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
length: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Toggle shipping', 'woocommerce' ) }
|
||||
{ __( 'Set length', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
) }
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_length_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_length_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
length: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set length', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_width_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_width_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
width: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set width', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_height_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_height_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
height: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set height', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'weight_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'weight_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
if ( Array.isArray( selection ) ) {
|
||||
onChange(
|
||||
selection.map( ( { id } ) => ( {
|
||||
id,
|
||||
weight: value,
|
||||
} ) )
|
||||
);
|
||||
} else {
|
||||
onChange( { weight: value } );
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_width_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set weight', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_width_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
width: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set width', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_height_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'dimensions_height_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handleDimensionsChange( {
|
||||
height: value,
|
||||
} );
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set height', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_select',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'weight_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
handlePrompt( {
|
||||
onOk( value ) {
|
||||
recordEvent(
|
||||
'product_variations_menu_shipping_update',
|
||||
{
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'weight_set',
|
||||
variation_id: ids,
|
||||
}
|
||||
);
|
||||
if ( Array.isArray( selection ) ) {
|
||||
onChange(
|
||||
selection.map( ( { id } ) => ( {
|
||||
id,
|
||||
weight: value,
|
||||
} ) )
|
||||
);
|
||||
} else {
|
||||
onChange( { weight: value } );
|
||||
}
|
||||
},
|
||||
} );
|
||||
onClose();
|
||||
} }
|
||||
>
|
||||
{ __( 'Set weight', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'shipping' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "./variations-actions-menu/styles.scss";
|
||||
@import "./variation-actions-menus/styles.scss";
|
||||
@import "./downloads-menu-item/styles.scss";
|
||||
@import "./pagination/styles.scss";
|
||||
@import "./table-empty-or-error-state/styles.scss";
|
||||
|
|
|
@ -9,4 +9,5 @@ export type VariationActionsMenuItemProps = {
|
|||
variation: Partial< ProductVariation > | Partial< ProductVariation >[]
|
||||
): void;
|
||||
onClose(): void;
|
||||
supportsMultipleSelection?: boolean;
|
||||
};
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export * from './variation-actions-menu';
|
||||
export * from './types';
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
|
||||
export type VariationActionsMenuProps = {
|
||||
selection: ProductVariation;
|
||||
onChange( variation: Partial< ProductVariation > ): void;
|
||||
onDelete( variation: ProductVariation ): void;
|
||||
};
|
|
@ -1,115 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { moreVertical } from '@wordpress/icons';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationActionsMenuProps } from './types';
|
||||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { ShippingMenuItem } from '../shipping-menu-item';
|
||||
import { InventoryMenuItem } from '../inventory-menu-item';
|
||||
import { PricingMenuItem } from '../pricing-menu-item';
|
||||
import { ToggleVisibilityMenuItem } from '../toggle-visibility-menu-item';
|
||||
import { DownloadsMenuItem } from '../downloads-menu-item';
|
||||
|
||||
export function VariationActionsMenu( {
|
||||
selection,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: VariationActionsMenuProps ) {
|
||||
return (
|
||||
<DropdownMenu
|
||||
popoverProps={ {
|
||||
// @ts-expect-error missing TS.
|
||||
placement: 'left-start',
|
||||
} }
|
||||
icon={ moreVertical }
|
||||
label={ __( 'Actions', 'woocommerce' ) }
|
||||
toggleProps={ {
|
||||
onClick() {
|
||||
recordEvent( 'product_variations_menu_view', {
|
||||
source: TRACKS_SOURCE,
|
||||
variation_id: selection.id,
|
||||
} );
|
||||
},
|
||||
} }
|
||||
>
|
||||
{ ( { onClose } ) => (
|
||||
<>
|
||||
<MenuGroup
|
||||
label={ sprintf(
|
||||
/** Translators: Variation ID */
|
||||
__( 'Variation Id: %s', 'woocommerce' ),
|
||||
selection.id
|
||||
) }
|
||||
>
|
||||
<MenuItem
|
||||
href={ selection.permalink }
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={ () => {
|
||||
recordEvent( 'product_variations_preview', {
|
||||
source: TRACKS_SOURCE,
|
||||
variation_id: selection.id,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ __( 'Preview', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<ToggleVisibilityMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
<PricingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<InventoryMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<ShippingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
{ window.wcAdminFeatures[
|
||||
'product-virtual-downloadable'
|
||||
] && (
|
||||
<DownloadsMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
) }
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
<MenuItem
|
||||
isDestructive
|
||||
label={ __( 'Delete variation', 'woocommerce' ) }
|
||||
variant="link"
|
||||
onClick={ () => {
|
||||
onDelete( selection );
|
||||
onClose();
|
||||
} }
|
||||
className="woocommerce-product-variations__actions--delete"
|
||||
>
|
||||
{ __( 'Delete', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
</>
|
||||
) }
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export const MULTIPLE_UPDATE = 'multiple-update';
|
||||
export const SINGLE_UPDATE = 'single-update';
|
||||
export const VARIATION_ACTIONS_SLOT_NAME = 'woocommerce-actions-menu-slot';
|
|
@ -0,0 +1,4 @@
|
|||
export * from './multiple-update-menu';
|
||||
export * from './single-update-menu';
|
||||
export * from './variation-quick-update-menu-item';
|
||||
export * from './types';
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, Dropdown } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationActionsMenuProps } from './types';
|
||||
import { VariationActions } from './variation-actions';
|
||||
|
||||
export function MultipleUpdateMenu( {
|
||||
selection,
|
||||
disabled,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: VariationActionsMenuProps ) {
|
||||
return (
|
||||
<Dropdown
|
||||
// @ts-expect-error missing prop in types.
|
||||
popoverProps={ {
|
||||
placement: 'bottom-end',
|
||||
} }
|
||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||
<Button
|
||||
disabled={ disabled }
|
||||
aria-expanded={ isOpen }
|
||||
icon={ isOpen ? chevronUp : chevronDown }
|
||||
variant="secondary"
|
||||
onClick={ onToggle }
|
||||
className="variations-actions-menu__toogle"
|
||||
>
|
||||
<span>{ __( 'Quick update', 'woocommerce' ) }</span>
|
||||
</Button>
|
||||
) }
|
||||
renderContent={ ( { onClose }: { onClose: () => void } ) => (
|
||||
<VariationActions
|
||||
selection={ selection }
|
||||
onClose={ onClose }
|
||||
onChange={ onChange }
|
||||
onDelete={ onDelete }
|
||||
supportsMultipleSelection={ true }
|
||||
/>
|
||||
) }
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { moreVertical } from '@wordpress/icons';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationActionsMenuProps } from './types';
|
||||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { VariationActions } from './variation-actions';
|
||||
|
||||
export function SingleUpdateMenu( {
|
||||
selection,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: VariationActionsMenuProps ) {
|
||||
return (
|
||||
<DropdownMenu
|
||||
popoverProps={ {
|
||||
// @ts-expect-error missing TS.
|
||||
placement: 'left-start',
|
||||
} }
|
||||
icon={ moreVertical }
|
||||
label={ __( 'Actions', 'woocommerce' ) }
|
||||
toggleProps={ {
|
||||
onClick() {
|
||||
recordEvent( 'product_variations_menu_view', {
|
||||
source: TRACKS_SOURCE,
|
||||
variation_id: ( selection as ProductVariation ).id,
|
||||
} );
|
||||
},
|
||||
} }
|
||||
>
|
||||
{ ( { onClose }: { onClose: () => void } ) => (
|
||||
<VariationActions
|
||||
selection={ selection }
|
||||
onClose={ onClose }
|
||||
onChange={ onChange }
|
||||
onDelete={ onDelete }
|
||||
supportsMultipleSelection={ false }
|
||||
/>
|
||||
) }
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
|
@ -9,7 +9,7 @@ import React, { createElement } from 'react';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationActionsMenu } from '../';
|
||||
import { SingleUpdateMenu, MultipleUpdateMenu } from '..';
|
||||
import { TRACKS_SOURCE } from '../../../../constants';
|
||||
import { PRODUCT_STOCK_STATUS_KEYS } from '../../../../utils/get-product-stock-status';
|
||||
|
||||
|
@ -25,7 +25,54 @@ const mockVariation = {
|
|||
parent_id: 1,
|
||||
} as ProductVariation;
|
||||
|
||||
describe( 'VariationActionsMenu', () => {
|
||||
const anotherMockVariation = {
|
||||
id: 11,
|
||||
manage_stock: false,
|
||||
attributes: [],
|
||||
downloads: [],
|
||||
name: '',
|
||||
parent_id: 1,
|
||||
} as ProductVariation;
|
||||
|
||||
describe( 'MultipleUpdateMenu', () => {
|
||||
let onChangeMock: jest.Mock, onDeleteMock: jest.Mock;
|
||||
beforeEach( () => {
|
||||
onChangeMock = jest.fn();
|
||||
onDeleteMock = jest.fn();
|
||||
( recordEvent as jest.Mock ).mockClear();
|
||||
} );
|
||||
|
||||
it( 'should render dropdown with pricing, inventory, and delete options when opened', () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Quick update' } ) );
|
||||
expect( queryByText( 'Update stock' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Set list price' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Toggle visibility' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should call onDelete when Delete menuItem is clicked', async () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
);
|
||||
await fireEvent.click(
|
||||
getByRole( 'button', { name: 'Quick update' } )
|
||||
);
|
||||
await fireEvent.click( getByText( 'Delete' ) );
|
||||
expect( onDeleteMock ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'SingleUpdateMenu', () => {
|
||||
let onChangeMock: jest.Mock, onDeleteMock: jest.Mock;
|
||||
beforeEach( () => {
|
||||
onChangeMock = jest.fn();
|
||||
|
@ -35,7 +82,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should trigger product_variations_menu_view track when dropdown toggled', () => {
|
||||
const { getByRole } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -53,7 +100,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should render dropdown with pricing, inventory, and delete options when opened', () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -67,7 +114,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should call onDelete when Delete menuItem is clicked', async () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -81,7 +128,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
describe( 'Inventory sub-menu', () => {
|
||||
it( 'should open Inventory sub-menu if Inventory is clicked with click track', async () => {
|
||||
const { queryByText, getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -108,7 +155,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
it( 'should onChange with stock_quantity when Update stock is clicked', async () => {
|
||||
window.prompt = jest.fn().mockReturnValue( '10' );
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -143,7 +190,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
it( 'should not call onChange when prompt is cancelled', async () => {
|
||||
window.prompt = jest.fn().mockReturnValue( null );
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -177,7 +224,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should call onChange with toggled manage_stock when toggle "track quantity" is clicked', async () => {
|
||||
const { getByRole, getByText, rerender } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -200,7 +247,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
} );
|
||||
onChangeMock.mockClear();
|
||||
rerender(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation, manage_stock: true } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -216,7 +263,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should call onChange with toggled stock_status when toggle "Set status to In stock" is clicked', async () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -242,7 +289,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should call onChange with toggled stock_status when toggle "Set status to Out of stock" is clicked', async () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -268,7 +315,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
|
||||
it( 'should call onChange with toggled stock_status when toggle "Set status to On back order" is clicked', async () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
|
@ -295,7 +342,7 @@ describe( 'VariationActionsMenu', () => {
|
|||
it( 'should call onChange with low_stock_amount when Edit low stock threshold is clicked', async () => {
|
||||
window.prompt = jest.fn().mockReturnValue( '7' );
|
||||
const { getByRole, getByText } = render(
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ { ...mockVariation } }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
|
@ -0,0 +1,274 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import React, { createElement } from 'react';
|
||||
import { SlotFillProvider } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
SingleUpdateMenu,
|
||||
MultipleUpdateMenu,
|
||||
VariationQuickUpdateMenuItem,
|
||||
} from '..';
|
||||
|
||||
jest.mock( '@woocommerce/tracks', () => ( {
|
||||
recordEvent: jest.fn(),
|
||||
} ) );
|
||||
const mockVariation = {
|
||||
id: 10,
|
||||
manage_stock: false,
|
||||
attributes: [],
|
||||
downloads: [],
|
||||
name: '',
|
||||
parent_id: 1,
|
||||
} as ProductVariation;
|
||||
|
||||
const anotherMockVariation = {
|
||||
id: 11,
|
||||
manage_stock: false,
|
||||
attributes: [],
|
||||
downloads: [],
|
||||
name: '',
|
||||
parent_id: 1,
|
||||
} as ProductVariation;
|
||||
|
||||
describe( 'SingleUpdateMenu', () => {
|
||||
let onClickMock: jest.Mock,
|
||||
onChangeMock: jest.Mock,
|
||||
onDeleteMock: jest.Mock;
|
||||
beforeEach( () => {
|
||||
onClickMock = jest.fn();
|
||||
onChangeMock = jest.fn();
|
||||
onDeleteMock = jest.fn();
|
||||
( recordEvent as jest.Mock ).mockClear();
|
||||
} );
|
||||
|
||||
it( 'should render a top level fill in the single variation actions', () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'top-level' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My top level item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||
fireEvent.click( getByText( 'My top level item' ) );
|
||||
expect( onClickMock ).toHaveBeenCalled();
|
||||
expect( onClickMock ).toHaveBeenCalledWith(
|
||||
expect.objectContaining( {
|
||||
onChange: onChangeMock,
|
||||
onClose: expect.any( Function ),
|
||||
selection: [ mockVariation ],
|
||||
} )
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the secondary area in the single variation actions', () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'secondary' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My secondary item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||
fireEvent.click( getByText( 'My secondary item' ) );
|
||||
expect( onClickMock ).toHaveBeenCalled();
|
||||
expect( onClickMock ).toHaveBeenCalledWith(
|
||||
expect.objectContaining( {
|
||||
onChange: onChangeMock,
|
||||
onClose: expect.any( Function ),
|
||||
selection: [ mockVariation ],
|
||||
} )
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the tertiary area in the single variation actions', () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'tertiary' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My tertiary item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||
fireEvent.click( getByText( 'My tertiary item' ) );
|
||||
expect( onClickMock ).toHaveBeenCalled();
|
||||
expect( onClickMock ).toHaveBeenCalledWith(
|
||||
expect.objectContaining( {
|
||||
onChange: onChangeMock,
|
||||
onClose: expect.any( Function ),
|
||||
selection: [ mockVariation ],
|
||||
} )
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the pricing group in the single variation actions', () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'shipping' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My shipping item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<SingleUpdateMenu
|
||||
selection={ mockVariation }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Actions' } ) );
|
||||
fireEvent.click( getByText( 'Shipping' ) );
|
||||
fireEvent.click( getByText( 'My shipping item' ) );
|
||||
expect( onClickMock ).toHaveBeenCalled();
|
||||
expect( onClickMock ).toHaveBeenCalledWith(
|
||||
expect.objectContaining( {
|
||||
onChange: onChangeMock,
|
||||
onClose: expect.any( Function ),
|
||||
selection: [ mockVariation ],
|
||||
} )
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'MultipleUpdateMenu', () => {
|
||||
let onClickMock: jest.Mock,
|
||||
onChangeMock: jest.Mock,
|
||||
onDeleteMock: jest.Mock;
|
||||
beforeEach( () => {
|
||||
onClickMock = jest.fn();
|
||||
onChangeMock = jest.fn();
|
||||
onDeleteMock = jest.fn();
|
||||
( recordEvent as jest.Mock ).mockClear();
|
||||
} );
|
||||
|
||||
it( 'should render a top level fill in the multiple variation actions', () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'top-level' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My top level item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Quick update' } ) );
|
||||
expect( queryByText( 'My top level item' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the secondary area in the multiple variation actions', () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'secondary' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My secondary item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Quick update' } ) );
|
||||
expect( queryByText( 'My secondary item' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the tertiary area in the multiple variation actions', () => {
|
||||
const { queryByText, getByRole } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'tertiary' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My tertiary item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
fireEvent.click( getByRole( 'button', { name: 'Quick update' } ) );
|
||||
expect( queryByText( 'My tertiary item' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should render a fill in the pricing group in the multiple variation actions', async () => {
|
||||
const { queryByText, getByRole, getByText } = render(
|
||||
<SlotFillProvider>
|
||||
<VariationQuickUpdateMenuItem
|
||||
group={ 'pricing' }
|
||||
order={ 20 }
|
||||
supportsMultipleSelection={ true }
|
||||
onClick={ onClickMock }
|
||||
>
|
||||
My pricing item
|
||||
</VariationQuickUpdateMenuItem>
|
||||
<MultipleUpdateMenu
|
||||
selection={ [ mockVariation, anotherMockVariation ] }
|
||||
onChange={ onChangeMock }
|
||||
onDelete={ onDeleteMock }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
await fireEvent.click(
|
||||
getByRole( 'button', { name: 'Quick update' } )
|
||||
);
|
||||
await fireEvent.click( getByText( 'Pricing' ) );
|
||||
expect( queryByText( 'My pricing item' ) ).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
|
||||
export type VariationActionsMenuProps = {
|
||||
disabled?: boolean;
|
||||
selection: ProductVariation | ProductVariation[];
|
||||
onChange( variation: Partial< ProductVariation > ): void;
|
||||
onDelete(
|
||||
variation: ProductVariation | Partial< ProductVariation >[]
|
||||
): void;
|
||||
};
|
||||
|
||||
export type VariationQuickUpdateSlotProps = {
|
||||
group: string;
|
||||
supportsMultipleSelection: boolean;
|
||||
selection: ProductVariation | ProductVariation[];
|
||||
onChange: (
|
||||
variation: Partial< ProductVariation > | Partial< ProductVariation >[]
|
||||
) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export type MenuItemProps = {
|
||||
children?: React.ReactNode;
|
||||
order?: number;
|
||||
group?: string;
|
||||
supportsMultipleSelection?: boolean;
|
||||
onClick?: ( {
|
||||
onChange,
|
||||
onClose,
|
||||
selection,
|
||||
}: {
|
||||
[ K in
|
||||
| 'onChange'
|
||||
| 'onClose'
|
||||
| 'selection' ]: VariationQuickUpdateSlotProps[ K ];
|
||||
} ) => void;
|
||||
onChange?: (
|
||||
variation: Partial< ProductVariation > | Partial< ProductVariation >[]
|
||||
) => void;
|
||||
onClose?: () => void;
|
||||
};
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { MenuGroup, MenuItem } from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationActionsMenuProps } from './types';
|
||||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { ShippingMenuItem } from '../shipping-menu-item';
|
||||
import { InventoryMenuItem } from '../inventory-menu-item';
|
||||
import { PricingMenuItem } from '../pricing-menu-item';
|
||||
import { ToggleVisibilityMenuItem } from '../toggle-visibility-menu-item';
|
||||
import { DownloadsMenuItem } from '../downloads-menu-item';
|
||||
import { VariationQuickUpdateMenuItem } from './variation-quick-update-menu-item';
|
||||
import { UpdateStockMenuItem } from '../update-stock-menu-item';
|
||||
import { SetListPriceMenuItem } from '../set-list-price-menu-item';
|
||||
|
||||
export function VariationActions( {
|
||||
selection,
|
||||
onChange,
|
||||
onDelete,
|
||||
onClose,
|
||||
supportsMultipleSelection = false,
|
||||
}: VariationActionsMenuProps & {
|
||||
onClose: () => void;
|
||||
supportsMultipleSelection: boolean;
|
||||
} ) {
|
||||
return (
|
||||
<div
|
||||
className={ classNames( {
|
||||
'components-dropdown-menu__menu': supportsMultipleSelection,
|
||||
} ) }
|
||||
>
|
||||
<MenuGroup
|
||||
label={
|
||||
supportsMultipleSelection
|
||||
? undefined
|
||||
: sprintf(
|
||||
/** Translators: Variation ID */
|
||||
__( 'Variation Id: %s', 'woocommerce' ),
|
||||
( selection as ProductVariation ).id
|
||||
)
|
||||
}
|
||||
>
|
||||
{ supportsMultipleSelection ? (
|
||||
<>
|
||||
<UpdateStockMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<SetListPriceMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<MenuItem
|
||||
href={ ( selection as ProductVariation ).permalink }
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={ () => {
|
||||
recordEvent( 'product_variations_preview', {
|
||||
source: TRACKS_SOURCE,
|
||||
variation_id: ( selection as ProductVariation )
|
||||
.id,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ __( 'Preview', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
) }
|
||||
|
||||
<ToggleVisibilityMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'top-level' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
<MenuGroup>
|
||||
<PricingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
<InventoryMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
<ShippingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
{ window.wcAdminFeatures[ 'product-virtual-downloadable' ] && (
|
||||
<DownloadsMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
) }
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'secondary' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
<MenuGroup>
|
||||
<MenuItem
|
||||
isDestructive
|
||||
label={
|
||||
! supportsMultipleSelection
|
||||
? __( 'Delete variation', 'woocommerce' )
|
||||
: undefined
|
||||
}
|
||||
variant="link"
|
||||
onClick={ () => {
|
||||
onDelete( selection );
|
||||
onClose();
|
||||
} }
|
||||
className="woocommerce-product-variations__actions--delete"
|
||||
>
|
||||
{ __( 'Delete', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<VariationQuickUpdateMenuItem.Slot
|
||||
group={ 'tertiary' }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
selection={ selection }
|
||||
supportsMultipleSelection={ supportsMultipleSelection }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Slot, Fill, MenuItem, MenuGroup } from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import {
|
||||
createOrderedChildren,
|
||||
sortFillsByOrder,
|
||||
} from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { MenuItemProps, VariationQuickUpdateSlotProps } from './types';
|
||||
import {
|
||||
MULTIPLE_UPDATE,
|
||||
SINGLE_UPDATE,
|
||||
VARIATION_ACTIONS_SLOT_NAME,
|
||||
} from './constants';
|
||||
|
||||
const DEFAULT_ORDER = 20;
|
||||
const TOP_LEVEL_MENU = 'top-level';
|
||||
|
||||
export const getGroupName = (
|
||||
group?: string,
|
||||
isMultipleSelection?: boolean
|
||||
) => {
|
||||
const nameSuffix = isMultipleSelection
|
||||
? `_${ MULTIPLE_UPDATE }`
|
||||
: `_${ SINGLE_UPDATE }`;
|
||||
return group
|
||||
? `${ VARIATION_ACTIONS_SLOT_NAME }_${ group }${ nameSuffix }`
|
||||
: VARIATION_ACTIONS_SLOT_NAME;
|
||||
};
|
||||
|
||||
export const VariationQuickUpdateMenuItem: React.FC< MenuItemProps > & {
|
||||
Slot: React.FC< Slot.Props & VariationQuickUpdateSlotProps >;
|
||||
} = ( {
|
||||
children,
|
||||
order = DEFAULT_ORDER,
|
||||
group = TOP_LEVEL_MENU,
|
||||
supportsMultipleSelection,
|
||||
onClick = () => {},
|
||||
} ) => {
|
||||
const handleClick =
|
||||
( fillProps: Fill.Props & VariationQuickUpdateSlotProps ) => () => {
|
||||
const { selection, onChange, onClose } = fillProps;
|
||||
onClick( {
|
||||
selection: Array.isArray( selection )
|
||||
? selection
|
||||
: [ selection ],
|
||||
onChange,
|
||||
onClose,
|
||||
} );
|
||||
};
|
||||
|
||||
const createFill = ( updateType: string ) => (
|
||||
<Fill
|
||||
key={ updateType }
|
||||
name={ getGroupName( group, updateType === MULTIPLE_UPDATE ) }
|
||||
>
|
||||
{ ( fillProps: Fill.Props & VariationQuickUpdateSlotProps ) =>
|
||||
createOrderedChildren(
|
||||
<MenuItem onClick={ handleClick( fillProps ) }>
|
||||
{ children }
|
||||
</MenuItem>,
|
||||
order,
|
||||
fillProps
|
||||
)
|
||||
}
|
||||
</Fill>
|
||||
);
|
||||
|
||||
const fills = supportsMultipleSelection
|
||||
? [ MULTIPLE_UPDATE, SINGLE_UPDATE ].map( createFill )
|
||||
: createFill( SINGLE_UPDATE );
|
||||
return <>{ fills }</>;
|
||||
};
|
||||
|
||||
VariationQuickUpdateMenuItem.Slot = ( {
|
||||
fillProps,
|
||||
group = TOP_LEVEL_MENU,
|
||||
onChange,
|
||||
onClose,
|
||||
selection,
|
||||
supportsMultipleSelection,
|
||||
} ) => {
|
||||
return (
|
||||
<Slot
|
||||
name={ getGroupName( group, supportsMultipleSelection ) }
|
||||
fillProps={ { ...fillProps, onChange, onClose, selection } }
|
||||
>
|
||||
{ ( fills ) => {
|
||||
if ( ! sortFillsByOrder || ! fills?.length ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <MenuGroup>{ sortFillsByOrder( fills ) }</MenuGroup>;
|
||||
} }
|
||||
</Slot>
|
||||
);
|
||||
};
|
|
@ -1,2 +0,0 @@
|
|||
export * from './variations-actions-menu';
|
||||
export * from './types';
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ProductVariation } from '@woocommerce/data';
|
||||
|
||||
export type VariationsActionsMenuProps = {
|
||||
disabled?: boolean;
|
||||
selection: ProductVariation[];
|
||||
onChange( variations: Partial< ProductVariation >[] ): void;
|
||||
onDelete( variations: Partial< ProductVariation >[] ): void;
|
||||
};
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariationsActionsMenuProps } from './types';
|
||||
import { UpdateStockMenuItem } from '../update-stock-menu-item';
|
||||
import { PricingMenuItem } from '../pricing-menu-item';
|
||||
import { SetListPriceMenuItem } from '../set-list-price-menu-item';
|
||||
import { InventoryMenuItem } from '../inventory-menu-item';
|
||||
import { ShippingMenuItem } from '../shipping-menu-item';
|
||||
import { ToggleVisibilityMenuItem } from '../toggle-visibility-menu-item';
|
||||
import { DownloadsMenuItem } from '../downloads-menu-item';
|
||||
|
||||
export function VariationsActionsMenu( {
|
||||
selection,
|
||||
disabled,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: VariationsActionsMenuProps ) {
|
||||
return (
|
||||
<Dropdown
|
||||
// @ts-expect-error missing prop in types.
|
||||
popoverProps={ {
|
||||
placement: 'bottom-end',
|
||||
} }
|
||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||
<Button
|
||||
disabled={ disabled }
|
||||
aria-expanded={ isOpen }
|
||||
icon={ isOpen ? chevronUp : chevronDown }
|
||||
variant="secondary"
|
||||
onClick={ onToggle }
|
||||
className="variations-actions-menu__toogle"
|
||||
>
|
||||
<span>{ __( 'Quick update', 'woocommerce' ) }</span>
|
||||
</Button>
|
||||
) }
|
||||
renderContent={ ( { onClose } ) => (
|
||||
<div className="components-dropdown-menu__menu">
|
||||
<MenuGroup>
|
||||
<UpdateStockMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<SetListPriceMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<ToggleVisibilityMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
<PricingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<InventoryMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
<ShippingMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
{ window.wcAdminFeatures[
|
||||
'product-virtual-downloadable'
|
||||
] && (
|
||||
<DownloadsMenuItem
|
||||
selection={ selection }
|
||||
onChange={ onChange }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
) }
|
||||
</MenuGroup>
|
||||
<MenuGroup>
|
||||
<MenuItem
|
||||
isDestructive
|
||||
variant="link"
|
||||
onClick={ () => {
|
||||
onDelete( selection );
|
||||
onClose();
|
||||
} }
|
||||
className="woocommerce-product-variations__actions--delete"
|
||||
>
|
||||
{ __( 'Delete', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -27,7 +27,7 @@ import {
|
|||
getProductStockStatusClass,
|
||||
truncate,
|
||||
} from '../../../utils';
|
||||
import { VariationActionsMenu } from '../variation-actions-menu';
|
||||
import { SingleUpdateMenu } from '../variation-actions-menus';
|
||||
import { VariationsTableRowProps } from './types';
|
||||
|
||||
const NOT_VISIBLE_TEXT = __( 'Not visible to customers', 'woocommerce' );
|
||||
|
@ -240,7 +240,7 @@ export function VariationsTableRow( {
|
|||
{ __( 'Edit', 'woocommerce' ) }
|
||||
</Button>
|
||||
|
||||
<VariationActionsMenu
|
||||
<SingleUpdateMenu
|
||||
selection={ variation }
|
||||
onChange={ handleChange }
|
||||
onDelete={ onDelete }
|
||||
|
|
|
@ -22,13 +22,13 @@ import { useEntityId, useEntityProp } from '@wordpress/core-data';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { TRACKS_SOURCE } from '../../constants';
|
||||
import { VariationsActionsMenu } from './variations-actions-menu';
|
||||
import { Pagination } from './pagination';
|
||||
import { EmptyOrErrorTableState } from './table-empty-or-error-state';
|
||||
import { VariationsFilter } from './variations-filter';
|
||||
import { useVariations } from './use-variations';
|
||||
import { TableRowSkeleton } from './table-row-skeleton';
|
||||
import { VariationsTableRow } from './variations-table-row';
|
||||
import { MultipleUpdateMenu } from './variation-actions-menus';
|
||||
|
||||
type VariationsTableProps = {
|
||||
noticeText?: string;
|
||||
|
@ -375,7 +375,7 @@ export const VariationsTable = forwardRef<
|
|||
) }
|
||||
</div>
|
||||
<div className="woocommerce-product-variations__actions">
|
||||
<VariationsActionsMenu
|
||||
<MultipleUpdateMenu
|
||||
selection={ selected }
|
||||
disabled={
|
||||
! areSomeSelected && ! isSelectingAll
|
||||
|
|
Loading…
Reference in New Issue