diff --git a/assets/js/atomic/blocks/product-elements/price/attributes.js b/assets/js/atomic/blocks/product-elements/price/attributes.ts similarity index 68% rename from assets/js/atomic/blocks/product-elements/price/attributes.js rename to assets/js/atomic/blocks/product-elements/price/attributes.ts index 50d3aa92270..d52d8097b47 100644 --- a/assets/js/atomic/blocks/product-elements/price/attributes.js +++ b/assets/js/atomic/blocks/product-elements/price/attributes.ts @@ -1,8 +1,9 @@ /** * External dependencies */ +import { BlockAttributes } from '@wordpress/blocks'; -export const blockAttributes = { +export const blockAttributes: BlockAttributes = { productId: { type: 'number', default: 0, diff --git a/assets/js/atomic/blocks/product-elements/price/block.js b/assets/js/atomic/blocks/product-elements/price/block.tsx similarity index 75% rename from assets/js/atomic/blocks/product-elements/price/block.js rename to assets/js/atomic/blocks/product-elements/price/block.tsx index 81734f352c5..c742adb62a3 100644 --- a/assets/js/atomic/blocks/product-elements/price/block.js +++ b/assets/js/atomic/blocks/product-elements/price/block.tsx @@ -1,7 +1,6 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import classnames from 'classnames'; import ProductPrice from '@woocommerce/base-components/product-price'; import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; @@ -11,22 +10,32 @@ import { } from '@woocommerce/shared-context'; import { useColorProps, useTypographyProps } from '@woocommerce/base-hooks'; import { withProductDataContext } from '@woocommerce/shared-hocs'; +import type { HTMLAttributes } from 'react'; +import { CurrencyCode } from '@woocommerce/type-defs/currency'; /** * Internal dependencies */ +import type { BlockAttributes } from './types'; import './style.scss'; -/** - * Product Price Block Component. - * - * @param {Object} props Incoming props. - * @param {string} [props.className] CSS Class name for the component. - * @param {string} [props.textAlign] Text alignment. - * context will be used if this is not provided. - * @return {*} The component. - */ -export const Block = ( props ) => { +type Props = BlockAttributes & HTMLAttributes< HTMLDivElement >; + +interface PriceProps { + currency_code: CurrencyCode; + currency_symbol: string; + currency_minor_unit: number; + currency_decimal_separator: string; + currency_thousand_separator: string; + currency_prefix: string; + currency_suffix: string; + price: string; + regular_price: string; + sale_price: string; + price_range: null | { min_amount: string; max_amount: string }; +} + +export const Block = ( props: Props ): JSX.Element | null => { const { className, textAlign } = props; const { parentClassName } = useInnerBlockLayoutContext(); const { product } = useProductDataContext(); @@ -54,7 +63,7 @@ export const Block = ( props ) => { ); } - const prices = product.prices; + const prices: PriceProps = product.prices; const currency = getCurrencyFromPriceResponse( prices ); const isOnSale = prices.price !== prices.regular_price; const priceClassName = classnames( { @@ -84,10 +93,4 @@ export const Block = ( props ) => { ); }; -Block.propTypes = { - className: PropTypes.string, - product: PropTypes.object, - textAlign: PropTypes.oneOf( [ 'left', 'right', 'center' ] ), -}; - export default withProductDataContext( Block ); diff --git a/assets/js/atomic/blocks/product-elements/price/constants.js b/assets/js/atomic/blocks/product-elements/price/constants.tsx similarity index 73% rename from assets/js/atomic/blocks/product-elements/price/constants.js rename to assets/js/atomic/blocks/product-elements/price/constants.tsx index e61f4701306..544becb88aa 100644 --- a/assets/js/atomic/blocks/product-elements/price/constants.js +++ b/assets/js/atomic/blocks/product-elements/price/constants.tsx @@ -4,17 +4,17 @@ import { __ } from '@wordpress/i18n'; import { currencyDollar, Icon } from '@wordpress/icons'; -export const BLOCK_TITLE = __( +export const BLOCK_TITLE: string = __( 'Product Price', 'woo-gutenberg-products-block' ); -export const BLOCK_ICON = ( +export const BLOCK_ICON: JSX.Element = ( ); -export const BLOCK_DESCRIPTION = __( +export const BLOCK_DESCRIPTION: string = __( 'Display the price of a product.', 'woo-gutenberg-products-block' ); diff --git a/assets/js/atomic/blocks/product-elements/price/edit.js b/assets/js/atomic/blocks/product-elements/price/edit.js deleted file mode 100644 index 2cbc90309f5..00000000000 --- a/assets/js/atomic/blocks/product-elements/price/edit.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * External dependencies - */ -import { - AlignmentToolbar, - BlockControls, - useBlockProps, -} from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; -import { useEffect } from 'react'; - -/** - * Internal dependencies - */ -import Block from './block'; -import withProductSelector from '../shared/with-product-selector'; -import { BLOCK_TITLE, BLOCK_ICON } from './constants'; - -const PriceEdit = ( { attributes, setAttributes, context } ) => { - const blockProps = useBlockProps(); - const blockAttrs = { - ...attributes, - ...context, - }; - const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); - - useEffect( - () => setAttributes( { isDescendentOfQueryLoop } ), - [ setAttributes, isDescendentOfQueryLoop ] - ); - - return ( - <> - - { isDescendentOfQueryLoop && ( - { - setAttributes( { textAlign: newAlign } ); - } } - /> - ) } - -
- -
- - ); -}; - -export default withProductSelector( { - icon: BLOCK_ICON, - label: BLOCK_TITLE, - description: __( - 'Choose a product to display its price.', - 'woo-gutenberg-products-block' - ), -} )( PriceEdit ); diff --git a/assets/js/atomic/blocks/product-elements/price/edit.tsx b/assets/js/atomic/blocks/product-elements/price/edit.tsx new file mode 100644 index 00000000000..c3e77eef7bb --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/edit.tsx @@ -0,0 +1,78 @@ +/** + * External dependencies + */ +import { + AlignmentToolbar, + BlockControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { useEffect } from 'react'; +import type { BlockAlignment } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Block from './block'; +import withProductSelector from '../shared/with-product-selector'; +import { BLOCK_TITLE as label, BLOCK_ICON as icon } from './constants'; + +type UnsupportedAligments = 'wide' | 'full'; +type AllowedAlignments = Exclude< BlockAlignment, UnsupportedAligments >; + +interface BlockAttributes { + textAlign?: AllowedAlignments; +} + +interface Attributes { + textAlign: 'left' | 'center' | 'right'; +} + +interface Context { + queryId: number; +} + +interface Props { + attributes: Attributes; + setAttributes: ( + attributes: Partial< BlockAttributes > & Record< string, unknown > + ) => void; + context: Context; +} + +const PriceEdit = ( { + attributes, + setAttributes, + context, +}: Props ): JSX.Element => { + const blockProps = useBlockProps(); + const blockAttrs = { + ...attributes, + ...context, + }; + const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); + + useEffect( + () => setAttributes( { isDescendentOfQueryLoop } ), + [ setAttributes, isDescendentOfQueryLoop ] + ); + + return ( + <> + + { isDescendentOfQueryLoop && ( + { + setAttributes( { textAlign } ); + } } + /> + ) } + +
+ +
+ + ); +}; + +export default withProductSelector( { icon, label } )( PriceEdit ); diff --git a/assets/js/atomic/blocks/product-elements/price/index.js b/assets/js/atomic/blocks/product-elements/price/index.ts similarity index 56% rename from assets/js/atomic/blocks/product-elements/price/index.js rename to assets/js/atomic/blocks/product-elements/price/index.ts index b10f3ab9dac..1934e31dc18 100644 --- a/assets/js/atomic/blocks/product-elements/price/index.js +++ b/assets/js/atomic/blocks/product-elements/price/index.ts @@ -1,8 +1,8 @@ /** * External dependencies */ -import { isFeaturePluginBuild } from '@woocommerce/block-settings'; import { registerBlockType } from '@wordpress/blocks'; +import type { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies @@ -10,13 +10,18 @@ import { registerBlockType } from '@wordpress/blocks'; import sharedConfig from '../shared/config'; import edit from './edit'; import attributes from './attributes'; +import { supports } from './supports'; import { BLOCK_TITLE as title, BLOCK_ICON as icon, BLOCK_DESCRIPTION as description, } from './constants'; -const blockConfig = { +type CustomBlockConfiguration = BlockConfiguration & { + ancestor: string[]; +}; + +const blockConfig: CustomBlockConfiguration = { ...sharedConfig, apiVersion: 2, title, @@ -29,25 +34,8 @@ const blockConfig = { usesContext: [ 'query', 'queryId', 'postId' ], icon: { src: icon }, attributes, + supports, edit, - supports: { - ...sharedConfig.supports, - ...( isFeaturePluginBuild() && { - color: { - text: true, - background: true, - link: false, - __experimentalSkipSerialization: true, - }, - typography: { - fontSize: true, - __experimentalFontWeight: true, - __experimentalFontStyle: true, - __experimentalSkipSerialization: true, - }, - __experimentalSelector: '.wc-block-components-product-price', - } ), - }, }; registerBlockType( 'woocommerce/product-price', blockConfig ); diff --git a/assets/js/atomic/blocks/product-elements/price/supports.ts b/assets/js/atomic/blocks/product-elements/price/supports.ts new file mode 100644 index 00000000000..078b70d46c8 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/supports.ts @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { isFeaturePluginBuild } from '@woocommerce/block-settings'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared/config'; + +export const supports = { + ...sharedConfig.supports, + ...( isFeaturePluginBuild() && { + color: { + text: true, + background: false, + link: false, + __experimentalSkipSerialization: true, + }, + typography: { + fontSize: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalSkipSerialization: true, + }, + __experimentalSelector: '.wc-block-components-product-price', + } ), +}; diff --git a/assets/js/atomic/blocks/product-elements/price/types.ts b/assets/js/atomic/blocks/product-elements/price/types.ts new file mode 100644 index 00000000000..81631b9698e --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/types.ts @@ -0,0 +1,6 @@ +export interface BlockAttributes { + productId?: number; + className?: string; + textAlign?: 'left' | 'center' | 'right'; + isDescendentOfQueryLoop?: boolean; +} diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/index.ts b/assets/js/atomic/blocks/product-elements/sale-badge/index.ts index f44222cf0df..582aa91f86b 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/index.ts +++ b/assets/js/atomic/blocks/product-elements/sale-badge/index.ts @@ -17,7 +17,11 @@ import { } from './constants'; import { supports } from './support'; -const blockConfig: BlockConfiguration = { +type CustomBlockConfiguration = BlockConfiguration & { + ancestor: string[]; +}; + +const blockConfig: CustomBlockConfiguration = { ...sharedConfig, title, description, diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/types.ts b/assets/js/atomic/blocks/product-elements/sale-badge/types.ts index e638757b452..884061b0eb2 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/types.ts +++ b/assets/js/atomic/blocks/product-elements/sale-badge/types.ts @@ -1,5 +1,5 @@ export interface BlockAttributes { - productId: number; - align: 'left' | 'center' | 'right'; + productId?: number; + align?: 'left' | 'center' | 'right'; isDescendentOfQueryLoop: boolean; } diff --git a/assets/js/atomic/blocks/product-elements/sku/index.ts b/assets/js/atomic/blocks/product-elements/sku/index.ts index 1e1d1cdc542..5caedddf1d8 100644 --- a/assets/js/atomic/blocks/product-elements/sku/index.ts +++ b/assets/js/atomic/blocks/product-elements/sku/index.ts @@ -16,7 +16,12 @@ import { BLOCK_DESCRIPTION as description, } from './constants'; -const blockConfig: BlockConfiguration = { +type CustomBlockConfiguration = BlockConfiguration & { + ancestor: string[]; +}; + +const blockConfig: CustomBlockConfiguration = { + ...sharedConfig, apiVersion: 2, title, description, @@ -31,7 +36,4 @@ const blockConfig: BlockConfiguration = { edit, }; -registerBlockType( 'woocommerce/product-sku', { - ...sharedConfig, - ...blockConfig, -} ); +registerBlockType( 'woocommerce/product-sku', { ...blockConfig } ); \ No newline at end of file diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts b/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts index f65b3161419..402a4ff2fb0 100644 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts @@ -18,7 +18,11 @@ import { BLOCK_DESCRIPTION as description, } from './constants'; -const blockConfig: BlockConfiguration = { +type CustomBlockConfiguration = BlockConfiguration & { + ancestor: string[]; +}; + +const blockConfig: CustomBlockConfiguration = { ...sharedConfig, apiVersion: 2, title, diff --git a/assets/js/base/components/formatted-monetary-amount/index.tsx b/assets/js/base/components/formatted-monetary-amount/index.tsx index 5c8d4033740..f13aa03da01 100644 --- a/assets/js/base/components/formatted-monetary-amount/index.tsx +++ b/assets/js/base/components/formatted-monetary-amount/index.tsx @@ -24,7 +24,7 @@ interface FormattedMonetaryAmountProps value: number | string; // Value of money amount. currency: Currency | Record< string, never >; // Currency configuration object. onValueChange?: ( unit: number ) => void; // Function to call when value changes. - style?: React.CSSProperties; + style?: React.CSSProperties | undefined; renderText?: ( value: string ) => JSX.Element; } @@ -35,16 +35,23 @@ const currencyToNumberFormat = ( currency: FormattedMonetaryAmountProps[ 'currency' ] ) => { return { - thousandSeparator: currency.thousandSeparator, - decimalSeparator: currency.decimalSeparator, - decimalScale: currency.minorUnit, + thousandSeparator: currency?.thousandSeparator, + decimalSeparator: currency?.decimalSeparator, + decimalScale: currency?.minorUnit, fixedDecimalScale: true, - prefix: currency.prefix, - suffix: currency.suffix, + prefix: currency?.prefix, + suffix: currency?.suffix, isNumericString: true, }; }; +type CustomFormattedMonetaryAmountProps = Omit< + FormattedMonetaryAmountProps, + 'currency' +> & { + currency: Currency | Record< string, never >; +}; + /** * FormattedMonetaryAmount component. * @@ -57,7 +64,7 @@ const FormattedMonetaryAmount = ( { onValueChange, displayType = 'text', ...props -}: FormattedMonetaryAmountProps ): ReactElement | null => { +}: CustomFormattedMonetaryAmountProps ): ReactElement | null => { const value = typeof rawValue === 'string' ? parseInt( rawValue, 10 ) : rawValue; diff --git a/assets/js/base/components/product-price/index.tsx b/assets/js/base/components/product-price/index.tsx index 814aebc7248..bf02a763b89 100644 --- a/assets/js/base/components/product-price/index.tsx +++ b/assets/js/base/components/product-price/index.tsx @@ -17,7 +17,7 @@ interface PriceRangeProps { /** * Currency configuration object */ - currency: Currency | Record< string, never >; + currency: Currency | Record< string, never > | undefined; /** * The maximum price for the range */ @@ -31,13 +31,13 @@ interface PriceRangeProps { * * **Note:** this excludes the dash in between the elements */ - priceClassName?: string; + priceClassName?: string | undefined; /** * Any custom style to be applied to each of the elements containing the prices * * **Note:** this excludes the dash in between the elements */ - priceStyle?: React.CSSProperties; + priceStyle?: React.CSSProperties | undefined; } const PriceRange = ( { @@ -89,19 +89,19 @@ interface SalePriceProps { /** * Currency configuration object */ - currency: Currency | Record< string, never >; + currency: Currency | Record< string, never > | undefined; /** * CSS class to be applied to the regular price container * * i.e. `` element */ - regularPriceClassName?: string; + regularPriceClassName?: string | undefined; /** * Custom style to be applied to the regular price container * * i.e. `` element */ - regularPriceStyle?: React.CSSProperties; + regularPriceStyle?: React.CSSProperties | undefined; /** * The regular price before the sale */ @@ -111,17 +111,17 @@ interface SalePriceProps { * * i.e. `` element */ - priceClassName?: string; + priceClassName?: string | undefined; /** * Custom style to be applied to the regular price container * * i.e. `` element */ - priceStyle?: React.CSSProperties; + priceStyle?: React.CSSProperties | undefined; /** * The new price during the sale */ - price: number | string; + price: number | string | undefined; } const SalePrice = ( { @@ -183,25 +183,25 @@ export interface ProductPriceProps { * Applies the `wc-block-components-product-price--align-${ align }` utility * class to the wrapper. */ - align?: 'left' | 'center' | 'right'; + align?: 'left' | 'center' | 'right' | undefined; /** * CSS class for the wrapper */ - className?: string; + className?: string | undefined; /** * Currency configuration object */ - currency: Currency | Record< string, never >; + currency?: Currency | Record< string, never >; /** * The string version of the element to use for the price interpolation * * **Note:** It should contain `` (which is also the default value) */ - format: string; + format?: string; /** * The current price */ - price: number | string; + price?: number | string; /** * CSS class for the current price wrapper */ @@ -209,36 +209,36 @@ export interface ProductPriceProps { /** * Custom style for the current price */ - priceStyle?: React.CSSProperties; + priceStyle?: React.CSSProperties | undefined; /** * The maximum price in a range * * If both `maxPrice` and `minPrice` are set, the component will be rendered * as a `PriceRange` component, otherwise, this value will be ignored. */ - maxPrice?: number | string; + maxPrice?: number | string | undefined; /** * The minimum price in a range * * If both `maxPrice` and `minPrice` are set, the component will be rendered * as a `PriceRange` component, otherwise, this value will be ignored. */ - minPrice?: number | string; + minPrice?: number | string | undefined; /** * The regular price if the item is currently on sale * * If this property exists and is different from the current price, then the * component will be rendered as a `SalePrice` component. */ - regularPrice?: number | string; + regularPrice?: number | string | undefined; /** * CSS class to apply to the regular price wrapper */ - regularPriceClassName?: string; + regularPriceClassName?: string | undefined; /** * Custom style to apply to the regular price wrapper. */ - regularPriceStyle?: React.CSSProperties; + regularPriceStyle?: React.CSSProperties | undefined; } const ProductPrice = ( { diff --git a/assets/js/settings/shared/settings-init.ts b/assets/js/settings/shared/settings-init.ts index b3a953ef59a..5184f6c3a01 100644 --- a/assets/js/settings/shared/settings-init.ts +++ b/assets/js/settings/shared/settings-init.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import { SymbolPosition } from '@woocommerce/types'; +import { SymbolPosition, CurrencyCode } from '@woocommerce/types'; declare global { interface Window { @@ -11,7 +11,7 @@ declare global { export interface WooCommerceSiteCurrency { // The ISO code for the currency. - code: string; + code: CurrencyCode; // The precision (decimal places). precision: number; // The symbol for the currency (eg '$') @@ -86,15 +86,19 @@ const defaults: WooCommerceSharedSettings = { const globalSharedSettings = typeof window.wcSettings === 'object' ? window.wcSettings : {}; +interface AllSettings extends Record< string, unknown > { + currency: WooCommerceSiteCurrency; +} + // Use defaults or global settings, depending on what is set. -const allSettings: Record< string, unknown > = { +const allSettings: AllSettings = { ...defaults, ...globalSharedSettings, }; allSettings.currency = { ...defaults.currency, - ...( allSettings.currency as Record< string, unknown > ), + ...( allSettings.currency as WooCommerceSiteCurrency ), }; allSettings.locale = { diff --git a/assets/js/shared/context/product-data-context.tsx b/assets/js/shared/context/product-data-context.tsx index 8bd146c8beb..deb6f2d8d0e 100644 --- a/assets/js/shared/context/product-data-context.tsx +++ b/assets/js/shared/context/product-data-context.tsx @@ -70,8 +70,8 @@ export const useProductDataContext = () => useContext( ProductDataContext ); interface ProductDataContextProviderProps { product: ProductResponseItem | null; + children: JSX.Element | JSX.Element[]; isLoading: boolean; - children: React.ReactNode; } /** diff --git a/checkstyle.xml b/checkstyle.xml index b27bd61bf3a..309aa9161dd 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -240,16 +240,11 @@ Argument of type 'string | BlockConfiguration<{}>' is not assignable to parameter of type 'string'. Type 'BlockConfiguration<{}>' is not assignable to type 'string'." source="TS2769" /> - - - - - - - - - - + + + + @@ -266,7 +261,7 @@ Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/packages/checkout/components/totals/taxes/index.tsx' Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/packages/checkout/components/totals/fees/index.tsx' Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/base/components/product-price/index.tsx' - Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/block.js' + Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/block.tsx' Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts' Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/base/components/cart-checkout/order-summary/order-summary-item.tsx' Imported via '@woocommerce/price-format' from file '/home/runner/work/woocommerce-blocks/woocommerce-blocks/assets/js/base/components/cart-checkout/product-sale-badge/index.tsx' @@ -584,6 +579,15 @@ + + + + - - - - - - - - - - - @@ -699,15 +687,31 @@ - - - - - - + + + + + + + + + + + + + + + + + - - - - + @@ -1100,10 +1094,6 @@ - - - - @@ -1316,14 +1306,9 @@ Type '{ text: true; background: true; link: false; gradients: true; __experimentalSkipSerialization: true; }' is not assignable to type 'Partial<ColorProps>'. Object literal may only specify known properties, and '__experimentalSkipSerialization' does not exist in type 'Partial<ColorProps>'." source="TS2322" /> - - - - - - - + + @@ -1344,6 +1329,9 @@ Types of parameters '__0' and 'props' are incompatible. Property 'context' is missing in type 'BlockEditProps<{}> & { children?: ReactNode; }' but required in type '{ attributes: any; setAttributes: any; context: any; }'." source="TS2769" /> + + + @@ -1361,11 +1349,24 @@ Type 'Readonly<{}>' is not assignable to type 'Record<string, unknown> & { className: string; }'. Property 'className' is missing in type 'Readonly<{}>' but required in type '{ className: string; }'." source="TS2322" /> + + + + + + + + + + + + @@ -3688,17 +3689,27 @@ - - + - + + + + + + + + +