Migrating product editor images section to slot-fill (#36461)
This commit is contained in:
parent
7e17a96914
commit
687dd6fdfe
|
@ -0,0 +1,7 @@
|
|||
export const PRODUCT_DETAILS_SLUG = 'product-details';
|
||||
|
||||
export const DETAILS_SECTION_ID = 'general/details';
|
||||
export const IMAGES_SECTION_ID = 'general/images';
|
||||
|
||||
export const TAB_GENERAL_ID = 'tab/general';
|
||||
export const PLUGIN_ID = 'woocommerce';
|
|
@ -1,2 +0,0 @@
|
|||
export const PRODUCT_DETAILS_SLUG = 'product-details';
|
||||
export const DETAILS_SECTION_ID = 'general/details';
|
|
@ -16,7 +16,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { getCheckboxTracks } from '../../sections/utils';
|
||||
import { PRODUCT_DETAILS_SLUG } from './index';
|
||||
import { PRODUCT_DETAILS_SLUG } from '../constants';
|
||||
|
||||
export const DetailsFeatureField = () => {
|
||||
const { getCheckboxControlProps } = useFormContext< Product >();
|
||||
|
|
|
@ -18,7 +18,7 @@ import { useState } from '@wordpress/element';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { EditProductLinkModal } from '../../shared/edit-product-link-modal';
|
||||
import { PRODUCT_DETAILS_SLUG } from './index';
|
||||
import { PRODUCT_DETAILS_SLUG } from '../constants';
|
||||
|
||||
export const DetailsNameField = ( {} ) => {
|
||||
const [ showProductLinkEditModal, setShowProductLinkEditModal ] =
|
||||
|
|
|
@ -18,20 +18,22 @@ import {
|
|||
DetailsFeatureField,
|
||||
DetailsSummaryField,
|
||||
DetailsDescriptionField,
|
||||
DETAILS_SECTION_ID,
|
||||
} from './index';
|
||||
|
||||
import { DETAILS_SECTION_ID, PLUGIN_ID, TAB_GENERAL_ID } from '../constants';
|
||||
|
||||
import './product-details-section.scss';
|
||||
|
||||
const DetailsSection = () => (
|
||||
<>
|
||||
<WooProductSectionItem
|
||||
id={ DETAILS_SECTION_ID }
|
||||
location="tab/general"
|
||||
pluginId="core"
|
||||
location={ TAB_GENERAL_ID }
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 1 }
|
||||
>
|
||||
<ProductFieldSection
|
||||
id="general/details"
|
||||
id={ DETAILS_SECTION_ID }
|
||||
title={ __( 'Product details', 'woocommerce' ) }
|
||||
description={ __(
|
||||
'This info will be displayed on the product page, category pages, social media, and search results.',
|
||||
|
@ -42,7 +44,7 @@ const DetailsSection = () => (
|
|||
<WooProductFieldItem
|
||||
id="details/name"
|
||||
section={ DETAILS_SECTION_ID }
|
||||
pluginId="core"
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 1 }
|
||||
>
|
||||
<DetailsNameField />
|
||||
|
@ -50,7 +52,7 @@ const DetailsSection = () => (
|
|||
<WooProductFieldItem
|
||||
id="details/categories"
|
||||
section={ DETAILS_SECTION_ID }
|
||||
pluginId="core"
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 3 }
|
||||
>
|
||||
<DetailsCategoriesField />
|
||||
|
@ -58,7 +60,7 @@ const DetailsSection = () => (
|
|||
<WooProductFieldItem
|
||||
id="details/feature"
|
||||
section={ DETAILS_SECTION_ID }
|
||||
pluginId="core"
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 5 }
|
||||
>
|
||||
<DetailsFeatureField />
|
||||
|
@ -66,7 +68,7 @@ const DetailsSection = () => (
|
|||
<WooProductFieldItem
|
||||
id="details/summary"
|
||||
section={ DETAILS_SECTION_ID }
|
||||
pluginId="core"
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 7 }
|
||||
>
|
||||
<DetailsSummaryField />
|
||||
|
@ -74,7 +76,7 @@ const DetailsSection = () => (
|
|||
<WooProductFieldItem
|
||||
id="details/description"
|
||||
section={ DETAILS_SECTION_ID }
|
||||
pluginId="core"
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 9 }
|
||||
>
|
||||
<DetailsDescriptionField />
|
||||
|
|
|
@ -3,4 +3,3 @@ export * from './details-field-categories';
|
|||
export * from './details-field-feature';
|
||||
export * from './details-field-summary';
|
||||
export * from './details-field-description';
|
||||
export * from './constants';
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
useFormContext,
|
||||
MediaUploader,
|
||||
ImageGallery,
|
||||
ImageGalleryItem,
|
||||
} from '@woocommerce/components';
|
||||
import { CardBody, DropZone } from '@wordpress/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { Icon, trash } from '@wordpress/icons';
|
||||
import { MediaItem } from '@wordpress/media-utils';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import DragAndDrop from '../../images/drag-and-drop.svg';
|
||||
|
||||
type Image = MediaItem & {
|
||||
src: string;
|
||||
};
|
||||
|
||||
export const ImagesGalleryField = () => {
|
||||
const { getInputProps, setValue } = useFormContext< Product >();
|
||||
const images = ( getInputProps( 'images' ).value as Image[] ) || [];
|
||||
const [ isRemovingZoneVisible, setIsRemovingZoneVisible ] =
|
||||
useState< boolean >( false );
|
||||
const [ isRemoving, setIsRemoving ] = useState< boolean >( false );
|
||||
const [ draggedImageId, setDraggedImageId ] = useState< number | null >(
|
||||
null
|
||||
);
|
||||
|
||||
const toggleRemoveZone = () => {
|
||||
setIsRemovingZoneVisible( ! isRemovingZoneVisible );
|
||||
};
|
||||
|
||||
const orderImages = ( newOrder: JSX.Element[] ) => {
|
||||
const orderedImages = newOrder.map( ( image ) => {
|
||||
return images.find(
|
||||
( file ) => file.id === parseInt( image?.props?.id, 10 )
|
||||
);
|
||||
} );
|
||||
recordEvent( 'product_images_change_image_order_via_image_gallery' );
|
||||
setValue( 'images', orderedImages );
|
||||
};
|
||||
const onFileUpload = ( files: MediaItem[] ) => {
|
||||
if ( files[ 0 ].id ) {
|
||||
recordEvent( 'product_images_add_via_file_upload_area' );
|
||||
setValue( 'images', [ ...images, ...files ] );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames( 'woocommerce-product-form__images', {
|
||||
'has-images': images.length > 0,
|
||||
} ) }
|
||||
>
|
||||
<ImageGallery
|
||||
onDragStart={ ( event ) => {
|
||||
const { id: imageId, dataset } =
|
||||
event.target as HTMLElement;
|
||||
if ( imageId ) {
|
||||
setDraggedImageId( parseInt( imageId, 10 ) );
|
||||
} else {
|
||||
const index = dataset?.index;
|
||||
if ( index ) {
|
||||
setDraggedImageId(
|
||||
images[ parseInt( index, 10 ) ]?.id
|
||||
);
|
||||
}
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onDragEnd={ () => {
|
||||
if ( isRemoving && draggedImageId ) {
|
||||
recordEvent(
|
||||
'product_images_remove_image_button_click'
|
||||
);
|
||||
setValue(
|
||||
'images',
|
||||
images.filter(
|
||||
( img ) => img.id !== draggedImageId
|
||||
)
|
||||
);
|
||||
setIsRemoving( false );
|
||||
setDraggedImageId( null );
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onOrderChange={ orderImages }
|
||||
onReplace={ ( { replaceIndex, media } ) => {
|
||||
if (
|
||||
images.find( ( img ) => media.id === img.id ) ===
|
||||
undefined
|
||||
) {
|
||||
images[ replaceIndex ] = media as Image;
|
||||
recordEvent(
|
||||
'product_images_replace_image_button_click'
|
||||
);
|
||||
setValue( 'images', images );
|
||||
}
|
||||
} }
|
||||
onSelectAsCover={ () =>
|
||||
recordEvent(
|
||||
'product_images_select_image_as_cover_button_click'
|
||||
)
|
||||
}
|
||||
>
|
||||
{ images.map( ( image ) => (
|
||||
<ImageGalleryItem
|
||||
key={ image.id || image.url }
|
||||
alt={ image.alt }
|
||||
src={ image.url || image.src }
|
||||
id={ `${ image.id }` }
|
||||
/>
|
||||
) ) }
|
||||
</ImageGallery>
|
||||
<div className="woocommerce-product-form__image-drop-zone">
|
||||
{ isRemovingZoneVisible ? (
|
||||
<CardBody>
|
||||
<div className="woocommerce-product-form__remove-image-drop-zone">
|
||||
<span>
|
||||
<Icon
|
||||
icon={ trash }
|
||||
size={ 20 }
|
||||
className="icon-control"
|
||||
/>
|
||||
{ __( 'Drop here to remove', 'woocommerce' ) }
|
||||
</span>
|
||||
<DropZone
|
||||
onHTMLDrop={ () => setIsRemoving( true ) }
|
||||
onDrop={ () => setIsRemoving( true ) }
|
||||
label={ __(
|
||||
'Drop here to remove',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
) : (
|
||||
<CardBody>
|
||||
<MediaUploader
|
||||
multipleSelect={ true }
|
||||
onError={ () => null }
|
||||
onFileUploadChange={ onFileUpload }
|
||||
onSelect={ ( files ) => {
|
||||
const newImages = files.filter(
|
||||
( img: Image ) =>
|
||||
! images.find(
|
||||
( image ) => image.id === img.id
|
||||
)
|
||||
);
|
||||
if ( newImages.length > 0 ) {
|
||||
recordEvent(
|
||||
'product_images_add_via_media_library'
|
||||
);
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
...newImages,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
onUpload={ ( files ) => {
|
||||
if ( files[ 0 ].id ) {
|
||||
recordEvent(
|
||||
'product_images_add_via_drag_and_drop_upload'
|
||||
);
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
...files,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
label={
|
||||
<>
|
||||
<img
|
||||
src={ DragAndDrop }
|
||||
alt={ __( 'Completed', 'woocommerce' ) }
|
||||
className="woocommerce-product-form__drag-and-drop-image"
|
||||
/>
|
||||
<span>
|
||||
{ __(
|
||||
'Drag images here or click to upload',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</CardBody>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
__experimentalWooProductFieldItem as WooProductFieldItem,
|
||||
__experimentalProductFieldSection as ProductFieldSection,
|
||||
Link,
|
||||
} from '@woocommerce/components';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ImagesGalleryField } from './index';
|
||||
import { IMAGES_SECTION_ID, TAB_GENERAL_ID, PLUGIN_ID } from '../constants';
|
||||
|
||||
import './images-section.scss';
|
||||
|
||||
const ImagesSection = () => (
|
||||
<>
|
||||
<WooProductSectionItem
|
||||
id={ IMAGES_SECTION_ID }
|
||||
location={ TAB_GENERAL_ID }
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 3 }
|
||||
>
|
||||
<ProductFieldSection
|
||||
id={ IMAGES_SECTION_ID }
|
||||
title={ __( 'Images', 'woocommerce' ) }
|
||||
description={
|
||||
<>
|
||||
<span>
|
||||
{ __(
|
||||
'For best results, use JPEG files that are 1000 by 1000 pixels or larger.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<Link
|
||||
className="woocommerce-form-section__header-link"
|
||||
href="https://woocommerce.com/posts/fast-high-quality-product-photos/"
|
||||
target="_blank"
|
||||
type="external"
|
||||
onClick={ () => {
|
||||
recordEvent( 'prepare_images_help' );
|
||||
} }
|
||||
>
|
||||
{ __(
|
||||
'How should I prepare images?',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</WooProductSectionItem>
|
||||
<WooProductFieldItem
|
||||
id="images/gallery"
|
||||
section={ IMAGES_SECTION_ID }
|
||||
pluginId={ PLUGIN_ID }
|
||||
order={ 1 }
|
||||
>
|
||||
<ImagesGalleryField />
|
||||
</WooProductFieldItem>
|
||||
</>
|
||||
);
|
||||
|
||||
registerPlugin( 'wc-admin-product-editor-images-section', {
|
||||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-product-editor',
|
||||
render: () => <ImagesSection />,
|
||||
} );
|
|
@ -0,0 +1 @@
|
|||
export * from './images-field-gallery';
|
|
@ -4,3 +4,4 @@
|
|||
import './product-form-fills';
|
||||
|
||||
export * from './details-section/details-section-fills';
|
||||
export * from './images-section/images-section-fills';
|
||||
|
|
|
@ -20,12 +20,12 @@ import { ProductInventorySection } from './sections/product-inventory-section';
|
|||
import { PricingSection } from './sections/pricing-section';
|
||||
import { ProductShippingSection } from './sections/product-shipping-section';
|
||||
import { ProductVariationsSection } from './sections/product-variations-section';
|
||||
import { ImagesSection } from './sections/images-section';
|
||||
import { validate } from './product-validation';
|
||||
import { AttributesSection } from './sections/attributes-section';
|
||||
import { OptionsSection } from './sections/options-section';
|
||||
import { ProductFormFooter } from './layout/product-form-footer';
|
||||
import { ProductFormTab } from './product-form-tab';
|
||||
import { TAB_GENERAL_ID } from './fills/constants';
|
||||
|
||||
export const ProductForm: React.FC< {
|
||||
product?: PartialProduct;
|
||||
|
@ -50,8 +50,9 @@ export const ProductForm: React.FC< {
|
|||
<ProductFormHeader />
|
||||
<ProductFormLayout>
|
||||
<ProductFormTab name="general" title="General">
|
||||
<WooProductSectionItem.Slot location="tab/general" />
|
||||
<ImagesSection />
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_GENERAL_ID }
|
||||
/>
|
||||
<AttributesSection />
|
||||
</ProductFormTab>
|
||||
<ProductFormTab
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
Link,
|
||||
useFormContext,
|
||||
MediaUploader,
|
||||
ImageGallery,
|
||||
ImageGalleryItem,
|
||||
} from '@woocommerce/components';
|
||||
import { Card, CardBody, DropZone } from '@wordpress/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import classnames from 'classnames';
|
||||
import { Icon, trash } from '@wordpress/icons';
|
||||
import { MediaItem } from '@wordpress/media-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductSectionLayout } from '../layout/product-section-layout';
|
||||
import DragAndDrop from '../images/drag-and-drop.svg';
|
||||
import './images-section.scss';
|
||||
|
||||
type Image = MediaItem & {
|
||||
src: string;
|
||||
};
|
||||
|
||||
export const ImagesSection: React.FC = () => {
|
||||
const { getInputProps, setValue } = useFormContext< Product >();
|
||||
const images = ( getInputProps( 'images' ).value as Image[] ) || [];
|
||||
const [ isRemovingZoneVisible, setIsRemovingZoneVisible ] =
|
||||
useState< boolean >( false );
|
||||
const [ isRemoving, setIsRemoving ] = useState< boolean >( false );
|
||||
const [ draggedImageId, setDraggedImageId ] = useState< number | null >(
|
||||
null
|
||||
);
|
||||
|
||||
const toggleRemoveZone = () => {
|
||||
setIsRemovingZoneVisible( ! isRemovingZoneVisible );
|
||||
};
|
||||
|
||||
const orderImages = ( newOrder: JSX.Element[] ) => {
|
||||
const orderedImages = newOrder.map( ( image ) => {
|
||||
return images.find(
|
||||
( file ) => file.id === parseInt( image?.props?.id, 10 )
|
||||
);
|
||||
} );
|
||||
recordEvent( 'product_images_change_image_order_via_image_gallery' );
|
||||
setValue( 'images', orderedImages );
|
||||
};
|
||||
const onFileUpload = ( files: MediaItem[] ) => {
|
||||
if ( files[ 0 ].id ) {
|
||||
recordEvent( 'product_images_add_via_file_upload_area' );
|
||||
setValue( 'images', [ ...images, ...files ] );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
title={ __( 'Images', 'woocommerce' ) }
|
||||
description={
|
||||
<>
|
||||
<span>
|
||||
{ __(
|
||||
'For best results, use JPEG files that are 1000 by 1000 pixels or larger.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<Link
|
||||
className="woocommerce-form-section__header-link"
|
||||
href="https://woocommerce.com/posts/fast-high-quality-product-photos/"
|
||||
target="_blank"
|
||||
type="external"
|
||||
onClick={ () => {
|
||||
recordEvent( 'prepare_images_help' );
|
||||
} }
|
||||
>
|
||||
{ __( 'How should I prepare images?', 'woocommerce' ) }
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
className={ classnames( 'woocommerce-product-form__images', {
|
||||
'has-images': images.length > 0,
|
||||
} ) }
|
||||
>
|
||||
<CardBody>
|
||||
<ImageGallery
|
||||
onDragStart={ ( event ) => {
|
||||
const { id: imageId, dataset } =
|
||||
event.target as HTMLElement;
|
||||
if ( imageId ) {
|
||||
setDraggedImageId( parseInt( imageId, 10 ) );
|
||||
} else {
|
||||
const index = dataset?.index;
|
||||
if ( index ) {
|
||||
setDraggedImageId(
|
||||
images[ parseInt( index, 10 ) ]?.id
|
||||
);
|
||||
}
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onDragEnd={ () => {
|
||||
if ( isRemoving && draggedImageId ) {
|
||||
recordEvent(
|
||||
'product_images_remove_image_button_click'
|
||||
);
|
||||
setValue(
|
||||
'images',
|
||||
images.filter(
|
||||
( img ) => img.id !== draggedImageId
|
||||
)
|
||||
);
|
||||
setIsRemoving( false );
|
||||
setDraggedImageId( null );
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onOrderChange={ orderImages }
|
||||
onReplace={ ( { replaceIndex, media } ) => {
|
||||
if (
|
||||
images.find(
|
||||
( img ) => media.id === img.id
|
||||
) === undefined
|
||||
) {
|
||||
images[ replaceIndex ] = media as Image;
|
||||
recordEvent(
|
||||
'product_images_replace_image_button_click'
|
||||
);
|
||||
setValue( 'images', images );
|
||||
}
|
||||
} }
|
||||
onSelectAsCover={ () =>
|
||||
recordEvent(
|
||||
'product_images_select_image_as_cover_button_click'
|
||||
)
|
||||
}
|
||||
>
|
||||
{ images.map( ( image ) => (
|
||||
<ImageGalleryItem
|
||||
key={ image.id || image.url }
|
||||
alt={ image.alt }
|
||||
src={ image.url || image.src }
|
||||
id={ `${ image.id }` }
|
||||
/>
|
||||
) ) }
|
||||
</ImageGallery>
|
||||
<div className="woocommerce-product-form__image-drop-zone">
|
||||
{ isRemovingZoneVisible ? (
|
||||
<CardBody>
|
||||
<div className="woocommerce-product-form__remove-image-drop-zone">
|
||||
<span>
|
||||
<Icon
|
||||
icon={ trash }
|
||||
size={ 20 }
|
||||
className="icon-control"
|
||||
/>
|
||||
{ __(
|
||||
'Drop here to remove',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<DropZone
|
||||
onHTMLDrop={ () =>
|
||||
setIsRemoving( true )
|
||||
}
|
||||
onDrop={ () => setIsRemoving( true ) }
|
||||
label={ __(
|
||||
'Drop here to remove',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
) : (
|
||||
<CardBody>
|
||||
<MediaUploader
|
||||
multipleSelect={ true }
|
||||
onError={ () => null }
|
||||
onFileUploadChange={ onFileUpload }
|
||||
onSelect={ ( files ) => {
|
||||
const newImages = files.filter(
|
||||
( img: Image ) =>
|
||||
! images.find(
|
||||
( image ) =>
|
||||
image.id === img.id
|
||||
)
|
||||
);
|
||||
if ( newImages.length > 0 ) {
|
||||
recordEvent(
|
||||
'product_images_add_via_media_library'
|
||||
);
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
...newImages,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
onUpload={ ( files ) => {
|
||||
if ( files[ 0 ].id ) {
|
||||
recordEvent(
|
||||
'product_images_add_via_drag_and_drop_upload'
|
||||
);
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
...files,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
label={
|
||||
<>
|
||||
<img
|
||||
src={ DragAndDrop }
|
||||
alt={ __(
|
||||
'Completed',
|
||||
'woocommerce'
|
||||
) }
|
||||
className="woocommerce-product-form__drag-and-drop-image"
|
||||
/>
|
||||
<span>
|
||||
{ __(
|
||||
'Drag images here or click to upload',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</CardBody>
|
||||
) }
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</ProductSectionLayout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Using slotfill to insert images section in product editor.
|
Loading…
Reference in New Issue