Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 1e647e2

Browse files
nielslangewavvvesgithub-actions[bot]
authored
Convert product-elements/image to TypeScript (#7572)
* Updated package-lock.json * fixed method sig * PHP versions matrix * removed extra space * renamed step * Update E2E and coding standards to use PHP 8.0 * Un-linted unit-tests.yml. Github flows use 4 spaces indent, while our .editorconfig file enforces 2 spaces. * Refactor unit-tests.yml * Linted unit-tests.yml to proper 2 space indents * Removed composer caching * Test without hacky permissions step * Concurrency disable. Jobs renaming. * Add step to install wp-env for PHP unit tests. * Another try at fixing perms for wp-env * Another try at fixing perms for wp-env * Restore missing steps * Convert product-elements/image to TypeScript * bot: update checkstyle.xml * Refactor edit.tsx * bot: update checkstyle.xml * Add interface to attributes.ts * Convert product-elements/image to TypeScript * bot: update checkstyle.xml * bot: update checkstyle.xml * Refactor edit.tsx * Add interface to attributes.ts * bot: update checkstyle.xml * bot: update checkstyle.xml * Solve TS error * Solve TS error * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Document types * Adjust TS interface * Correct merge mistakes * bot: update checkstyle.xml * Solve TS errors * bot: update checkstyle.xml * Fix broken JS unit test Co-authored-by: Paulo Arromba <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 21d7d66 commit 1e647e2

File tree

10 files changed

+214
-125
lines changed

10 files changed

+214
-125
lines changed

assets/js/atomic/blocks/product-elements/image/attributes.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1-
export const attributes = {
1+
interface BlockAttributes {
2+
productId: {
3+
type: string;
4+
default: number;
5+
};
6+
showProductLink: {
7+
type: string;
8+
default: boolean;
9+
};
10+
showSaleBadge: {
11+
type: string;
12+
default: boolean;
13+
};
14+
saleBadgeAlign: {
15+
type: string;
16+
default: string;
17+
};
18+
imageSizing: {
19+
type: string;
20+
default: string;
21+
};
22+
isDescendentOfQueryLoop: {
23+
type: string;
24+
default: boolean;
25+
};
26+
}
27+
export const blockAttributes: BlockAttributes = {
228
showProductLink: {
329
type: 'boolean',
430
default: true,
@@ -24,3 +50,5 @@ export const attributes = {
2450
default: false,
2551
},
2652
};
53+
54+
export default blockAttributes;

assets/js/atomic/blocks/product-elements/image/block.js renamed to assets/js/atomic/blocks/product-elements/image/block.tsx

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/**
22
* External dependencies
33
*/
4-
import PropTypes from 'prop-types';
54
import { Fragment } from '@wordpress/element';
65
import { __, sprintf } from '@wordpress/i18n';
76
import classnames from 'classnames';
@@ -17,39 +16,79 @@ import {
1716
} from '@woocommerce/base-hooks';
1817
import { withProductDataContext } from '@woocommerce/shared-hocs';
1918
import { useStoreEvents } from '@woocommerce/base-context/hooks';
19+
import type { HTMLAttributes } from 'react';
2020

2121
/**
2222
* Internal dependencies
2323
*/
2424
import ProductSaleBadge from '../sale-badge/block';
2525
import './style.scss';
26+
import type { BlockAttributes } from './types';
2627

27-
/**
28-
* Product Image Block Component.
29-
*
30-
* @param {Object} props Incoming props.
31-
* @param {string} [props.className] CSS Class name for the component.
32-
* @param {string|undefined} [props.imageSizing] Size of image to use.
33-
* @param {boolean|undefined} [props.showProductLink] Whether or not to display a link to the product page.
34-
* @param {boolean} [props.showSaleBadge] Whether or not to display the on sale badge.
35-
* @param {string|undefined} [props.saleBadgeAlign] How should the sale badge be aligned if displayed.
36-
* @param {boolean} [props.isDescendentOfQueryLoop] Whether or not be a children of Query Loop Block.
37-
* @return {*} The component.
38-
*/
39-
export const Block = ( props ) => {
28+
const ImagePlaceholder = (): JSX.Element => {
29+
return (
30+
<img
31+
src={ PLACEHOLDER_IMG_SRC }
32+
alt=""
33+
width={ undefined }
34+
height={ undefined }
35+
/>
36+
);
37+
};
38+
39+
interface ImageProps {
40+
image?: null | {
41+
alt?: string | undefined;
42+
id: number;
43+
name: string;
44+
sizes?: string | undefined;
45+
src?: string | undefined;
46+
srcset?: string | undefined;
47+
thumbnail?: string | undefined;
48+
};
49+
loaded: boolean;
50+
showFullSize: boolean;
51+
fallbackAlt: string;
52+
}
53+
54+
const Image = ( {
55+
image,
56+
loaded,
57+
showFullSize,
58+
fallbackAlt,
59+
}: ImageProps ): JSX.Element => {
60+
const { thumbnail, src, srcset, sizes, alt } = image || {};
61+
const imageProps = {
62+
alt: alt || fallbackAlt,
63+
hidden: ! loaded,
64+
src: thumbnail,
65+
...( showFullSize && { src, srcSet: srcset, sizes } ),
66+
};
67+
68+
return (
69+
<>
70+
{ imageProps.src && (
71+
/* eslint-disable-next-line jsx-a11y/alt-text */
72+
<img data-testid="product-image" { ...imageProps } />
73+
) }
74+
{ ! image && <ImagePlaceholder /> }
75+
</>
76+
);
77+
};
78+
79+
type Props = BlockAttributes & HTMLAttributes< HTMLDivElement >;
80+
81+
export const Block = ( props: Props ): JSX.Element | null => {
4082
const {
4183
className,
4284
imageSizing = 'full-size',
4385
showProductLink = true,
4486
showSaleBadge,
4587
saleBadgeAlign = 'right',
4688
} = props;
47-
4889
const { parentClassName } = useInnerBlockLayoutContext();
4990
const { product, isLoading } = useProductDataContext();
50-
5191
const { dispatchStoreEvent } = useStoreEvents();
52-
5392
const typographyProps = useTypographyProps( props );
5493
const borderProps = useBorderProps( props );
5594
const spacingProps = useSpacingProps( props );
@@ -128,39 +167,4 @@ export const Block = ( props ) => {
128167
);
129168
};
130169

131-
const ImagePlaceholder = () => {
132-
// The alt text is left empty on purpose, as it's considered a decorative image.
133-
// More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/.
134-
// Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494.
135-
return <img src={ PLACEHOLDER_IMG_SRC } alt="" />;
136-
};
137-
138-
const Image = ( { image, loaded, showFullSize, fallbackAlt } ) => {
139-
const { thumbnail, src, srcset, sizes, alt } = image || {};
140-
const imageProps = {
141-
alt: alt || fallbackAlt,
142-
hidden: ! loaded,
143-
src: thumbnail,
144-
...( showFullSize && { src, srcSet: srcset, sizes } ),
145-
};
146-
147-
return (
148-
<>
149-
{ imageProps.src && (
150-
/* eslint-disable-next-line jsx-a11y/alt-text */
151-
<img data-testid="product-image" { ...imageProps } />
152-
) }
153-
{ ! image && <ImagePlaceholder /> }
154-
</>
155-
);
156-
};
157-
158-
Block.propTypes = {
159-
className: PropTypes.string,
160-
fallbackAlt: PropTypes.string,
161-
showProductLink: PropTypes.bool,
162-
showSaleBadge: PropTypes.bool,
163-
saleBadgeAlign: PropTypes.string,
164-
};
165-
166170
export default withProductDataContext( Block );
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { __ } from '@wordpress/i18n';
5+
import { image, Icon } from '@wordpress/icons';
6+
7+
export const BLOCK_TITLE: string = __(
8+
'Product Image',
9+
'woo-gutenberg-products-block'
10+
);
11+
export const BLOCK_ICON: JSX.Element = (
12+
<Icon icon={ image } className="wc-block-editor-components-block-icon" />
13+
);
14+
export const BLOCK_DESCRIPTION: string = __(
15+
'Display the main product image.',
16+
'woo-gutenberg-products-block'
17+
);

assets/js/atomic/blocks/product-elements/image/edit.js renamed to assets/js/atomic/blocks/product-elements/image/edit.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
66
import { createInterpolateElement, useEffect } from '@wordpress/element';
77
import { getAdminLink, getSettingWithCoercion } from '@woocommerce/settings';
88
import { isBoolean } from '@woocommerce/types';
9+
import type { BlockEditProps } from '@wordpress/blocks';
10+
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
911
import {
1012
Disabled,
1113
PanelBody,
1214
ToggleControl,
15+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
16+
// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
1317
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
1418
__experimentalToggleGroupControl as ToggleGroupControl,
19+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
20+
// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
1521
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
1622
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
1723
} from '@wordpress/components';
@@ -20,26 +26,37 @@ import {
2026
* Internal dependencies
2127
*/
2228
import Block from './block';
29+
import withProductSelector from '../shared/with-product-selector';
30+
import {
31+
BLOCK_TITLE as label,
32+
BLOCK_ICON as icon,
33+
BLOCK_DESCRIPTION as description,
34+
} from './constants';
35+
import type { BlockAttributes } from './types';
36+
37+
type SaleBadgeAlignProps = 'left' | 'center' | 'right';
38+
type ImageSizingProps = 'full-size' | 'cropped';
2339

24-
const Edit = ( { attributes, setAttributes, context } ) => {
40+
const Edit = ( {
41+
attributes,
42+
setAttributes,
43+
context,
44+
}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => {
2545
const { showProductLink, imageSizing, showSaleBadge, saleBadgeAlign } =
2646
attributes;
27-
2847
const blockProps = useBlockProps();
29-
3048
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
31-
32-
useEffect(
33-
() => setAttributes( { isDescendentOfQueryLoop } ),
34-
[ setAttributes, isDescendentOfQueryLoop ]
35-
);
36-
3749
const isBlockThemeEnabled = getSettingWithCoercion(
3850
'is_block_theme_enabled',
3951
false,
4052
isBoolean
4153
);
4254

55+
useEffect(
56+
() => setAttributes( { isDescendentOfQueryLoop } ),
57+
[ setAttributes, isDescendentOfQueryLoop ]
58+
);
59+
4360
useEffect( () => {
4461
if ( isBlockThemeEnabled && attributes.imageSizing !== 'full-size' ) {
4562
setAttributes( { imageSizing: 'full-size' } );
@@ -91,7 +108,7 @@ const Edit = ( { attributes, setAttributes, context } ) => {
91108
'woo-gutenberg-products-block'
92109
) }
93110
value={ saleBadgeAlign }
94-
onChange={ ( value ) =>
111+
onChange={ ( value: SaleBadgeAlignProps ) =>
95112
setAttributes( { saleBadgeAlign: value } )
96113
}
97114
>
@@ -143,7 +160,7 @@ const Edit = ( { attributes, setAttributes, context } ) => {
143160
}
144161
) }
145162
value={ imageSizing }
146-
onChange={ ( value ) =>
163+
onChange={ ( value: ImageSizingProps ) =>
147164
setAttributes( { imageSizing: value } )
148165
}
149166
>
@@ -172,4 +189,4 @@ const Edit = ( { attributes, setAttributes, context } ) => {
172189
);
173190
};
174191

175-
export default Edit;
192+
export default withProductSelector( { icon, label, description } )( Edit );

assets/js/atomic/blocks/product-elements/image/frontend.js renamed to assets/js/atomic/blocks/product-elements/image/frontend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ import { withFilteredAttributes } from '@woocommerce/shared-hocs';
77
* Internal dependencies
88
*/
99
import Block from './block';
10-
import { attributes } from './attributes';
10+
import attributes from './attributes';
1111

1212
export default withFilteredAttributes( attributes )( Block );

assets/js/atomic/blocks/product-elements/image/index.js renamed to assets/js/atomic/blocks/product-elements/image/index.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,34 @@
22
* External dependencies
33
*/
44
import { registerBlockType } from '@wordpress/blocks';
5-
import { image, Icon } from '@wordpress/icons';
6-
import { __ } from '@wordpress/i18n';
5+
import type { BlockConfiguration } from '@wordpress/blocks';
76

87
/**
98
* Internal dependencies
109
*/
1110
import edit from './edit';
1211

1312
import { supports } from './supports';
14-
import { attributes } from './attributes';
13+
import attributes from './attributes';
1514
import sharedConfig from '../shared/config';
15+
import {
16+
BLOCK_TITLE as title,
17+
BLOCK_ICON as icon,
18+
BLOCK_DESCRIPTION as description,
19+
} from './constants';
1620

17-
const blockConfig = {
21+
type CustomBlockConfiguration = BlockConfiguration & {
22+
ancestor: string[];
23+
};
24+
25+
const blockConfig: CustomBlockConfiguration = {
26+
...sharedConfig,
1827
apiVersion: 2,
1928
name: 'woocommerce/product-image',
20-
title: __( 'Product Image', 'woo-gutenberg-products-block' ),
21-
icon: {
22-
src: (
23-
<Icon
24-
icon={ image }
25-
className="wc-block-editor-components-block-icon"
26-
/>
27-
),
28-
},
29+
title,
30+
icon: { src: icon },
2931
keywords: [ 'WooCommerce' ],
30-
description: __(
31-
'Display the main product image.',
32-
'woo-gutenberg-products-block'
33-
),
32+
description,
3433
usesContext: [ 'query', 'queryId', 'postId' ],
3534
ancestor: [
3635
'@woocommerce/all-products',
@@ -43,7 +42,4 @@ const blockConfig = {
4342
edit,
4443
};
4544

46-
registerBlockType( 'woocommerce/product-image', {
47-
...sharedConfig,
48-
...blockConfig,
49-
} );
45+
registerBlockType( 'woocommerce/product-image', { ...blockConfig } );

0 commit comments

Comments
 (0)