This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Single Product Block #2399
Closed
Closed
Single Product Block #2399
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
f9f00a5
Create placeholder code for single product block
mikejolley 39588eb
Edit mode
mikejolley 8853f01
Put block behind experimental flag
mikejolley ed59e30
Share atomic blocks with all products
mikejolley 1d2694c
frontend render
mikejolley 117b378
Fix notice in admin
mikejolley 28b42e3
Update string
mikejolley bdf053a
Script behind flag
mikejolley 2d3b072
conflict
mikejolley a515893
FIx height of control items
mikejolley 6c3ed98
Basic insertion test
mikejolley beff720
Restore icon
mikejolley e971850
withBlockErrorBoundary HOC
mikejolley a257cdf
Consolodate contexts
mikejolley 4ef09cf
Add styles
mikejolley 8e9e22c
getAllowedInnerBlocks util
mikejolley 16c0802
getAttributesFromDataset support for arrays and objects
mikejolley 518a5fb
useSyncedLayoutConfig hook
mikejolley c3e9dff
Split up edit controls
mikejolley f035b35
Edit layout progress
mikejolley 41b08a1
Add core columns in block map
mikejolley 261f3a0
Add specific props to context provider
mikejolley 3e5ba21
Revert BlockErrorBoundary changes
mikejolley 2709449
introduce a new shared contexts build
nerrad 446d05d
Clean up atomic imports
mikejolley 96d19eb
Atomic component context support
mikejolley f5ebad7
update jest config
mikejolley 956f691
[Experiment] Alternative layout rendering (#2542)
mikejolley 6150303
Fix import
mikejolley b9280f8
Refactor components for attribute support
mikejolley a2121de
Remove readme
mikejolley da55665
Move back old components files
mikejolley 88b1893
Mv files
mikejolley 995b931
Revert "Mv files"
mikejolley 3d46cd6
mv files again
mikejolley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './product'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import PropTypes from 'prop-types'; | ||
import classnames from 'classnames'; | ||
import { __, _n, sprintf } from '@wordpress/i18n'; | ||
import { useEffect, useRef } from '@wordpress/element'; | ||
import { useStoreAddToCart } from '@woocommerce/base-hooks'; | ||
import { | ||
useInnerBlockConfigurationContext, | ||
useProductDataContextContext, | ||
} from '@woocommerce/shared-context'; | ||
import { decodeEntities } from '@wordpress/html-entities'; | ||
import { triggerFragmentRefresh } from '@woocommerce/base-utils'; | ||
|
||
const ProductButton = ( { className } ) => { | ||
const { product } = useProductDataContextContext(); | ||
const { layoutStyleClassPrefix } = useInnerBlockConfigurationContext(); | ||
const componentClass = `${ layoutStyleClassPrefix }__product-add-to-cart`; | ||
|
||
return ( | ||
<div | ||
className={ classnames( | ||
className, | ||
componentClass, | ||
'wp-block-button', | ||
{ | ||
'is-loading': ! product, | ||
} | ||
) } | ||
> | ||
{ product ? ( | ||
<AddToCartButton product={ product } /> | ||
) : ( | ||
<AddToCartButtonPlaceholder /> | ||
) } | ||
</div> | ||
); | ||
}; | ||
|
||
const AddToCartButton = ( { product } ) => { | ||
const firstMount = useRef( true ); | ||
|
||
const { | ||
id, | ||
permalink, | ||
add_to_cart: productCartDetails, | ||
has_options: hasOptions, | ||
is_purchasable: isPurchasable, | ||
is_in_stock: isInStock, | ||
} = product; | ||
|
||
const { | ||
cartQuantity, | ||
addingToCart, | ||
cartIsLoading, | ||
addToCart, | ||
} = useStoreAddToCart( id ); | ||
|
||
useEffect( () => { | ||
// Avoid running on first mount when cart quantity is first set. | ||
if ( firstMount.current ) { | ||
firstMount.current = false; | ||
return; | ||
} | ||
triggerFragmentRefresh(); | ||
}, [ cartQuantity ] ); | ||
|
||
if ( cartIsLoading ) { | ||
return <AddToCartButtonPlaceholder />; | ||
} | ||
|
||
const addedToCart = Number.isFinite( cartQuantity ) && cartQuantity > 0; | ||
const allowAddToCart = ! hasOptions && isPurchasable && isInStock; | ||
const buttonAriaLabel = decodeEntities( | ||
productCartDetails?.description || '' | ||
); | ||
const buttonText = addedToCart | ||
? sprintf( | ||
// translators: %s number of products in cart. | ||
_n( | ||
'%d in cart', | ||
'%d in cart', | ||
cartQuantity, | ||
'woo-gutenberg-products-block' | ||
), | ||
cartQuantity | ||
) | ||
: decodeEntities( | ||
productCartDetails?.text || | ||
__( 'Add to cart', 'woo-gutenberg-products-block' ) | ||
); | ||
|
||
if ( ! allowAddToCart ) { | ||
return ( | ||
<a | ||
href={ permalink } | ||
aria-label={ buttonAriaLabel } | ||
className={ classnames( | ||
'wp-block-button__link', | ||
'add_to_cart_button', | ||
{ | ||
loading: addingToCart, | ||
added: addedToCart, | ||
} | ||
) } | ||
rel="nofollow" | ||
> | ||
{ buttonText } | ||
</a> | ||
); | ||
} | ||
|
||
return ( | ||
<button | ||
onClick={ addToCart } | ||
aria-label={ buttonAriaLabel } | ||
className={ classnames( | ||
'wp-block-button__link', | ||
'add_to_cart_button', | ||
{ | ||
loading: addingToCart, | ||
added: addedToCart, | ||
} | ||
) } | ||
disabled={ addingToCart } | ||
> | ||
{ buttonText } | ||
</button> | ||
); | ||
}; | ||
|
||
const AddToCartButtonPlaceholder = () => { | ||
return ( | ||
<button | ||
className={ classnames( | ||
'wp-block-button__link', | ||
'add_to_cart_button' | ||
) } | ||
disabled={ true } | ||
/> | ||
); | ||
}; | ||
|
||
ProductButton.propTypes = { | ||
className: PropTypes.string, | ||
product: PropTypes.object, | ||
}; | ||
|
||
export default ProductButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { Disabled } from '@wordpress/components'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import Block from './block'; | ||
|
||
export default () => { | ||
return ( | ||
<Disabled> | ||
<Block /> | ||
</Disabled> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import Block from './block'; | ||
|
||
/** | ||
* Wrapper component used on the frontend. | ||
*/ | ||
const FrontendBlock = () => { | ||
return <Block />; | ||
}; | ||
|
||
export default FrontendBlock; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
export const blockAttributes = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defining these separately so the frontend.js can validate attributes against them. |
||
productLink: { | ||
type: 'boolean', | ||
default: true, | ||
}, | ||
showSaleBadge: { | ||
type: 'boolean', | ||
default: true, | ||
}, | ||
saleBadgeAlign: { | ||
type: 'string', | ||
default: 'right', | ||
}, | ||
}; | ||
|
||
export default blockAttributes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import PropTypes from 'prop-types'; | ||
import { useState } from '@wordpress/element'; | ||
import classnames from 'classnames'; | ||
import { PLACEHOLDER_IMG_SRC } from '@woocommerce/block-settings'; | ||
import { | ||
useInnerBlockConfigurationContext, | ||
useProductDataContextContext, | ||
} from '@woocommerce/shared-context'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import ProductSaleBadge from '../sale-badge/block.js'; | ||
|
||
const ProductImage = ( { | ||
className, | ||
productLink = true, | ||
showSaleBadge = true, | ||
saleBadgeAlign = 'right', | ||
} ) => { | ||
const { product } = useProductDataContextContext(); | ||
const { layoutStyleClassPrefix } = useInnerBlockConfigurationContext(); | ||
const componentClass = `${ layoutStyleClassPrefix }__product-image`; | ||
const [ imageLoaded, setImageLoaded ] = useState( false ); | ||
|
||
if ( ! product ) { | ||
return ( | ||
<div | ||
className={ classnames( | ||
className, | ||
componentClass, | ||
'is-loading' | ||
) } | ||
> | ||
<ImagePlaceholder componentClass={ componentClass } /> | ||
</div> | ||
); | ||
} | ||
|
||
const image = | ||
product?.images && product.images.length ? product.images[ 0 ] : null; | ||
|
||
return ( | ||
<div className={ classnames( className, componentClass ) }> | ||
{ productLink ? ( | ||
<a href={ product.permalink } rel="nofollow"> | ||
{ showSaleBadge && ( | ||
<ProductSaleBadge align={ saleBadgeAlign } /> | ||
) } | ||
<Image | ||
componentClass={ componentClass } | ||
image={ image } | ||
onLoad={ () => setImageLoaded( true ) } | ||
loaded={ imageLoaded } | ||
/> | ||
</a> | ||
) : ( | ||
<> | ||
{ showSaleBadge && ( | ||
<ProductSaleBadge align={ saleBadgeAlign } /> | ||
) } | ||
<Image | ||
componentClass={ componentClass } | ||
image={ image } | ||
onLoad={ () => setImageLoaded( true ) } | ||
loaded={ imageLoaded } | ||
/> | ||
</> | ||
) } | ||
</div> | ||
); | ||
}; | ||
|
||
const ImagePlaceholder = ( { componentClass } ) => { | ||
return ( | ||
<img | ||
className={ classnames( | ||
`${ componentClass }__image`, | ||
`${ componentClass }__image_placeholder` | ||
) } | ||
src={ PLACEHOLDER_IMG_SRC } | ||
alt="" | ||
/> | ||
); | ||
}; | ||
|
||
const Image = ( { componentClass, image, onLoad, loaded } ) => { | ||
const { thumbnail, srcset, sizes, alt } = image || {}; | ||
|
||
return ( | ||
<> | ||
<img | ||
className={ classnames( `${ componentClass }__image` ) } | ||
src={ thumbnail } | ||
srcSet={ srcset } | ||
sizes={ sizes } | ||
alt={ alt } | ||
onLoad={ onLoad } | ||
hidden={ ! loaded } | ||
/> | ||
{ ! loaded && ( | ||
<ImagePlaceholder componentClass={ componentClass } /> | ||
) } | ||
</> | ||
); | ||
}; | ||
|
||
ProductImage.propTypes = { | ||
className: PropTypes.string, | ||
product: PropTypes.object, | ||
productLink: PropTypes.bool, | ||
showSaleBadge: PropTypes.bool, | ||
saleBadgeAlign: PropTypes.string, | ||
}; | ||
|
||
export default ProductImage; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved contents of Atomic Components to a block.js per atomic block. This is so we can reuse the same block for edit.js and frontend.js, and also allows frontend.js to see the defined blockAttributes so it can map dataset to block attributes correctly.