Adding attributes block to product block editor. (#38051)
This commit is contained in:
parent
94599a14cf
commit
3679f019bb
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.545 14.4296L13.9351 20.0409C13.7898 20.1865 13.6172 20.3019 13.4272 20.3807C13.2373 20.4595 13.0336 20.5 12.828 20.5C12.6224 20.5 12.4187 20.4595 12.2288 20.3807C12.0388 20.3019 11.8662 20.1865 11.7209 20.0409L5 13.3261V5.5H12.8241L19.545 12.2226C19.8364 12.5159 20 12.9126 20 13.3261C20 13.7396 19.8364 14.1363 19.545 14.4296V14.4296Z" stroke="#1E1E1E" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="9" cy="9.5" r="1" fill="#1E1E1E"/>
|
||||
</svg>
|
After Width: | Height: | Size: 585 B |
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adding attributes components, block and styles.
|
|
@ -35,7 +35,7 @@
|
|||
"@woocommerce/components": "workspace:*",
|
||||
"@woocommerce/currency": "workspace:*",
|
||||
"@woocommerce/customer-effort-score": "workspace:*",
|
||||
"@woocommerce/data": "workspace:^4.1.0",
|
||||
"@woocommerce/data": "workspace:*",
|
||||
"@woocommerce/experimental": "workspace:*",
|
||||
"@woocommerce/navigation": "workspace:^8.1.0",
|
||||
"@woocommerce/number": "workspace:*",
|
||||
|
@ -89,6 +89,7 @@
|
|||
"@types/wordpress__keycodes": "^2.3.1",
|
||||
"@types/wordpress__media-utils": "^3.0.0",
|
||||
"@types/wordpress__plugins": "^3.0.0",
|
||||
"@types/wordpress__rich-text": "^3.4.6",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-js-tests": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "woocommerce/product-attributes-field",
|
||||
"title": "Product attributes",
|
||||
"category": "widgets",
|
||||
"description": "The product attributes.",
|
||||
"keywords": [ "products", "attributes" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"__experimentalRole": "content"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false,
|
||||
"__experimentalToolbar": false
|
||||
},
|
||||
"editorStyle": "file:./editor.css"
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { ProductAttribute } from '@woocommerce/data';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { useEntityProp, useEntityId } from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Attributes as AttributesContainer } from '../../components/attributes/attributes';
|
||||
|
||||
export function Edit() {
|
||||
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
|
||||
ProductAttribute[]
|
||||
>( 'postType', 'product', 'attributes' );
|
||||
|
||||
const productId = useEntityId( 'postType', 'product' );
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<AttributesContainer
|
||||
productId={ productId }
|
||||
value={ entityAttributes }
|
||||
onChange={ setEntityAttributes }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.wp-block-woocommerce-product-images-field {
|
||||
.woocommerce-image-gallery {
|
||||
margin-top: $gap-largest;
|
||||
}
|
||||
.woocommerce-media-uploader {
|
||||
text-align: left;
|
||||
}
|
||||
.woocommerce-media-uploader__label {
|
||||
display: none;
|
||||
}
|
||||
.woocommerce-sortable {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:not(.has-images) {
|
||||
.woocommerce-sortable {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { initBlock } from '../../utils';
|
||||
import metadata from './block.json';
|
||||
import { Edit } from './edit';
|
||||
|
||||
const { name } = metadata;
|
||||
|
||||
export { metadata, name };
|
||||
|
||||
export const settings = {
|
||||
example: {},
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export const init = () =>
|
||||
initBlock( {
|
||||
name,
|
||||
metadata: metadata as never,
|
||||
settings,
|
||||
} );
|
|
@ -17,3 +17,4 @@ export { init as initSummary } from './summary';
|
|||
export { init as initTab } from './tab';
|
||||
export { init as initInventoryQuantity } from './inventory-quantity';
|
||||
export { init as initToggle } from './toggle';
|
||||
export { init as attributesInit } from './attributes';
|
||||
|
|
|
@ -2,32 +2,32 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import {
|
||||
useState,
|
||||
createElement,
|
||||
Fragment,
|
||||
createInterpolateElement,
|
||||
} from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { ProductAttribute } from '@woocommerce/data';
|
||||
import {
|
||||
Sortable,
|
||||
__experimentalSelectControlMenuSlot as SelectControlMenuSlot,
|
||||
Link,
|
||||
} from '@woocommerce/components';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-field.scss';
|
||||
import { EditAttributeModal } from './edit-attribute-modal';
|
||||
import { EnhancedProductAttribute } from '~/products/hooks/use-product-attributes';
|
||||
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
|
||||
import {
|
||||
getAttributeId,
|
||||
getAttributeKey,
|
||||
reorderSortableProductAttributePositions,
|
||||
} from './utils';
|
||||
import { AttributeEmptyState } from '../attribute-empty-state';
|
||||
import {
|
||||
AttributeListItem,
|
||||
NewAttributeListItem,
|
||||
} from '../attribute-list-item';
|
||||
import { AttributeListItem } from '../attribute-list-item';
|
||||
import { NewAttributeModal } from './new-attribute-modal';
|
||||
|
||||
type AttributeControlProps = {
|
||||
|
@ -67,9 +67,9 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
uiStrings = {
|
||||
newAttributeModalTitle: undefined,
|
||||
emptyStateSubtitle: undefined,
|
||||
newAttributeListItemLabel: undefined,
|
||||
newAttributeListItemLabel: __( 'Add attributes', 'woocommerce' ),
|
||||
globalAttributeHelperMessage: __(
|
||||
`You can change the attribute's name in {{link}}Attributes{{/link}}.`,
|
||||
`You can change the attribute's name in <link>Attributes</link>.`,
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
|
@ -160,30 +160,6 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
closeEditModal( updatedAttribute );
|
||||
};
|
||||
|
||||
if ( ! value.length ) {
|
||||
return (
|
||||
<>
|
||||
<AttributeEmptyState
|
||||
addNewLabel={ uiStrings.newAttributeModalTitle }
|
||||
onNewClick={ () => openNewModal() }
|
||||
subtitle={ uiStrings.emptyStateSubtitle }
|
||||
/>
|
||||
{ isNewModalVisible && (
|
||||
<NewAttributeModal
|
||||
onCancel={ () => {
|
||||
closeNewModal();
|
||||
onNewModalCancel();
|
||||
} }
|
||||
onAdd={ handleAdd }
|
||||
selectedAttributeIds={ [] }
|
||||
title={ uiStrings.newAttributeModalTitle }
|
||||
/>
|
||||
) }
|
||||
<SelectControlMenuSlot />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const sortedAttributes = value.sort( ( a, b ) => a.position - b.position );
|
||||
|
||||
const attributeKeyValues = value.reduce(
|
||||
|
@ -203,37 +179,44 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
|
||||
return (
|
||||
<div className="woocommerce-attribute-field">
|
||||
<Sortable
|
||||
onOrderChange={ ( items ) => {
|
||||
const itemPositions = items.reduce(
|
||||
( positions, { props }, index ) => {
|
||||
positions[ getAttributeKey( props.attribute ) ] =
|
||||
index;
|
||||
return positions;
|
||||
},
|
||||
{} as Record< number | string, number >
|
||||
);
|
||||
onChange(
|
||||
reorderSortableProductAttributePositions(
|
||||
itemPositions,
|
||||
attributeKeyValues
|
||||
)
|
||||
);
|
||||
} }
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="woocommerce-add-attribute-list-item__add-button"
|
||||
onClick={ openNewModal }
|
||||
>
|
||||
{ sortedAttributes.map( ( attr ) => (
|
||||
<AttributeListItem
|
||||
attribute={ attr }
|
||||
key={ getAttributeId( attr ) }
|
||||
onEditClick={ () => openEditModal( attr ) }
|
||||
onRemoveClick={ () => handleRemove( attr ) }
|
||||
/>
|
||||
) ) }
|
||||
</Sortable>
|
||||
<NewAttributeListItem
|
||||
label={ uiStrings.newAttributeListItemLabel }
|
||||
onClick={ () => openNewModal() }
|
||||
/>
|
||||
{ uiStrings.newAttributeListItemLabel }
|
||||
</Button>
|
||||
{ Boolean( value.length ) && (
|
||||
<Sortable
|
||||
onOrderChange={ ( items ) => {
|
||||
const itemPositions = items.reduce(
|
||||
( positions, { props }, index ) => {
|
||||
positions[
|
||||
getAttributeKey( props.attribute )
|
||||
] = index;
|
||||
return positions;
|
||||
},
|
||||
{} as Record< number | string, number >
|
||||
);
|
||||
onChange(
|
||||
reorderSortableProductAttributePositions(
|
||||
itemPositions,
|
||||
attributeKeyValues
|
||||
)
|
||||
);
|
||||
} }
|
||||
>
|
||||
{ sortedAttributes.map( ( attr ) => (
|
||||
<AttributeListItem
|
||||
attribute={ attr }
|
||||
key={ getAttributeId( attr ) }
|
||||
onEditClick={ () => openEditModal( attr ) }
|
||||
onRemoveClick={ () => handleRemove( attr ) }
|
||||
/>
|
||||
) ) }
|
||||
</Sortable>
|
||||
) }
|
||||
|
||||
{ isNewModalVisible && (
|
||||
<NewAttributeModal
|
||||
title={ uiStrings.newAttributeModalTitle }
|
||||
|
@ -253,9 +236,9 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
__( 'Edit %s', 'woocommerce' ),
|
||||
currentAttribute.name
|
||||
) }
|
||||
globalAttributeHelperMessage={ interpolateComponents( {
|
||||
mixedString: uiStrings.globalAttributeHelperMessage,
|
||||
components: {
|
||||
globalAttributeHelperMessage={ createInterpolateElement(
|
||||
uiStrings.globalAttributeHelperMessage,
|
||||
{
|
||||
link: (
|
||||
<Link
|
||||
href={ getAdminLink(
|
||||
|
@ -267,8 +250,8 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
|
|||
<></>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
} ) }
|
||||
}
|
||||
) }
|
||||
onCancel={ () => {
|
||||
closeEditModal( currentAttribute );
|
||||
onEditModalCancel( currentAttribute );
|
|
@ -1,5 +1,6 @@
|
|||
.woocommerce-attribute-field {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
|
||||
.woocommerce-sortable {
|
||||
margin: 0;
|
||||
|
@ -12,4 +13,23 @@
|
|||
background: none;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.woocommerce-add-attribute-list-item__add-button {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-product-attributes-field {
|
||||
|
||||
.woocommerce-sortable {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.woocommerce-list-item {
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import {
|
|||
CheckboxControl,
|
||||
TextControl,
|
||||
} from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement } from '@wordpress/element';
|
||||
import { __experimentalTooltip as Tooltip } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
|
@ -20,8 +20,6 @@ import {
|
|||
} from '../attribute-term-input-field';
|
||||
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
|
||||
|
||||
import './edit-attribute-modal.scss';
|
||||
|
||||
type EditAttributeModalProps = {
|
||||
title?: string;
|
||||
nameLabel?: string;
|
|
@ -2,7 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement, Fragment } from '@wordpress/element';
|
||||
import { trash } from '@wordpress/icons';
|
||||
import {
|
||||
Form,
|
||||
|
@ -20,13 +20,12 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './new-attribute-modal.scss';
|
||||
import { AttributeInputField } from '../attribute-input-field';
|
||||
import {
|
||||
AttributeTermInputField,
|
||||
CustomAttributeTermInputField,
|
||||
} from '../attribute-term-input-field';
|
||||
import { EnhancedProductAttribute } from '~/products/hooks/use-product-attributes';
|
||||
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
|
||||
import { getProductAttributeObject } from './utils';
|
||||
|
||||
type NewAttributeModalProps = {
|
|
@ -2,7 +2,12 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { render, act, screen } from '@testing-library/react';
|
||||
import { useState, useEffect } from '@wordpress/element';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
createElement,
|
||||
Fragment,
|
||||
} from '@wordpress/element';
|
||||
import { ProductAttribute } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
|
@ -108,12 +113,11 @@ describe( 'AttributeControl', () => {
|
|||
} );
|
||||
|
||||
describe( 'empty state', () => {
|
||||
it( 'should show subtitle and "Add first attribute" button', () => {
|
||||
it( 'should show subtitle and "Add attributes" button', () => {
|
||||
const { queryByText } = render(
|
||||
<AttributeControl value={ [] } onChange={ () => {} } />
|
||||
);
|
||||
expect( queryByText( 'No attributes yet' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Add first attribute' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Add attributes' ) ).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -166,7 +170,7 @@ describe( 'AttributeControl', () => {
|
|||
|
||||
describe( 'deleting', () => {
|
||||
it( 'should show a window confirm when trash icon is clicked', async () => {
|
||||
jest.spyOn( global, 'confirm' ).mockReturnValueOnce( false );
|
||||
jest.spyOn( globalThis, 'confirm' ).mockReturnValueOnce( false );
|
||||
act( () => {
|
||||
render(
|
||||
<AttributeControl
|
||||
|
@ -178,11 +182,11 @@ describe( 'AttributeControl', () => {
|
|||
(
|
||||
await screen.findAllByLabelText( 'Remove attribute' )
|
||||
)[ 0 ].click();
|
||||
expect( global.confirm ).toHaveBeenCalled();
|
||||
expect( globalThis.confirm ).toHaveBeenCalled();
|
||||
} );
|
||||
|
||||
it( 'should trigger onChange with removed item when user clicks ok on alert', async () => {
|
||||
jest.spyOn( global, 'confirm' ).mockReturnValueOnce( true );
|
||||
jest.spyOn( globalThis, 'confirm' ).mockReturnValueOnce( true );
|
||||
const onChange = jest.fn();
|
||||
|
||||
act( () => {
|
||||
|
@ -198,12 +202,12 @@ describe( 'AttributeControl', () => {
|
|||
await screen.findAllByLabelText( 'Remove attribute' )
|
||||
)[ 0 ].click();
|
||||
|
||||
expect( global.confirm ).toHaveBeenCalled();
|
||||
expect( globalThis.confirm ).toHaveBeenCalled();
|
||||
expect( onChange ).toHaveBeenCalledWith( [ attributeList[ 1 ] ] );
|
||||
} );
|
||||
|
||||
it( 'should not trigger onChange with removed item when user cancel', async () => {
|
||||
jest.spyOn( global, 'confirm' ).mockReturnValueOnce( false );
|
||||
jest.spyOn( globalThis, 'confirm' ).mockReturnValueOnce( false );
|
||||
const onChange = jest.fn();
|
||||
act( () => {
|
||||
render(
|
||||
|
@ -216,14 +220,14 @@ describe( 'AttributeControl', () => {
|
|||
(
|
||||
await screen.findAllByLabelText( 'Remove attribute' )
|
||||
)[ 0 ].click();
|
||||
expect( global.confirm ).toHaveBeenCalled();
|
||||
expect( globalThis.confirm ).toHaveBeenCalled();
|
||||
expect( onChange ).not.toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'dragging', () => {
|
||||
it.skip( 'should trigger onChange with new order when onOrderChange triggered', async () => {
|
||||
jest.spyOn( global, 'confirm' ).mockReturnValueOnce( true );
|
||||
jest.spyOn( globalThis, 'confirm' ).mockReturnValueOnce( true );
|
||||
const onChange = jest.fn();
|
||||
|
||||
act( () => {
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { ProductAttribute, ProductAttributeTerm } from '@woocommerce/data';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
|
@ -5,6 +5,7 @@ import { sprintf, __ } from '@wordpress/i18n';
|
|||
import { useSelect } from '@wordpress/data';
|
||||
import { Spinner, Icon } from '@wordpress/components';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME,
|
||||
QueryProductAttribute,
|
||||
|
@ -21,8 +22,7 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-input-field.scss';
|
||||
import { EnhancedProductAttribute } from '~/products/hooks/use-product-attributes';
|
||||
import { EnhancedProductAttribute } from '../../hooks/use-product-attributes';
|
||||
|
||||
type NarrowedQueryAttribute = Pick< QueryProductAttribute, 'id' | 'name' >;
|
||||
|
||||
|
@ -51,6 +51,8 @@ export const AttributeInputField: React.FC< AttributeInputFieldProps > = ( {
|
|||
disabled,
|
||||
ignoredAttributeIds = [],
|
||||
} ) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { attributes, isLoading } = useSelect( ( select: WCDataSelector ) => {
|
||||
const { getProductAttributes, hasFinishedResolution } = select(
|
||||
EXPERIMENTAL_PRODUCT_ATTRIBUTES_STORE_NAME
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement } from '@wordpress/element';
|
||||
import { ProductAttribute, QueryProductAttribute } from '@woocommerce/data';
|
||||
|
||||
/**
|
|
@ -4,6 +4,7 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { ListItem } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
type NewAttributeListItemProps = {
|
||||
label?: string;
|
|
@ -7,11 +7,7 @@ import { ProductAttribute } from '@woocommerce/data';
|
|||
import { sprintf, __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { closeSmall } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-list-item.scss';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
type AttributeListItemProps = {
|
||||
attribute: ProductAttribute;
|
|
@ -4,7 +4,14 @@
|
|||
import { sprintf, __ } from '@wordpress/i18n';
|
||||
import { CheckboxControl, Icon, Spinner } from '@wordpress/components';
|
||||
import { resolveSelect } from '@wordpress/data';
|
||||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
createElement,
|
||||
Fragment,
|
||||
} from '@wordpress/element';
|
||||
import { useDebounce } from '@wordpress/compose';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import {
|
||||
|
@ -21,7 +28,6 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-term-input-field.scss';
|
||||
import { CreateAttributeTermModal } from './create-attribute-term-modal';
|
||||
|
||||
type AttributeTermInputFieldProps = {
|
|
@ -8,7 +8,7 @@ import {
|
|||
TextareaControl,
|
||||
TextControl,
|
||||
} from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement, Fragment } from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { cleanForSlug } from '@wordpress/url';
|
||||
import { Form, FormContextType, FormErrors } from '@woocommerce/components';
|
||||
|
@ -19,11 +19,6 @@ import {
|
|||
QueryProductAttribute,
|
||||
} from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './create-attribute-term-modal.scss';
|
||||
|
||||
type CreateAttributeTermModalProps = {
|
||||
initialAttributeTermName: string;
|
||||
attributeId: number;
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { sprintf, __ } from '@wordpress/i18n';
|
||||
import { CheckboxControl, Icon } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement, Fragment } from '@wordpress/element';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import {
|
||||
__experimentalSelectControl as SelectControl,
|
||||
|
@ -11,11 +11,6 @@ import {
|
|||
__experimentalSelectControlMenuItem as MenuItem,
|
||||
} from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-term-input-field.scss';
|
||||
|
||||
type CustomAttributeTermInputFieldProps = {
|
||||
value?: string[];
|
||||
onChange: ( value: string[] ) => void;
|
|
@ -2,7 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { act, render, waitFor, screen } from '@testing-library/react';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, createElement } from '@wordpress/element';
|
||||
import { resolveSelect } from '@wordpress/data';
|
||||
import { ProductAttributeTerm } from '@woocommerce/data';
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { ProductAttribute } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
|
@ -8,7 +9,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { AttributeControl } from '../attribute-control';
|
||||
import { useProductAttributes } from '~/products/hooks/use-product-attributes';
|
||||
import { useProductAttributes } from '../../hooks/use-product-attributes';
|
||||
|
||||
type AttributesProps = {
|
||||
value: ProductAttribute[];
|
|
@ -9,6 +9,7 @@
|
|||
button,
|
||||
span,
|
||||
label,
|
||||
div,
|
||||
input {
|
||||
font-family: var( --wp--preset--font-family--system-font );
|
||||
}
|
||||
|
|
|
@ -27,4 +27,7 @@ export {
|
|||
type ShippingDimensionsImageProps,
|
||||
type HighlightSides,
|
||||
} from './shipping-dimensions-image';
|
||||
|
||||
export { AttributeControl as __experimentalAttributeControl } from './attribute-control';
|
||||
export { Attributes as __experimentalAttributes } from './attributes';
|
||||
export * from './add-new-shipping-class-modal';
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from '@wordpress/element';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { sift } from '../../utils';
|
||||
import { sift } from '../utils';
|
||||
|
||||
type useProductAttributesProps = {
|
||||
allAttributes: ProductAttribute[];
|
|
@ -24,6 +24,13 @@
|
|||
@import 'components/edit-product-link-modal/style.scss';
|
||||
@import 'components/details-categories-field/style.scss';
|
||||
@import 'components/details-categories-field/create-category-modal.scss';
|
||||
@import 'components/attribute-control/attribute-field.scss';
|
||||
@import 'components/attribute-control/edit-attribute-modal.scss';
|
||||
@import 'components/attribute-control/new-attribute-modal.scss';
|
||||
@import 'components/attribute-input-field/attribute-input-field.scss';
|
||||
@import 'components/attribute-list-item/attribute-list-item.scss';
|
||||
@import 'components/attribute-term-input-field/attribute-term-input-field.scss';
|
||||
@import 'components/attribute-term-input-field/create-attribute-term-modal.scss';
|
||||
|
||||
/* Field Blocks */
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export * from './create-ordered-children';
|
|||
export * from './sort-fills-by-order';
|
||||
export * from './init-blocks';
|
||||
export * from './product-apifetch-middleware';
|
||||
export * from './sift';
|
||||
|
||||
export {
|
||||
AUTO_DRAFT_NAME,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
type SiftResult< T > = [ T[], T[] ];
|
||||
|
||||
/**
|
||||
* Similar to filter, but return two arrays separated by a partitioner function
|
||||
*
|
||||
* @param {Array} arr - Original array of values.
|
||||
* @param {Function} partitioner - Function to return truthy/falsy values to separate items in array.
|
||||
*
|
||||
* @return {Array} - Array of two arrays, first including truthy values, and second including falsy.
|
||||
*/
|
||||
export const sift = < T >(
|
||||
arr: Array< T >,
|
||||
partitioner: ( item: T ) => boolean
|
||||
): SiftResult< T > =>
|
||||
arr.reduce< SiftResult< T > >(
|
||||
( all, item ) => {
|
||||
all[ !! partitioner( item ) ? 0 : 1 ].push( item );
|
||||
return all;
|
||||
},
|
||||
[ [], [] ]
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
<svg width="151" height="72" viewBox="0 0 151 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="45.8291" y="25.2363" width="78.4945" height="45.2637" rx="3.7381" fill="#F6F7F7" stroke="#DDDDDD" stroke-width="3" stroke-dasharray="4 3"/>
|
||||
<rect x="2.3125" y="1.5" width="78.4945" height="69" rx="3.7381" fill="white" stroke="#DDDDDD" stroke-width="3"/>
|
||||
<line x1="11.3516" y1="9.94922" x2="33.6702" y2="9.94922" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="11.0156" y1="55.4668" x2="33.3343" y2="55.4668" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="11.0156" y1="61.0054" x2="26.2134" y2="61.0054" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<rect x="11.0156" y="41.0605" width="27.0659" height="8.07692" rx="1.5" stroke="#DDDDDD" stroke-width="3"/>
|
||||
<line x1="45.0361" y1="55.4673" x2="67.3548" y2="55.4673" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="45.0361" y1="61.0054" x2="60.2339" y2="61.0054" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<rect x="45.0361" y="41.0605" width="27.0659" height="8.07692" rx="1.5" stroke="#DDDDDD" stroke-width="3"/>
|
||||
<rect x="11.0156" y="18.1152" width="61.0879" height="14.4066" rx="1.5" stroke="#DDDDDD" stroke-width="3"/>
|
||||
<path d="M58.5703 23.7363L61.4236 26.5897C61.8142 26.9802 62.4473 26.9802 62.8379 26.5897L65.6912 23.7363" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="17.5" y1="25" x2="39.8187" y2="25" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M149.188 42.6308L134.977 30.1968" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle r="18.0392" transform="matrix(-1 0 0 1 122.541 19.5392)" fill="white" stroke="#DDDDDD" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M121.297 15.834H131.667M121.297 24.0007H131.667" stroke="#DDDDDD" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<ellipse cx="114.333" cy="23.9993" rx="2.33333" ry="2.33333" fill="#DDDDDD"/>
|
||||
<ellipse cx="114.333" cy="15.8333" rx="2.33333" ry="2.33333" fill="#DDDDDD"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,14 +0,0 @@
|
|||
.woocommerce-attribute-empty-state {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__image {
|
||||
max-width: 150px;
|
||||
margin: $gap-larger 0 $gap-large;
|
||||
}
|
||||
&__add-new {
|
||||
margin: $gap-large 0 $gap-larger;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Card, CardBody } from '@wordpress/components';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './attribute-empty-state.scss';
|
||||
import AttributeEmptyStateLogo from './attribute-empty-state-logo.svg';
|
||||
|
||||
type AttributeEmptyStateProps = {
|
||||
image?: string;
|
||||
subtitle?: string;
|
||||
addNewLabel?: string;
|
||||
onNewClick?: () => void;
|
||||
};
|
||||
|
||||
export const AttributeEmptyState: React.FC< AttributeEmptyStateProps > = ( {
|
||||
image = AttributeEmptyStateLogo,
|
||||
subtitle = __( 'No attributes yet', 'woocommerce' ),
|
||||
addNewLabel = __( 'Add first attribute', 'woocommerce' ),
|
||||
onNewClick,
|
||||
} ) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<div className="woocommerce-attribute-empty-state">
|
||||
<img
|
||||
src={ image }
|
||||
alt="Completed"
|
||||
className="woocommerce-attribute-empty-state__image"
|
||||
/>
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
weight="600"
|
||||
size="14"
|
||||
lineHeight="20px"
|
||||
className="woocommerce-attribute-empty-state__subtitle"
|
||||
>
|
||||
{ subtitle }
|
||||
</Text>
|
||||
{ typeof onNewClick === 'function' && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="woocommerce-attribute-empty-state__add-new"
|
||||
onClick={ onNewClick }
|
||||
>
|
||||
{ addNewLabel }
|
||||
</Button>
|
||||
) }
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
|
@ -1,2 +0,0 @@
|
|||
export * from './attribute-empty-state';
|
||||
export { default as AttributeEmptyStateLogo } from './attribute-empty-state-logo.svg';
|
|
@ -5,12 +5,12 @@ import { __ } from '@wordpress/i18n';
|
|||
import { Product, ProductAttribute } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useFormContext } from '@woocommerce/components';
|
||||
import { AttributeControl } from '@woocommerce/product-editor/src/components/attribute-control';
|
||||
import { useProductAttributes } from '@woocommerce/product-editor/src/hooks/use-product-attributes';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { AttributeControl } from '../attribute-control';
|
||||
import { useProductAttributes } from '~/products/hooks/use-product-attributes';
|
||||
import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper';
|
||||
|
||||
type OptionsProps = {
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useFormContext } from '@woocommerce/components';
|
||||
|
||||
import { __experimentalAttributes as Attributes } from '@woocommerce/product-editor';
|
||||
import { Product } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Attributes } from '../../fields/attributes';
|
||||
|
||||
export const AttributesField = () => {
|
||||
const {
|
||||
|
|
|
@ -5,8 +5,8 @@ const { get } = require( 'lodash' );
|
|||
const path = require( 'path' );
|
||||
const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
|
||||
const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' );
|
||||
const BundleAnalyzerPlugin =
|
||||
require( 'webpack-bundle-analyzer' ).BundleAnalyzerPlugin;
|
||||
const BundleAnalyzerPlugin = require( 'webpack-bundle-analyzer' )
|
||||
.BundleAnalyzerPlugin;
|
||||
const MomentTimezoneDataPlugin = require( 'moment-timezone-data-webpack-plugin' );
|
||||
const ForkTsCheckerWebpackPlugin = require( 'fork-ts-checker-webpack-plugin' );
|
||||
const ReactRefreshWebpackPlugin = require( '@pmmmwh/react-refresh-webpack-plugin' );
|
||||
|
@ -208,8 +208,8 @@ const webpackConfig = {
|
|||
new CopyWebpackPlugin( {
|
||||
patterns: [
|
||||
{
|
||||
from: '../../packages/js/product-editor/assets/icons',
|
||||
to: './product-editor/icons',
|
||||
from: '../../packages/js/product-editor/assets',
|
||||
to: './product-editor',
|
||||
},
|
||||
],
|
||||
} ),
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Moving product attributes components to product-editor package.
|
|
@ -484,6 +484,26 @@ class WC_Post_Types {
|
|||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'woocommerce/product-section',
|
||||
array(
|
||||
'title' => __( 'Attributes', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Attributes guide link opening tag. %2$s: Attributes guide link closing tag.*/
|
||||
__( 'Add descriptive pieces of information that customers can use to filter and search for this product. %1$sLearn more%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/document/managing-product-taxonomies/#product-attributes" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'icon' => array(
|
||||
'src' => plugins_url( '/assets/client/admin/product-editor/icons/section_attributes.svg', WC_PLUGIN_FILE ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'woocommerce/product-attributes-field',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
|
|
|
@ -2032,7 +2032,7 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../customer-effort-score
|
||||
'@woocommerce/data':
|
||||
specifier: workspace:^4.1.0
|
||||
specifier: workspace:*
|
||||
version: link:../data
|
||||
'@woocommerce/experimental':
|
||||
specifier: workspace:*
|
||||
|
@ -2185,6 +2185,9 @@ importers:
|
|||
'@types/wordpress__plugins':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0(react-dom@17.0.2)(react@17.0.2)
|
||||
'@types/wordpress__rich-text':
|
||||
specifier: ^3.4.6
|
||||
version: 3.4.6
|
||||
'@woocommerce/eslint-plugin':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-plugin
|
||||
|
@ -5976,8 +5979,8 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.12.9
|
||||
'@babel/helper-annotate-as-pure': 7.16.7
|
||||
'@babel/helper-create-class-features-plugin': 7.17.6(@babel/core@7.12.9)
|
||||
'@babel/helper-annotate-as-pure': 7.18.6
|
||||
'@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.12.9)
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.12.9)
|
||||
transitivePeerDependencies:
|
||||
|
@ -6006,8 +6009,8 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.21.3
|
||||
'@babel/helper-annotate-as-pure': 7.16.7
|
||||
'@babel/helper-create-class-features-plugin': 7.17.6(@babel/core@7.21.3)
|
||||
'@babel/helper-annotate-as-pure': 7.18.6
|
||||
'@babel/helper-create-class-features-plugin': 7.19.0(@babel/core@7.21.3)
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.21.3)
|
||||
transitivePeerDependencies:
|
||||
|
@ -6827,7 +6830,7 @@ packages:
|
|||
dependencies:
|
||||
'@babel/core': 7.12.9
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
'@babel/helper-remap-async-to-generator': 7.16.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -6855,7 +6858,7 @@ packages:
|
|||
dependencies:
|
||||
'@babel/core': 7.21.3
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
'@babel/helper-remap-async-to-generator': 7.16.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -13468,7 +13471,7 @@ packages:
|
|||
'@babel/generator': 7.17.7
|
||||
'@babel/parser': 7.17.8
|
||||
'@babel/plugin-transform-react-jsx': 7.17.3(@babel/core@7.17.8)
|
||||
'@babel/preset-env': 7.16.11(@babel/core@7.17.8)
|
||||
'@babel/preset-env': 7.20.2(@babel/core@7.17.8)
|
||||
'@jest/transform': 26.6.2
|
||||
'@mdx-js/loader': 1.6.22(react@17.0.2)
|
||||
'@mdx-js/mdx': 1.6.22
|
||||
|
@ -13492,7 +13495,7 @@ packages:
|
|||
acorn: 7.4.1
|
||||
acorn-jsx: 5.3.2(acorn@7.4.1)
|
||||
acorn-walk: 7.2.0
|
||||
core-js: 3.21.1
|
||||
core-js: 3.29.1
|
||||
doctrine: 3.0.0
|
||||
escodegen: 2.0.0
|
||||
fast-deep-equal: 3.1.3
|
||||
|
@ -13508,7 +13511,7 @@ packages:
|
|||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
react-element-to-jsx-string: 14.3.4(react-dom@17.0.2)(react@17.0.2)
|
||||
regenerator-runtime: 0.13.9
|
||||
regenerator-runtime: 0.13.11
|
||||
remark-external-links: 8.0.0
|
||||
remark-slug: 6.1.0
|
||||
ts-dedent: 2.2.0
|
||||
|
@ -20836,8 +20839,8 @@ packages:
|
|||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.20.2
|
||||
caniuse-lite: 1.0.30001352
|
||||
browserslist: 4.21.4
|
||||
caniuse-lite: 1.0.30001418
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
|
@ -22380,6 +22383,7 @@ packages:
|
|||
escalade: 3.1.1
|
||||
node-releases: 2.0.6
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/browserslist@4.20.4:
|
||||
resolution: {integrity: sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==}
|
||||
|
|
Loading…
Reference in New Issue