Merge branch 'trunk' into feature/34906-marketing-channels-card
This commit is contained in:
commit
b717ce9645
|
@ -9,7 +9,6 @@ import {
|
|||
__experimentalSelectControlMenuSlot as SelectControlMenuSlot,
|
||||
Link,
|
||||
} from '@woocommerce/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
|
||||
|
@ -17,48 +16,69 @@ import { getAdminLink } from '@woocommerce/settings';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-field.scss';
|
||||
import { AddAttributeModal } from './add-attribute-modal';
|
||||
import { EditAttributeModal } from './edit-attribute-modal';
|
||||
import { EnhancedProductAttribute } from '~/products/hooks/use-product-attributes';
|
||||
import {
|
||||
getAttributeId,
|
||||
getAttributeKey,
|
||||
reorderSortableProductAttributePositions,
|
||||
} from './utils';
|
||||
import { AttributeEmptyState } from '../attribute-empty-state';
|
||||
import {
|
||||
AddAttributeListItem,
|
||||
AttributeListItem,
|
||||
NewAttributeListItem,
|
||||
} from '../attribute-list-item';
|
||||
import { NewAttributeModal } from './new-attribute-modal';
|
||||
|
||||
type AttributeControlProps = {
|
||||
value: ProductAttribute[];
|
||||
onAdd?: ( attribute: EnhancedProductAttribute[] ) => void;
|
||||
onChange: ( value: ProductAttribute[] ) => void;
|
||||
// TODO: should we support an 'any' option to show all attributes?
|
||||
attributeType?: 'regular' | 'for-variations';
|
||||
onEdit?: ( attribute: ProductAttribute ) => void;
|
||||
onRemove?: ( attribute: ProductAttribute ) => void;
|
||||
onRemoveCancel?: ( attribute: ProductAttribute ) => void;
|
||||
onNewModalCancel?: () => void;
|
||||
onNewModalClose?: () => void;
|
||||
onNewModalOpen?: () => void;
|
||||
onEditModalCancel?: ( attribute?: ProductAttribute ) => void;
|
||||
onEditModalClose?: ( attribute?: ProductAttribute ) => void;
|
||||
onEditModalOpen?: ( attribute?: ProductAttribute ) => void;
|
||||
uiStrings?: {
|
||||
emptyStateSubtitle?: string;
|
||||
newAttributeListItemLabel?: string;
|
||||
newAttributeModalTitle?: string;
|
||||
globalAttributeHelperMessage: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
||||
value,
|
||||
attributeType = 'regular',
|
||||
onAdd = () => {},
|
||||
onChange,
|
||||
onEdit = () => {},
|
||||
onNewModalCancel = () => {},
|
||||
onNewModalClose = () => {},
|
||||
onNewModalOpen = () => {},
|
||||
onEditModalCancel = () => {},
|
||||
onEditModalClose = () => {},
|
||||
onEditModalOpen = () => {},
|
||||
onRemove = () => {},
|
||||
onRemoveCancel = () => {},
|
||||
uiStrings = {
|
||||
newAttributeModalTitle: undefined,
|
||||
emptyStateSubtitle: undefined,
|
||||
newAttributeListItemLabel: undefined,
|
||||
globalAttributeHelperMessage: __(
|
||||
`You can change the attribute's name in {{link}}Attributes{{/link}}.`,
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
} ) => {
|
||||
const [ showAddAttributeModal, setShowAddAttributeModal ] =
|
||||
useState( false );
|
||||
const [ editingAttributeId, setEditingAttributeId ] = useState<
|
||||
const [ isNewModalVisible, setIsNewModalVisible ] = useState( false );
|
||||
const [ currentAttributeId, setCurrentAttributeId ] = useState<
|
||||
null | string
|
||||
>( null );
|
||||
|
||||
const isOnlyForVariations = attributeType === 'for-variations';
|
||||
|
||||
const newAttributeProps = { variation: isOnlyForVariations };
|
||||
|
||||
const CANCEL_BUTTON_EVENT_NAME = isOnlyForVariations
|
||||
? 'product_add_options_modal_cancel_button_click'
|
||||
: 'product_add_attributes_modal_cancel_button_click';
|
||||
|
||||
const fetchAttributeId = ( attribute: { id: number; name: string } ) =>
|
||||
`${ attribute.id }-${ attribute.name }`;
|
||||
|
||||
const handleChange = ( newAttributes: EnhancedProductAttribute[] ) => {
|
||||
onChange(
|
||||
newAttributes.map( ( attr ) => {
|
||||
|
@ -74,79 +94,89 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
);
|
||||
};
|
||||
|
||||
const onRemove = ( attribute: ProductAttribute ) => {
|
||||
const handleRemove = ( attribute: ProductAttribute ) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if ( window.confirm( __( 'Remove this attribute?', 'woocommerce' ) ) ) {
|
||||
recordEvent(
|
||||
'product_remove_attribute_confirmation_confirm_click'
|
||||
);
|
||||
handleChange(
|
||||
value.filter(
|
||||
( attr ) =>
|
||||
fetchAttributeId( attr ) !==
|
||||
fetchAttributeId( attribute )
|
||||
getAttributeId( attr ) !== getAttributeId( attribute )
|
||||
)
|
||||
);
|
||||
} else {
|
||||
recordEvent( 'product_remove_attribute_confirmation_cancel_click' );
|
||||
onRemove( attribute );
|
||||
return;
|
||||
}
|
||||
onRemoveCancel( attribute );
|
||||
};
|
||||
|
||||
const onAddNewAttributes = (
|
||||
newAttributes: EnhancedProductAttribute[]
|
||||
) => {
|
||||
const openNewModal = () => {
|
||||
setIsNewModalVisible( true );
|
||||
onNewModalOpen();
|
||||
};
|
||||
|
||||
const closeNewModal = () => {
|
||||
setIsNewModalVisible( false );
|
||||
onNewModalClose();
|
||||
};
|
||||
|
||||
const openEditModal = ( attribute: ProductAttribute ) => {
|
||||
setCurrentAttributeId( getAttributeId( attribute ) );
|
||||
onEditModalOpen( attribute );
|
||||
};
|
||||
|
||||
const closeEditModal = ( attribute: ProductAttribute ) => {
|
||||
setCurrentAttributeId( null );
|
||||
onEditModalClose( attribute );
|
||||
};
|
||||
|
||||
const handleAdd = ( newAttributes: EnhancedProductAttribute[] ) => {
|
||||
handleChange( [
|
||||
...( value || [] ),
|
||||
...newAttributes
|
||||
.filter(
|
||||
( newAttr ) =>
|
||||
! ( value || [] ).find( ( attr ) =>
|
||||
newAttr.id === 0
|
||||
? newAttr.name === attr.name // check name if custom attribute = id === 0.
|
||||
: attr.id === newAttr.id
|
||||
)
|
||||
)
|
||||
.map( ( newAttr, index ) => {
|
||||
return {
|
||||
...newAttributeProps,
|
||||
...newAttr,
|
||||
position: ( value || [] ).length + index,
|
||||
};
|
||||
} ),
|
||||
...value,
|
||||
...newAttributes.filter(
|
||||
( newAttr ) =>
|
||||
! value.find(
|
||||
( attr ) =>
|
||||
getAttributeId( newAttr ) === getAttributeId( attr )
|
||||
)
|
||||
),
|
||||
] );
|
||||
recordEvent( 'product_add_attributes_modal_add_button_click' );
|
||||
setShowAddAttributeModal( false );
|
||||
onAdd( newAttributes );
|
||||
closeNewModal();
|
||||
};
|
||||
|
||||
const handleEdit = ( updatedAttribute: ProductAttribute ) => {
|
||||
const updatedAttributes = value.map( ( attr ) => {
|
||||
if (
|
||||
getAttributeId( attr ) === getAttributeId( updatedAttribute )
|
||||
) {
|
||||
return updatedAttribute;
|
||||
}
|
||||
|
||||
return attr;
|
||||
} );
|
||||
|
||||
onEdit( updatedAttribute );
|
||||
handleChange( updatedAttributes );
|
||||
closeEditModal( updatedAttribute );
|
||||
};
|
||||
|
||||
if ( ! value.length ) {
|
||||
return (
|
||||
<>
|
||||
<AttributeEmptyState
|
||||
addNewLabel={
|
||||
isOnlyForVariations
|
||||
? __( 'Add options', 'woocommerce' )
|
||||
: undefined
|
||||
}
|
||||
onNewClick={ () => {
|
||||
recordEvent(
|
||||
'product_add_first_attribute_button_click'
|
||||
);
|
||||
setShowAddAttributeModal( true );
|
||||
} }
|
||||
subtitle={
|
||||
isOnlyForVariations
|
||||
? __( 'No options yet', 'woocommerce' )
|
||||
: undefined
|
||||
}
|
||||
addNewLabel={ uiStrings.newAttributeModalTitle }
|
||||
onNewClick={ () => openNewModal() }
|
||||
subtitle={ uiStrings.emptyStateSubtitle }
|
||||
/>
|
||||
{ showAddAttributeModal && (
|
||||
<AddAttributeModal
|
||||
{ isNewModalVisible && (
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {
|
||||
recordEvent( CANCEL_BUTTON_EVENT_NAME );
|
||||
setShowAddAttributeModal( false );
|
||||
closeNewModal();
|
||||
onNewModalCancel();
|
||||
} }
|
||||
onAdd={ onAddNewAttributes }
|
||||
onAdd={ handleAdd }
|
||||
selectedAttributeIds={ [] }
|
||||
title={ uiStrings.newAttributeModalTitle }
|
||||
/>
|
||||
) }
|
||||
<SelectControlMenuSlot />
|
||||
|
@ -167,20 +197,10 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
{} as Record< number | string, ProductAttribute >
|
||||
);
|
||||
|
||||
const editingAttribute = value.find(
|
||||
( attr ) => fetchAttributeId( attr ) === editingAttributeId
|
||||
const currentAttribute = value.find(
|
||||
( attr ) => getAttributeId( attr ) === currentAttributeId
|
||||
) as EnhancedProductAttribute;
|
||||
|
||||
const editAttributeCopy = isOnlyForVariations
|
||||
? __(
|
||||
`You can change the option's name in {{link}}Attributes{{/link}}.`,
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
`You can change the attribute's name in {{link}}Attributes{{/link}}.`,
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="woocommerce-attribute-field">
|
||||
<Sortable
|
||||
|
@ -204,54 +224,37 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
{ sortedAttributes.map( ( attr ) => (
|
||||
<AttributeListItem
|
||||
attribute={ attr }
|
||||
key={ fetchAttributeId( attr ) }
|
||||
onEditClick={ () =>
|
||||
setEditingAttributeId( fetchAttributeId( attr ) )
|
||||
}
|
||||
onRemoveClick={ () => onRemove( attr ) }
|
||||
key={ getAttributeId( attr ) }
|
||||
onEditClick={ () => openEditModal( attr ) }
|
||||
onRemoveClick={ () => handleRemove( attr ) }
|
||||
/>
|
||||
) ) }
|
||||
</Sortable>
|
||||
<AddAttributeListItem
|
||||
label={
|
||||
isOnlyForVariations
|
||||
? __( 'Add option', 'woocommerce' )
|
||||
: undefined
|
||||
}
|
||||
onAddClick={ () => {
|
||||
recordEvent(
|
||||
isOnlyForVariations
|
||||
? 'product_add_option_button'
|
||||
: 'product_add_attribute_button'
|
||||
);
|
||||
setShowAddAttributeModal( true );
|
||||
} }
|
||||
<NewAttributeListItem
|
||||
label={ uiStrings.newAttributeListItemLabel }
|
||||
onClick={ () => openNewModal() }
|
||||
/>
|
||||
{ showAddAttributeModal && (
|
||||
<AddAttributeModal
|
||||
title={
|
||||
isOnlyForVariations
|
||||
? __( 'Add options', 'woocommerce' )
|
||||
: undefined
|
||||
}
|
||||
{ isNewModalVisible && (
|
||||
<NewAttributeModal
|
||||
title={ uiStrings.newAttributeModalTitle }
|
||||
onCancel={ () => {
|
||||
recordEvent( CANCEL_BUTTON_EVENT_NAME );
|
||||
setShowAddAttributeModal( false );
|
||||
closeNewModal();
|
||||
onNewModalCancel();
|
||||
} }
|
||||
onAdd={ onAddNewAttributes }
|
||||
onAdd={ handleAdd }
|
||||
selectedAttributeIds={ value.map( ( attr ) => attr.id ) }
|
||||
/>
|
||||
) }
|
||||
<SelectControlMenuSlot />
|
||||
{ editingAttribute && (
|
||||
{ currentAttribute && (
|
||||
<EditAttributeModal
|
||||
title={ sprintf(
|
||||
/* translators: %s is the attribute name */
|
||||
__( 'Edit %s', 'woocommerce' ),
|
||||
editingAttribute.name
|
||||
currentAttribute.name
|
||||
) }
|
||||
globalAttributeHelperMessage={ interpolateComponents( {
|
||||
mixedString: editAttributeCopy,
|
||||
mixedString: uiStrings.globalAttributeHelperMessage,
|
||||
components: {
|
||||
link: (
|
||||
<Link
|
||||
|
@ -266,26 +269,14 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
),
|
||||
},
|
||||
} ) }
|
||||
onCancel={ () => setEditingAttributeId( null ) }
|
||||
onEdit={ ( changedAttribute ) => {
|
||||
const newAttributesSet = [ ...value ];
|
||||
const changedAttributeIndex: number =
|
||||
newAttributesSet.findIndex( ( attr ) =>
|
||||
attr.id !== 0
|
||||
? attr.id === changedAttribute.id
|
||||
: attr.name === changedAttribute.name
|
||||
);
|
||||
|
||||
newAttributesSet.splice(
|
||||
changedAttributeIndex,
|
||||
1,
|
||||
changedAttribute
|
||||
);
|
||||
|
||||
handleChange( newAttributesSet );
|
||||
setEditingAttributeId( null );
|
||||
onCancel={ () => {
|
||||
closeEditModal( currentAttribute );
|
||||
onEditModalCancel( currentAttribute );
|
||||
} }
|
||||
attribute={ editingAttribute }
|
||||
onEdit={ ( updatedAttribute ) => {
|
||||
handleEdit( updatedAttribute );
|
||||
} }
|
||||
attribute={ currentAttribute }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
.woocommerce-edit-attribute-modal {
|
||||
overflow: visible;
|
||||
|
||||
&__buttons {
|
||||
margin-top: $gap-larger;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $gap-smaller;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-edit-attribute-modal__body {
|
||||
|
|
|
@ -136,7 +136,7 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( {
|
|||
<Tooltip text={ visibleTooltip } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="woocommerce-add-attribute-modal__buttons">
|
||||
<div className="woocommerce-edit-attribute-modal__buttons">
|
||||
<Button
|
||||
isSecondary
|
||||
label={ cancelAccessibleLabel }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.woocommerce-add-attribute-modal {
|
||||
.woocommerce-new-attribute-modal {
|
||||
.components-notice.is-info {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
|
@ -20,7 +20,7 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './add-attribute-modal.scss';
|
||||
import './new-attribute-modal.scss';
|
||||
import { AttributeInputField } from '../attribute-input-field';
|
||||
import {
|
||||
AttributeTermInputField,
|
||||
|
@ -29,7 +29,7 @@ import {
|
|||
import { EnhancedProductAttribute } from '~/products/hooks/use-product-attributes';
|
||||
import { getProductAttributeObject } from './utils';
|
||||
|
||||
type AddAttributeModalProps = {
|
||||
type NewAttributeModalProps = {
|
||||
title?: string;
|
||||
notice?: string;
|
||||
attributeLabel?: string;
|
||||
|
@ -54,7 +54,7 @@ type AttributeForm = {
|
|||
attributes: Array< EnhancedProductAttribute | null >;
|
||||
};
|
||||
|
||||
export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
||||
export const NewAttributeModal: React.FC< NewAttributeModalProps > = ( {
|
||||
title = __( 'Add attributes', 'woocommerce' ),
|
||||
notice = __(
|
||||
'By default, attributes are filterable and visible on the product page. You can change these settings for each attribute separately later.',
|
||||
|
@ -83,12 +83,11 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
const scrollAttributeIntoView = ( index: number ) => {
|
||||
setTimeout( () => {
|
||||
const attributeRow = document.querySelector(
|
||||
`.woocommerce-add-attribute-modal__table-row-${ index }`
|
||||
`.woocommerce-new-attribute-modal__table-row-${ index }`
|
||||
);
|
||||
attributeRow?.scrollIntoView( { behavior: 'smooth' } );
|
||||
}, 0 );
|
||||
};
|
||||
|
||||
const [ showConfirmClose, setShowConfirmClose ] = useState( false );
|
||||
const addAnother = (
|
||||
values: AttributeForm,
|
||||
|
@ -148,9 +147,9 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
setTimeout( () => {
|
||||
const valueInputField: HTMLInputElement | null =
|
||||
document.querySelector(
|
||||
'.woocommerce-add-attribute-modal__table-row-' +
|
||||
'.woocommerce-new-attribute-modal__table-row-' +
|
||||
index +
|
||||
' .woocommerce-add-attribute-modal__table-attribute-value-column .woocommerce-experimental-select-control__input'
|
||||
' .woocommerce-new-attribute-modal__table-attribute-value-column .woocommerce-experimental-select-control__input'
|
||||
);
|
||||
if ( valueInputField ) {
|
||||
valueInputField.focus();
|
||||
|
@ -198,16 +197,16 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
onClose( values );
|
||||
}
|
||||
} }
|
||||
className="woocommerce-add-attribute-modal"
|
||||
className="woocommerce-new-attribute-modal"
|
||||
>
|
||||
<Notice isDismissible={ false }>
|
||||
<p>{ notice }</p>
|
||||
</Notice>
|
||||
|
||||
<div className="woocommerce-add-attribute-modal__body">
|
||||
<table className="woocommerce-add-attribute-modal__table">
|
||||
<div className="woocommerce-new-attribute-modal__body">
|
||||
<table className="woocommerce-new-attribute-modal__table">
|
||||
<thead>
|
||||
<tr className="woocommerce-add-attribute-modal__table-header">
|
||||
<tr className="woocommerce-new-attribute-modal__table-header">
|
||||
<th>{ attributeLabel }</th>
|
||||
<th>{ valueLabel }</th>
|
||||
</tr>
|
||||
|
@ -217,9 +216,9 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
( attribute, index ) => (
|
||||
<tr
|
||||
key={ index }
|
||||
className={ `woocommerce-add-attribute-modal__table-row woocommerce-add-attribute-modal__table-row-${ index }` }
|
||||
className={ `woocommerce-new-attribute-modal__table-row woocommerce-new-attribute-modal__table-row-${ index }` }
|
||||
>
|
||||
<td className="woocommerce-add-attribute-modal__table-attribute-column">
|
||||
<td className="woocommerce-new-attribute-modal__table-attribute-column">
|
||||
<AttributeInputField
|
||||
placeholder={
|
||||
attributePlaceholder
|
||||
|
@ -265,7 +264,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
] }
|
||||
/>
|
||||
</td>
|
||||
<td className="woocommerce-add-attribute-modal__table-attribute-value-column">
|
||||
<td className="woocommerce-new-attribute-modal__table-attribute-value-column">
|
||||
{ attribute === null ||
|
||||
attribute.id !== 0 ? (
|
||||
<AttributeTermInputField
|
||||
|
@ -329,7 +328,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
/>
|
||||
) }
|
||||
</td>
|
||||
<td className="woocommerce-add-attribute-modal__table-attribute-trash-column">
|
||||
<td className="woocommerce-new-attribute-modal__table-attribute-trash-column">
|
||||
<Button
|
||||
icon={ trash }
|
||||
disabled={
|
||||
|
@ -361,7 +360,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className="woocommerce-add-attribute-modal__add-attribute"
|
||||
className="woocommerce-new-attribute-modal__add-attribute"
|
||||
variant="tertiary"
|
||||
label={ addAnotherAccessibleLabel }
|
||||
onClick={ () => {
|
||||
|
@ -374,7 +373,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
|
|||
{ addAnotherLabel }
|
||||
</Button>
|
||||
</div>
|
||||
<div className="woocommerce-add-attribute-modal__buttons">
|
||||
<div className="woocommerce-new-attribute-modal__buttons">
|
||||
<Button
|
||||
isSecondary
|
||||
label={ cancelLabel }
|
|
@ -144,7 +144,6 @@ describe( 'AttributeControl', () => {
|
|||
<AttributeControl
|
||||
value={ [ ...attributeList ] }
|
||||
onChange={ () => {} }
|
||||
attributeType="for-variations"
|
||||
/>
|
||||
);
|
||||
} );
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ProductAttribute, ProductAttributeTerm } from '@woocommerce/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { AddAttributeModal } from '../add-attribute-modal';
|
||||
import { NewAttributeModal } from '../new-attribute-modal';
|
||||
|
||||
let attributeOnChange: ( val: ProductAttribute ) => void;
|
||||
jest.mock( '../../attribute-input-field', () => ( {
|
||||
|
@ -118,14 +118,14 @@ const attributeTermList: ProductAttributeTerm[] = [
|
|||
},
|
||||
];
|
||||
|
||||
describe( 'AddAttributeModal', () => {
|
||||
describe( 'NewAttributeModal', () => {
|
||||
beforeEach( () => {
|
||||
jest.clearAllMocks();
|
||||
} );
|
||||
|
||||
it( 'should render at-least one row with the attribute dropdown fields', () => {
|
||||
const { queryAllByText } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ () => {} }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -139,7 +139,7 @@ describe( 'AddAttributeModal', () => {
|
|||
|
||||
it( 'should enable attribute term field once attribute is selected', () => {
|
||||
const { queryAllByText } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ () => {} }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -155,7 +155,7 @@ describe( 'AddAttributeModal', () => {
|
|||
|
||||
it( 'should allow us to add multiple new rows with the attribute fields', () => {
|
||||
const { queryAllByText, queryByRole } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ () => {} }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -175,7 +175,7 @@ describe( 'AddAttributeModal', () => {
|
|||
|
||||
it( 'should allow us to remove the added fields', () => {
|
||||
const { queryAllByText, queryByRole, queryAllByLabelText } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ () => {} }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -201,7 +201,7 @@ describe( 'AddAttributeModal', () => {
|
|||
|
||||
it( 'should not allow us to remove all the rows', () => {
|
||||
const { queryAllByText, queryAllByLabelText } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ () => {} }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -221,7 +221,7 @@ describe( 'AddAttributeModal', () => {
|
|||
it( 'should not return empty attribute rows', () => {
|
||||
const onAddMock = jest.fn();
|
||||
const { queryAllByText, queryByLabelText, queryByRole } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ onAddMock }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -247,7 +247,7 @@ describe( 'AddAttributeModal', () => {
|
|||
it( 'should not add attribute if no terms were selected', () => {
|
||||
const onAddMock = jest.fn();
|
||||
const { queryByRole } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ onAddMock }
|
||||
selectedAttributeIds={ [] }
|
||||
|
@ -265,7 +265,7 @@ describe( 'AddAttributeModal', () => {
|
|||
it( 'should add attribute with array of terms', () => {
|
||||
const onAddMock = jest.fn();
|
||||
const { queryByRole } = render(
|
||||
<AddAttributeModal
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {} }
|
||||
onAdd={ onAddMock }
|
||||
selectedAttributeIds={ [] }
|
|
@ -15,6 +15,15 @@ export function getAttributeKey(
|
|||
return attribute.id !== 0 ? attribute.id : attribute.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute ID that works universally across global and local attributes.
|
||||
*
|
||||
* @param attribute Product attribute.
|
||||
* @return string
|
||||
*/
|
||||
export const getAttributeId = ( attribute: ProductAttribute ) =>
|
||||
`${ attribute.id }-${ attribute.name }`;
|
||||
|
||||
/**
|
||||
* Updates the position of a product attribute from the new items list.
|
||||
*
|
||||
|
|
|
@ -2,25 +2,24 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { DragEventHandler } from 'react';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { ListItem } from '@woocommerce/components';
|
||||
|
||||
type AddAttributeListItemProps = {
|
||||
type NewAttributeListItemProps = {
|
||||
label?: string;
|
||||
onAddClick?: () => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const AddAttributeListItem: React.FC< AddAttributeListItemProps > = ( {
|
||||
export const NewAttributeListItem: React.FC< NewAttributeListItemProps > = ( {
|
||||
label = __( 'Add attribute', 'woocommerce' ),
|
||||
onAddClick,
|
||||
onClick,
|
||||
} ) => {
|
||||
return (
|
||||
<ListItem className="woocommerce-add-attribute-list-item">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="woocommerce-add-attribute-list-item__add-button"
|
||||
onClick={ onAddClick }
|
||||
onClick={ onClick }
|
||||
>
|
||||
{ label }
|
||||
</Button>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { ProductAttribute } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -28,9 +29,33 @@ export const Attributes: React.FC< AttributesProps > = ( {
|
|||
|
||||
return (
|
||||
<AttributeControl
|
||||
attributeType="regular"
|
||||
value={ attributes }
|
||||
onAdd={ () => {
|
||||
recordEvent( 'product_add_attributes_modal_add_button_click' );
|
||||
} }
|
||||
onChange={ handleChange }
|
||||
onNewModalCancel={ () => {
|
||||
recordEvent(
|
||||
'product_add_attributes_modal_cancel_button_click'
|
||||
);
|
||||
} }
|
||||
onNewModalOpen={ () => {
|
||||
if ( ! attributes.length ) {
|
||||
recordEvent( 'product_add_first_attribute_button_click' );
|
||||
return;
|
||||
}
|
||||
recordEvent( 'product_add_attribute_button' );
|
||||
} }
|
||||
onRemove={ () =>
|
||||
recordEvent(
|
||||
'product_remove_attribute_confirmation_confirm_click'
|
||||
)
|
||||
}
|
||||
onRemoveCancel={ () =>
|
||||
recordEvent(
|
||||
'product_remove_attribute_confirmation_cancel_click'
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Product, ProductAttribute } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useFormContext } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
|
@ -40,9 +42,38 @@ export const Options: React.FC< OptionsProps > = ( {
|
|||
|
||||
return (
|
||||
<AttributeControl
|
||||
attributeType="for-variations"
|
||||
value={ attributes }
|
||||
onAdd={ () => {
|
||||
recordEvent( 'product_add_options_modal_add_button_click' );
|
||||
} }
|
||||
onChange={ handleChange }
|
||||
onNewModalCancel={ () => {
|
||||
recordEvent( 'product_add_options_modal_cancel_button_click' );
|
||||
} }
|
||||
onNewModalOpen={ () => {
|
||||
if ( ! attributes.length ) {
|
||||
recordEvent( 'product_add_first_option_button_click' );
|
||||
return;
|
||||
}
|
||||
recordEvent( 'product_add_option_button' );
|
||||
} }
|
||||
uiStrings={ {
|
||||
emptyStateSubtitle: __( 'No options yet', 'woocommerce' ),
|
||||
newAttributeListItemLabel: __( 'Add option', 'woocommerce' ),
|
||||
newAttributeModalTitle: __( 'Add options', 'woocommerce' ),
|
||||
globalAttributeHelperMessage: __(
|
||||
`You can change the option's name in {{link}}Attributes{{/link}}.`,
|
||||
'woocommerce'
|
||||
),
|
||||
} }
|
||||
onRemove={ () =>
|
||||
recordEvent(
|
||||
'product_remove_option_confirmation_confirm_click'
|
||||
)
|
||||
}
|
||||
onRemoveCancel={ () =>
|
||||
recordEvent( 'product_remove_option_confirmation_cancel_click' )
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -78,12 +78,21 @@ export function useProductAttributes( {
|
|||
};
|
||||
};
|
||||
|
||||
const getAugmentedAttributes = ( atts: ProductAttribute[] ) => {
|
||||
return atts.map( ( attribute, index ) => ( {
|
||||
...attribute,
|
||||
variation: isVariationAttributes,
|
||||
position: attributes.length + index,
|
||||
} ) );
|
||||
};
|
||||
|
||||
const handleChange = ( newAttributes: ProductAttribute[] ) => {
|
||||
const augmentedAttributes = getAugmentedAttributes( newAttributes );
|
||||
const otherAttributes = isVariationAttributes
|
||||
? allAttributes.filter( ( attribute ) => ! attribute.variation )
|
||||
: allAttributes.filter( ( attribute ) => !! attribute.variation );
|
||||
setAttributes( newAttributes );
|
||||
onChange( [ ...otherAttributes, ...newAttributes ] );
|
||||
setAttributes( augmentedAttributes );
|
||||
onChange( [ ...otherAttributes, ...augmentedAttributes ] );
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Add IR and fields priorities to list of get_country_locale() method to follow conventional way of addressing in Iran.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: enhancement
|
||||
|
||||
Change the sass variable names to more predictable ones.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Remove attribute type logic from attribute component
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
Comment: Mark purchase tests skipped temporarily due to a change in the endpoint behavior
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Update woocommerce-blocks to 9.4.3.
|
|
@ -293,7 +293,7 @@
|
|||
}
|
||||
|
||||
mark.yes {
|
||||
color: $green;
|
||||
color: var(--wc-green);
|
||||
}
|
||||
|
||||
mark.no {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"maxmind-db/reader": "^1.11",
|
||||
"pelago/emogrifier": "^6.0",
|
||||
"woocommerce/action-scheduler": "3.5.4",
|
||||
"woocommerce/woocommerce-blocks": "9.4.2"
|
||||
"woocommerce/woocommerce-blocks": "9.4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1164,6 +1164,20 @@ class WC_Countries {
|
|||
'label' => __( 'State', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'IR' => array(
|
||||
'state' => array(
|
||||
'priority' => 50,
|
||||
),
|
||||
'city' => array(
|
||||
'priority' => 60,
|
||||
),
|
||||
'address_1' => array(
|
||||
'priority' => 70,
|
||||
),
|
||||
'address_2' => array(
|
||||
'priority' => 80,
|
||||
),
|
||||
),
|
||||
'IT' => array(
|
||||
'postcode' => array(
|
||||
'priority' => 65,
|
||||
|
|
|
@ -82,6 +82,7 @@ class WC_Admin_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
|||
* Test is_complete function of Purchase task.
|
||||
*/
|
||||
public function test_is_not_complete_if_remaining_paid_products() {
|
||||
$this->markTestSkipped( 'Skipped temporarily due to change in endpoint behavior.' );
|
||||
update_option( OnboardingProfile::DATA_OPTION, array( 'product_types' => array( 'memberships' ) ) );
|
||||
$this->assertEquals( false, $this->task->is_complete() );
|
||||
}
|
||||
|
@ -160,6 +161,7 @@ class WC_Admin_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
|||
* Test the task title if 2 paid items exist.
|
||||
*/
|
||||
public function test_get_title_if_multiple_paid_themes() {
|
||||
$this->markTestSkipped( 'Skipped temporarily due to change in endpoint behavior.' );
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
|
@ -174,6 +176,7 @@ class WC_Admin_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
|||
* Test the task title if multiple additional paid items exist.
|
||||
*/
|
||||
public function test_get_title_if_multiple_paid_products() {
|
||||
$this->markTestSkipped( 'Skipped temporarily due to change in endpoint behavior.' );
|
||||
update_option(
|
||||
OnboardingProfile::DATA_OPTION,
|
||||
array(
|
||||
|
|
Loading…
Reference in New Issue