From dd94bb78eed4abc73e1c96c917a7e5843eb7e28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 23 Dec 2022 15:28:44 -0300 Subject: [PATCH] Add product variation image (#36133) * Convert getCheckboxTracks into generic function because of a type mismatch * Add image to product variation and export types * Add single image field * Integrate SingleImageField in variation details section * Add changelog file * Add comment suggestions * Fix set image onFileUploadChange --- packages/js/data/changelog/add-36115 | 4 + packages/js/data/src/index.ts | 6 +- .../js/data/src/product-variations/types.ts | 44 ++++++- .../fields/single-image-field/index.ts | 1 + .../single-image-field.scss | 28 +++++ .../single-image-field/single-image-field.tsx | 107 ++++++++++++++++++ .../product-variation-details-section.tsx | 56 ++++++++- .../client/products/sections/utils.ts | 12 +- plugins/woocommerce/changelog/add-36115 | 4 + 9 files changed, 249 insertions(+), 13 deletions(-) create mode 100644 packages/js/data/changelog/add-36115 create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx create mode 100644 plugins/woocommerce/changelog/add-36115 diff --git a/packages/js/data/changelog/add-36115 b/packages/js/data/changelog/add-36115 new file mode 100644 index 00000000000..08103beb8f0 --- /dev/null +++ b/packages/js/data/changelog/add-36115 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add image to product variation and export types diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index f386b083731..8805b6dc794 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -77,7 +77,11 @@ export * from './countries/types'; export * from './onboarding/types'; export * from './plugins/types'; export * from './products/types'; -export { ProductVariation } from './product-variations/types'; +export type { + ProductVariation, + ProductVariationAttribute, + ProductVariationImage, +} from './product-variations/types'; export { QueryProductAttribute, ProductAttributeSelectors, diff --git a/packages/js/data/src/product-variations/types.ts b/packages/js/data/src/product-variations/types.ts index efd9df6b37c..5275ec24961 100644 --- a/packages/js/data/src/product-variations/types.ts +++ b/packages/js/data/src/product-variations/types.ts @@ -15,11 +15,53 @@ export type ProductVariationAttribute = { option: string; }; +/** + * Product variation - Image properties + */ +export interface ProductVariationImage { + /** + * Image ID. + */ + id: number; + /** + * The date the image was created, in the site's timezone. + */ + readonly date_created: string; + /** + * The date the image was created, as GMT. + */ + readonly date_created_gmt: string; + /** + * The date the image was last modified, in the site's timezone. + */ + readonly date_modified: string; + /** + * The date the image was last modified, as GMT. + */ + readonly date_modified_gmt: string; + /** + * Image URL. + */ + src: string; + /** + * Image name. + */ + name: string; + /** + * Image alternative text. + */ + alt: string; +} + export type ProductVariation = Omit< Product, - 'name' | 'slug' | 'attributes' + 'name' | 'slug' | 'attributes' | 'images' > & { attributes: ProductVariationAttribute[]; + /** + * Variation image data. + */ + image?: ProductVariationImage; }; type Query = Omit< ProductQuery, 'name' >; diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts b/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts new file mode 100644 index 00000000000..b3430e285c3 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts @@ -0,0 +1 @@ +export * from './single-image-field'; diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss new file mode 100644 index 00000000000..75eecca08fa --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss @@ -0,0 +1,28 @@ +.woocommerce-single-image-field { + &__gallery { + margin-top: $gap-smaller; + + .woocommerce-image-gallery .woocommerce-sortable { + margin: 0; + } + } + + &__drop-zone { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: calc($gap * 2) 0; + margin-top: $gap-smaller; + gap: calc($gap * 2); + isolation: isolate; + + min-height: 144px; + + background: $white; + border: 1px dashed $gray-700; + border-radius: 2px; + + position: relative; + } +} diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx new file mode 100644 index 00000000000..77f31422bcc --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + MediaUploader, + ImageGallery, + ImageGalleryItem, +} from '@woocommerce/components'; +import { MediaItem } from '@wordpress/media-utils'; +import uniqueId from 'lodash/uniqueId'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import './single-image-field.scss'; + +export function SingleImageField( { + id, + label, + value, + className, + onChange, + ...props +}: SingleImageFieldProps ) { + const fieldId = id ?? uniqueId(); + + function handleChange( image?: MediaItem ) { + if ( typeof onChange === 'function' ) { + onChange( image ); + } + } + + return ( +
+ + + { value ? ( +
+ handleChange( media ) } + onRemove={ () => handleChange( undefined ) } + > + + +
+ ) : ( +
+ null } + onSelect={ ( image ) => + handleChange( image as MediaItem ) + } + onUpload={ ( [ image ] ) => handleChange( image ) } + onFileUploadChange={ ( [ image ] ) => + handleChange( image ) + } + label={ __( + 'Drag image here or click to upload', + 'woocommerce' + ) } + buttonText={ __( 'Choose image', 'woocommerce' ) } + /> +
+ ) } +
+ ); +} + +export type SingleImageFieldProps = Omit< + React.DetailedHTMLProps< + React.HTMLAttributes< HTMLDivElement >, + HTMLDivElement + >, + 'onChange' +> & { + label: string; + value?: MediaItem; + onChange?( value?: MediaItem ): void; +}; diff --git a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx index d67ede8e144..c92c265c1f7 100644 --- a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx +++ b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx @@ -3,13 +3,19 @@ */ import { __ } from '@wordpress/i18n'; import { BlockInstance, serialize, parse } from '@wordpress/blocks'; -import { CheckboxControl, Card, CardBody } from '@wordpress/components'; +import { + CheckboxControl, + Card, + CardBody, + BaseControl, +} from '@wordpress/components'; +import { MediaItem } from '@wordpress/media-utils'; import { useFormContext, __experimentalRichTextEditor as RichTextEditor, __experimentalTooltip as Tooltip, } from '@woocommerce/components'; -import { ProductVariation } from '@woocommerce/data'; +import { ProductVariation, ProductVariationImage } from '@woocommerce/data'; import { useState } from '@wordpress/element'; /** @@ -17,15 +23,42 @@ import { useState } from '@wordpress/element'; */ import { getCheckboxTracks } from './utils'; import { ProductSectionLayout } from '../layout/product-section-layout'; +import { SingleImageField } from '../fields/single-image-field'; + +function parseVariationImage( + media?: MediaItem +): ProductVariationImage | undefined { + if ( ! media ) return undefined; + return { + id: media.id, + src: media.url, + alt: media.alt, + name: media.title, + } as ProductVariationImage; +} + +function formatVariationImage( + image?: ProductVariationImage +): MediaItem | undefined { + if ( ! image ) return undefined; + return { + id: image.id, + url: image.src, + alt: image.alt, + title: image.name, + } as MediaItem; +} export const ProductVariationDetailsSection: React.FC = () => { - const { getCheckboxControlProps, values, setValue } = + const { getCheckboxControlProps, getInputProps, values, setValue } = useFormContext< ProductVariation >(); const [ descriptionBlocks, setDescriptionBlocks ] = useState< BlockInstance[] >( parse( values.description || '' ) ); + const imageFieldProps = getInputProps( 'image' ); + return ( { } { ...getCheckboxControlProps( 'status', - getCheckboxTracks( 'status' ) + getCheckboxTracks< ProductVariation >( 'status' ) ) } checked={ values.status === 'publish' } onChange={ () => @@ -77,6 +110,21 @@ export const ProductVariationDetailsSection: React.FC = () => { 'woocommerce' ) } /> + + + + setValue( + 'image', + parseVariationImage( media ) + ) + } + /> + diff --git a/plugins/woocommerce-admin/client/products/sections/utils.ts b/plugins/woocommerce-admin/client/products/sections/utils.ts index d2e6e418355..ab56552bcf6 100644 --- a/plugins/woocommerce-admin/client/products/sections/utils.ts +++ b/plugins/woocommerce-admin/client/products/sections/utils.ts @@ -23,22 +23,20 @@ type CurrencyConfig = { /** * Get additional props to be passed to all checkbox inputs. * - * @param {string} name Name of the checkbox - * @return {Object} Props. + * @param name Name of the checkbox. + * @return Props. */ -export const getCheckboxTracks = ( name: string ) => { +export function getCheckboxTracks< T = Product >( name: string ) { return { onChange: ( - isChecked: - | ChangeEvent< HTMLInputElement > - | Product[ keyof Product ] + isChecked: ChangeEvent< HTMLInputElement > | T[ keyof T ] ) => { recordEvent( `product_checkbox_${ name }`, { checked: isChecked, } ); }, }; -}; +} /** * Get input props for currency related values and symbol positions. diff --git a/plugins/woocommerce/changelog/add-36115 b/plugins/woocommerce/changelog/add-36115 new file mode 100644 index 00000000000..094918475cd --- /dev/null +++ b/plugins/woocommerce/changelog/add-36115 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation image