diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts
index b2cae177672..a92f80b861d 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts
@@ -1,15 +1,23 @@
-interface BlockAttributes {
- productId: {
- type: string;
- default: number;
- };
-}
+/**
+ * Internal dependencies
+ */
+import { BlockAttributes } from './types';
-export const blockAttributes: BlockAttributes = {
+export const blockAttributes: Record<
+ keyof BlockAttributes,
+ {
+ type: string;
+ default: unknown;
+ }
+> = {
productId: {
type: 'number',
default: 0,
},
+ isDescendentOfQueryLoop: {
+ type: 'boolean',
+ default: false,
+ },
};
export default blockAttributes;
diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx
index a27e2d88b2e..22f2ca99582 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx
@@ -3,6 +3,9 @@
*/
import EditProductLink from '@woocommerce/editor-components/edit-product-link';
import { useBlockProps } from '@wordpress/block-editor';
+import type { BlockEditProps } from '@wordpress/blocks';
+import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
+import { useEffect } from 'react';
/**
* Internal dependencies
@@ -16,16 +19,28 @@ import {
} from './constants';
import type { BlockAttributes } from './types';
-interface Props {
- attributes: BlockAttributes;
-}
-
-const Edit = ( { attributes }: Props ): JSX.Element => {
+const Edit = ( {
+ attributes,
+ setAttributes,
+ context,
+}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => {
const blockProps = useBlockProps();
+
+ const blockAttrs = {
+ ...attributes,
+ ...context,
+ };
+ const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
+
+ useEffect(
+ () => setAttributes( { isDescendentOfQueryLoop } ),
+ [ setAttributes, isDescendentOfQueryLoop ]
+ );
+
return (
-
+
);
};
diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts
index 213ea732545..8acb8ebba6e 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts
@@ -10,7 +10,6 @@ import type { BlockConfiguration } from '@wordpress/blocks';
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
-import { Save } from './save';
import { supports } from './supports';
import {
@@ -28,7 +27,12 @@ const blockConfig: BlockConfiguration = {
attributes,
supports,
edit,
- save: Save,
+ usesContext: [ 'query', 'queryId', 'postId' ],
+ ancestor: [
+ '@woocommerce/all-products',
+ '@woocommerce/single-product',
+ 'core/post-template',
+ ],
};
registerExperimentalBlockType( 'woocommerce/product-stock-indicator', {
diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts
index e64df6f3d4f..b695a3b5f5d 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts
@@ -1,3 +1,4 @@
export interface BlockAttributes {
productId: number;
+ isDescendentOfQueryLoop: boolean;
}
diff --git a/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php b/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php
index 33a894f6865..51ae70dc3fd 100644
--- a/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php
+++ b/plugins/woocommerce-blocks/src/BlockTypes/ProductStockIndicator.php
@@ -1,6 +1,8 @@
register_chunk_translations( [ $this->block_name ] );
+ return null;
+ }
+
+ /**
+ * Register the context.
+ */
+ protected function get_block_type_uses_context() {
+ return [ 'query', 'queryId', 'postId' ];
+ }
+
+ /**
+ * Get stock text based on stock. For example:
+ * - In stock
+ * - Out of stock
+ * - Available on backorder
+ * - 2 left in stock
+ *
+ * @param [bool] $is_in_stock Whether the product is in stock.
+ * @param [bool] $is_low_stock Whether the product is low in stock.
+ * @param [int|null] $low_stock_amount The amount of stock that is considered low.
+ * @param [bool] $is_on_backorder Whether the product is on backorder.
+ * @return string Stock text.
+ */
+ protected static function getTextBasedOnStock( $is_in_stock, $is_low_stock, $low_stock_amount, $is_on_backorder ) {
+ if ( $is_low_stock ) {
+ return sprintf(
+ /* translators: %d is number of items in stock for product */
+ __( '%d left in stock', 'woo-gutenberg-products-block' ),
+ $low_stock_amount
+ );
+ } elseif ( $is_on_backorder ) {
+ return __( 'Available on backorder', 'woo-gutenberg-products-block' );
+ } elseif ( $is_in_stock ) {
+ return __( 'In stock', 'woo-gutenberg-products-block' );
+ } else {
+ return __( 'Out of stock', 'woo-gutenberg-products-block' );
+ }
+ }
+
+
+
+ /**
+ * Include and render the block.
+ *
+ * @param array $attributes Block attributes. Default empty array.
+ * @param string $content Block content. Default empty string.
+ * @param WP_Block $block Block instance.
+ * @return string Rendered block type output.
+ */
+ protected function render( $attributes, $content, $block ) {
+ if ( ! empty( $content ) ) {
+ parent::register_block_type_assets();
+ $this->register_chunk_translations( [ $this->block_name ] );
+ return $content;
+ }
+
+ $post_id = $block->context['postId'];
+ $product = wc_get_product( $post_id );
+ $is_in_stock = $product->is_in_stock();
+ $is_on_backorder = $product->is_on_backorder();
+
+ $low_stock_amount = $product->get_low_stock_amount();
+ $total_stock = $product->get_stock_quantity();
+ $is_low_stock = $low_stock_amount && $total_stock <= $low_stock_amount;
+
+ $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
+
+ $classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : '';
+ $classnames .= isset( $attributes['className'] ) ? ' ' . $attributes['className'] . ' ' : '';
+ $classnames .= ! $is_in_stock ? ' wc-block-components-product-stock-indicator--out-of-stock ' : '';
+ $classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-stock ' : '';
+ $classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : '';
+ $classnames .= $is_on_backorder ? ' wc-block-components-product-stock-indicator--available-on-backorder ' : '';
+
+ $output = '';
+ $output .= '