/* eslint-disable @wordpress/no-unsafe-wp-apis */ /** * External dependencies */ import type { BlockAlignment } from '@wordpress/blocks'; import { ProductResponseItem, isEmpty } from '@woocommerce/types'; import { Icon, Placeholder, Spinner } from '@wordpress/components'; import classnames from 'classnames'; import { useCallback, useState } from '@wordpress/element'; import { WP_REST_API_Category } from 'wp-types'; import { useStyleProps } from '@woocommerce/base-hooks'; import type { ComponentType, Dispatch, SetStateAction } from 'react'; /** * Internal dependencies */ import { CallToAction } from './call-to-action'; import { ConstrainedResizable } from './constrained-resizable'; import { EditorBlock, GenericBlockUIConfig } from './types'; import { useBackgroundImage } from './use-background-image'; import { dimRatioToClass, getBackgroundImageStyles, getClassPrefixFromName, } from './utils'; interface WithFeaturedItemConfig extends GenericBlockUIConfig { emptyMessage: string; noSelectionButtonLabel: string; } export interface FeaturedItemRequiredAttributes { contentAlign: BlockAlignment; dimRatio: number; focalPoint: { x: number; y: number }; hasParallax: boolean; imageFit: 'cover' | 'none'; isRepeated: boolean; linkText: string; mediaId: number; mediaSrc: string; minHeight: number; overlayColor: string; overlayGradient: string; showDesc: boolean; showPrice: boolean; editMode: boolean; } interface FeaturedCategoryRequiredAttributes extends FeaturedItemRequiredAttributes { categoryId: number | 'preview'; productId: never; } interface FeaturedProductRequiredAttributes extends FeaturedItemRequiredAttributes { categoryId: never; productId: number | 'preview'; } interface FeaturedItemRequiredProps< T > { attributes: ( | FeaturedCategoryRequiredAttributes | FeaturedProductRequiredAttributes ) & EditorBlock< T >[ 'attributes' ] & { // This is hardcoded because border and color are not yet included // in Gutenberg's official types. style: { border?: { radius?: number }; color?: { text?: string }; }; textColor?: string; }; isLoading: boolean; setAttributes: ( attrs: Partial< FeaturedItemRequiredAttributes > ) => void; useEditingImage: [ boolean, Dispatch< SetStateAction< boolean > > ]; } interface FeaturedCategoryProps< T > extends FeaturedItemRequiredProps< T > { category: WP_REST_API_Category; product: never; } interface FeaturedProductProps< T > extends FeaturedItemRequiredProps< T > { category: never; product: ProductResponseItem; } type FeaturedItemProps< T extends EditorBlock< T > > = | ( T & FeaturedCategoryProps< T > ) | ( T & FeaturedProductProps< T > ); export const withFeaturedItem = ( { emptyMessage, icon, label, noSelectionButtonLabel, }: WithFeaturedItemConfig ) => < T extends EditorBlock< T > >( Component: ComponentType< T > ) => ( props: FeaturedItemProps< T > ) => { const [ isEditingImage ] = props.useEditingImage; const { attributes, category, isLoading, isSelected, name, product, setAttributes, } = props; const { mediaId, mediaSrc } = attributes; const item = category || product; const [ backgroundImageSize, setBackgroundImageSize ] = useState( {} ); const { backgroundImageSrc } = useBackgroundImage( { item, mediaId, mediaSrc, blockName: name, } ); const className = getClassPrefixFromName( name ); const onResize = useCallback( ( _event, _direction, elt ) => { setAttributes( { minHeight: parseInt( elt.style.height, 10 ), } ); }, [ setAttributes ] ); const renderButton = () => { const { categoryId, linkText, productId } = attributes; return ( ); }; const renderNoItemButton = () => { return ( <>

{ emptyMessage }

); }; const renderNoItem = () => ( } label={ label } > { isLoading ? : renderNoItemButton() } ); const styleProps = useStyleProps( attributes ); const renderItem = () => { const { contentAlign, dimRatio, focalPoint, hasParallax, isRepeated, imageFit, minHeight, overlayColor, overlayGradient, showDesc, showPrice, style, textColor, } = attributes; const containerClass = classnames( className, { 'is-selected': isSelected && attributes.categoryId !== 'preview' && attributes.productId !== 'preview', 'is-loading': ! item && isLoading, 'is-not-found': ! item && ! isLoading, 'has-background-dim': dimRatio !== 0, 'is-repeated': isRepeated, }, dimRatioToClass( dimRatio ), contentAlign !== 'center' && `has-${ contentAlign }-content`, styleProps.className ); const containerStyle = { borderRadius: style?.border?.radius, color: textColor ? `var(--wp--preset--color--${ textColor })` : style?.color?.text, boxSizing: 'border-box', minHeight, ...styleProps.style, }; const isImgElement = ! isRepeated && ! hasParallax; const backgroundImageStyle = getBackgroundImageStyles( { focalPoint, imageFit, isImgElement, isRepeated, url: backgroundImageSrc, } ); const overlayStyle = { background: overlayGradient, backgroundColor: overlayColor, }; return ( <>
{ backgroundImageSrc && ( isImgElement ? ( { { setBackgroundImageSize( { height: e.currentTarget ?.naturalHeight, width: e.currentTarget ?.naturalWidth, } ); } } /> ) : (
) ) }

{ ! isEmpty( product?.variation ) && (

) } { showDesc && (
) } { showPrice && (
) }
{ renderButton() }
); }; if ( isEditingImage ) { return ( ); } return ( <> { item ? renderItem() : renderNoItem() } ); };