Merged in feature/from-pantheon (pull request #16)
code from pantheon * code from pantheon
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
export * from './inner-block-layout-context';
|
||||
export * from './product-data-context';
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createContext, useContext } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* This context is a configuration object used for connecting
|
||||
* all children blocks in a given tree contained in the context with information
|
||||
* about the parent block. Typically this is used for extensibility features.
|
||||
*
|
||||
* @member {Object} InnerBlockLayoutContext A react context object
|
||||
*/
|
||||
const InnerBlockLayoutContext = createContext( {
|
||||
parentName: '',
|
||||
parentClassName: '',
|
||||
isLoading: false,
|
||||
} );
|
||||
|
||||
export const useInnerBlockLayoutContext = () =>
|
||||
useContext( InnerBlockLayoutContext );
|
||||
|
||||
interface InnerBlockLayoutContextProviderProps {
|
||||
parentName?: string;
|
||||
parentClassName?: string;
|
||||
isLoading?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const InnerBlockLayoutContextProvider = ( {
|
||||
parentName = '',
|
||||
parentClassName = '',
|
||||
isLoading = false,
|
||||
children,
|
||||
}: InnerBlockLayoutContextProviderProps ) => {
|
||||
const contextValue = {
|
||||
parentName,
|
||||
parentClassName,
|
||||
isLoading,
|
||||
};
|
||||
return (
|
||||
<InnerBlockLayoutContext.Provider value={ contextValue }>
|
||||
{ children }
|
||||
</InnerBlockLayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ProductResponseItem } from '@woocommerce/types';
|
||||
import { createContext, useContext } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Default product shape matching API response.
|
||||
*/
|
||||
const defaultProductData: ProductResponseItem = {
|
||||
id: 0,
|
||||
name: '',
|
||||
parent: 0,
|
||||
type: 'simple',
|
||||
variation: '',
|
||||
permalink: '',
|
||||
sku: '',
|
||||
short_description: '',
|
||||
description: '',
|
||||
on_sale: false,
|
||||
prices: {
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
price: '0',
|
||||
regular_price: '0',
|
||||
sale_price: '0',
|
||||
price_range: null,
|
||||
},
|
||||
price_html: '',
|
||||
average_rating: '0',
|
||||
review_count: 0,
|
||||
images: [],
|
||||
categories: [],
|
||||
tags: [],
|
||||
attributes: [],
|
||||
variations: [],
|
||||
has_options: false,
|
||||
is_purchasable: false,
|
||||
is_in_stock: false,
|
||||
is_on_backorder: false,
|
||||
low_stock_remaining: null,
|
||||
sold_individually: false,
|
||||
add_to_cart: {
|
||||
text: 'Add to cart',
|
||||
description: 'Add to cart',
|
||||
url: '',
|
||||
minimum: 1,
|
||||
maximum: 99,
|
||||
multiple_of: 1,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This context is used to pass product data down to all children blocks in a given tree.
|
||||
*
|
||||
* @member {Object} ProductDataContext A react context object
|
||||
*/
|
||||
const ProductDataContext = createContext( {
|
||||
product: defaultProductData,
|
||||
hasContext: false,
|
||||
isLoading: false,
|
||||
} );
|
||||
|
||||
export const useProductDataContext = () => useContext( ProductDataContext );
|
||||
|
||||
interface ProductDataContextProviderProps {
|
||||
product: ProductResponseItem | null;
|
||||
children: JSX.Element | JSX.Element[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This context is used to pass product data down to all children blocks in a given tree.
|
||||
*
|
||||
* @param {Object} object A react context object
|
||||
* @param {any|null} object.product The product data to be passed down
|
||||
* @param {Object} object.children The product data to be passed down
|
||||
* @param {boolean} object.isLoading The product data to be passed down
|
||||
*/
|
||||
export const ProductDataContextProvider = ( {
|
||||
product = null,
|
||||
children,
|
||||
isLoading,
|
||||
}: ProductDataContextProviderProps ) => {
|
||||
const contextValue = {
|
||||
product: product || defaultProductData,
|
||||
isLoading,
|
||||
hasContext: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductDataContext.Provider value={ contextValue }>
|
||||
{ isLoading ? (
|
||||
<div className="is-loading">{ children }</div>
|
||||
) : (
|
||||
children
|
||||
) }
|
||||
</ProductDataContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './with-product-data-context';
|
||||
export * from './with-filtered-attributes';
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getValidBlockAttributes } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* HOC that filters given attributes by valid block attribute values, or uses defaults if undefined.
|
||||
*
|
||||
* @param {Object} blockAttributes Component being wrapped.
|
||||
*/
|
||||
export const withFilteredAttributes =
|
||||
( blockAttributes ) => ( OriginalComponent ) => {
|
||||
return ( ownProps ) => {
|
||||
const validBlockAttributes = getValidBlockAttributes(
|
||||
blockAttributes,
|
||||
ownProps
|
||||
);
|
||||
|
||||
return (
|
||||
<OriginalComponent
|
||||
{ ...ownProps }
|
||||
{ ...validBlockAttributes }
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useStoreProducts } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
ProductDataContextProvider,
|
||||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
const getProductById = ( products, id ) =>
|
||||
products.find( ( product ) => product.id === id );
|
||||
|
||||
/**
|
||||
* Loads the product from the API and adds to the context provider.
|
||||
*
|
||||
* @param {Object} props Component props.
|
||||
*/
|
||||
const OriginalComponentWithContext = ( props ) => {
|
||||
const { productId, OriginalComponent, postId, product } = props;
|
||||
|
||||
const id = props?.isDescendentOfQueryLoop ? postId : productId;
|
||||
|
||||
const { products, productsLoading } = useStoreProducts( {
|
||||
include: id,
|
||||
} );
|
||||
|
||||
const productFromAPI = {
|
||||
product:
|
||||
id > 0 && products.length > 0
|
||||
? getProductById( products, id )
|
||||
: null,
|
||||
isLoading: productsLoading,
|
||||
};
|
||||
|
||||
if ( product ) {
|
||||
return (
|
||||
<ProductDataContextProvider product={ product } isLoading={ false }>
|
||||
<OriginalComponent { ...props } />
|
||||
</ProductDataContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProductDataContextProvider
|
||||
product={ productFromAPI.product }
|
||||
isLoading={ productFromAPI.isLoading }
|
||||
>
|
||||
<OriginalComponent { ...props } />
|
||||
</ProductDataContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This HOC sees if the Block is wrapped in Product Data Context, and if not, wraps it with context
|
||||
* based on the productId attribute, if set.
|
||||
*
|
||||
* @param {Function} OriginalComponent Component being wrapped.
|
||||
*/
|
||||
export const withProductDataContext = ( OriginalComponent ) => {
|
||||
return ( props ) => {
|
||||
const productDataContext = useProductDataContext();
|
||||
|
||||
// If a product prop was provided, use this as the context for the tree.
|
||||
if ( !! props.product || ! productDataContext.hasContext ) {
|
||||
return (
|
||||
<OriginalComponentWithContext
|
||||
{ ...props }
|
||||
OriginalComponent={ OriginalComponent }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <OriginalComponent { ...props } />;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user