Commit eeecf7ef29 for woocommerce

commit eeecf7ef299f12f6d65bf4f08a31b337c90d7adc
Author: Alefe Souza <contact@alefesouza.com>
Date:   Thu Dec 11 15:44:09 2025 -0300

    Show product thumbnails on Blocks Checkout with multiple packages (#61625)

    * New design for multiple packages

    * Make sure extra packages are selected when switching to Ship mode

    * Add unit tests

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Decrease shipping package font size

    * Styling improvements for collapsible shipping methods

    * Hide third thumbnail on small screens

    * Add extra checks

    * Add unit tests for ShippingPackageItemIcon

    * Set thumbnail object fit as cover

    * Do not use existing address card class

    * Fix linting error

    * Fix unit tests

    * Add better sanitization on package names

    * Fix linting error

    * Use separate card styles for local pickup

    * Rename ProductImageProps to ShippingPackageItemIconProps

    * Fix no address notices size

    * Update JSDoc

    * Update ShippingPackageItemIcon JSDoc description

    ---------

    Co-authored-by: github-actions <github-actions@github.com>

diff --git a/plugins/woocommerce/changelog/61625-update-recurring-carts-product-thumbnails b/plugins/woocommerce/changelog/61625-update-recurring-carts-product-thumbnails
new file mode 100644
index 0000000000..49f6410ee6
--- /dev/null
+++ b/plugins/woocommerce/changelog/61625-update-recurring-carts-product-thumbnails
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Blocks Checkout design improvements for multiple shipping packages.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/css/abstracts/_mixins.scss b/plugins/woocommerce/client/blocks/assets/css/abstracts/_mixins.scss
index bcc2f49414..2a046861c6 100644
--- a/plugins/woocommerce/client/blocks/assets/css/abstracts/_mixins.scss
+++ b/plugins/woocommerce/client/blocks/assets/css/abstracts/_mixins.scss
@@ -392,6 +392,17 @@ $fontSizes: (
 	}
 }

+@mixin card-styles() {
+	@include font-regular-locked;
+	border: 1px solid $universal-border-light;
+	padding: $gap;
+	margin: 0;
+	border-radius: $universal-border-radius;
+	display: flex;
+	justify-content: flex-start;
+	align-items: flex-start;
+}
+
 @keyframes wc-skeleton-shimmer {
 	100% {
 		transform: translateX(100%);
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/index.js b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/index.js
index 6b03afc288..66f5cf9c6e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/index.js
@@ -17,4 +17,6 @@ export { default as PaymentMethodIcons } from './payment-method-icons';
 export { default as PaymentMethodLabel } from './payment-method-label';
 export { default as AdditionalFieldsPlaceholder } from './additional-fields-placeholder';
 export { default as PasswordStrengthMeter } from './password-strength-meter';
+export { default as PackageItems } from './package-items';
+export { default as ShippingPackageItemIcon } from './shipping-package-item-icon';
 export * from './totals';
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
index 54bfd8a067..745c3e4bc5 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
@@ -7,9 +7,25 @@ import {
 } from '@woocommerce/blocks-components';
 import { CartShippingPackageShippingRate } from '@woocommerce/types';
 import { useShippingData } from '@woocommerce/base-context';
+import clsx from 'clsx';
+import { sanitizeHTML } from '@woocommerce/sanitize';
+import { useStoreCart } from '@woocommerce/base-context/hooks';
+import {
+	PackageItems,
+	ShippingPackageItemIcon,
+} from '@woocommerce/base-components/cart-checkout';
+
+/**
+ * Internal dependencies
+ */
+import type { PackageData } from '../shipping-rates-control-package/types';
+
+import './style.scss';

 interface LocalPickupSelectProps {
 	title?: string | undefined;
+	packageData?: PackageData;
+	showItems?: boolean;
 	selectedOption: string;
 	pickupLocations: CartShippingPackageShippingRate[];
 	renderPickupLocation: (
@@ -24,6 +40,8 @@ interface LocalPickupSelectProps {
  */
 export const LocalPickupSelect = ( {
 	title,
+	packageData = undefined,
+	showItems,
 	selectedOption,
 	pickupLocations,
 	renderPickupLocation,
@@ -31,6 +49,7 @@ export const LocalPickupSelect = ( {
 	onChange,
 }: LocalPickupSelectProps ) => {
 	const { shippingRates } = useShippingData();
+	const { cartItems } = useStoreCart();
 	const internalPackageCount = shippingRates?.length || 1;
 	// Hacky way to check if there are multiple packages, this way is borrowed from  `assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx`
 	// We have no built-in way of checking if other extensions have added packages.
@@ -39,9 +58,60 @@ export const LocalPickupSelect = ( {
 		document.querySelectorAll(
 			'.wc-block-components-local-pickup-select .wc-block-components-radio-control'
 		).length > 1;
+
+	// If showItems is not set, we check if we have multiple packages.
+	// We sometimes don't want to show items even if we have multiple packages.
+	const shouldShowItems = showItems ?? multiplePackages;
+
+	let header = multiplePackages && title && <div>{ title }</div>;
+
+	// packageData was added in version 10.4
+	if ( ( multiplePackages || shouldShowItems ) && packageData ) {
+		header = (
+			<div className="wc-block-components-shipping-rates-control__package-header">
+				<div
+					className="wc-block-components-shipping-rates-control__package-title"
+					dangerouslySetInnerHTML={ {
+						__html: sanitizeHTML(
+							String( packageData.name ?? '' )
+						),
+					} }
+				/>
+				{ shouldShowItems && (
+					<PackageItems packageData={ packageData } />
+				) }
+			</div>
+		);
+
+		if ( multiplePackages ) {
+			const packageItems = packageData.items || [];
+
+			header = (
+				<div className="wc-block-components-shipping-rates-control__package-container">
+					{ header }
+					<div className="wc-block-components-shipping-rates-control__package-thumbnails">
+						{ packageItems.slice( 0, 3 ).map( ( item ) => (
+							<ShippingPackageItemIcon
+								key={ item.key }
+								packageItem={ item }
+								cartItems={ cartItems }
+							/>
+						) ) }
+					</div>
+				</div>
+			);
+		}
+	}
+
 	return (
-		<div className="wc-block-components-local-pickup-select">
-			{ multiplePackages && title ? <div>{ title }</div> : false }
+		<div
+			className={ clsx(
+				'wc-block-components-local-pickup-select',
+				multiplePackages &&
+					'wc-block-components-local-pickup-select--multiple'
+			) }
+		>
+			{ header }
 			<RadioControl
 				onChange={ onChange }
 				highlightChecked={ true }
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/style.scss b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/style.scss
new file mode 100644
index 0000000000..bfdbeb3881
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/style.scss
@@ -0,0 +1,20 @@
+.wc-block-components-local-pickup-rates-control {
+	.wc-block-components-local-pickup-select {
+		margin-bottom: $gap;
+		display: flex;
+		flex-direction: column;
+		gap: $gap-small;
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+	}
+
+	.wc-block-components-radio-control {
+		width: 100%;
+	}
+}
+
+.wc-block-components-local-pickup-select--multiple {
+	@include card-styles();
+}
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
index e6eaa428f8..d7e9479549 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
@@ -3,46 +3,82 @@
  */
 import { render, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
+import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';
+import { CartShippingPackageShippingRate } from '@woocommerce/types';

 /**
  * Internal dependencies
  */
 import LocalPickupSelect from '..';
-import { generateShippingRate } from '../../../../../mocks/shipping-package';
+import {
+	generateShippingRate,
+	generateShippingPackage,
+} from '../../../../../mocks/shipping-package';
+
+jest.mock( '@woocommerce/base-context/hooks' );

 describe( 'LocalPickupSelect', () => {
+	const defaultPackageData = generateShippingPackage( {
+		packageId: 0,
+		shippingRates: [],
+	} );
+
+	const mockShippingData = ( packageData = defaultPackageData ) => {
+		( useShippingData as jest.Mock ).mockImplementation( () => ( {
+			shippingRates: [ packageData ],
+		} ) );
+	};
+
+	const defaultRenderPickupLocation = (
+		location: CartShippingPackageShippingRate
+	) => ( {
+		value: `${ location.rate_id }`,
+		onChange: jest.fn(),
+		label: `${ location.name }`,
+		description: `${ location.description }`,
+	} );
+
+	const defaultPickupLocations = [
+		generateShippingRate( {
+			rateId: '1',
+			name: 'Store 1',
+			instanceID: 1,
+			price: '0',
+		} ),
+		generateShippingRate( {
+			rateId: '2',
+			name: 'Store 2',
+			instanceID: 1,
+			price: '0',
+		} ),
+	];
+
+	beforeEach( () => {
+		mockShippingData();
+
+		( useStoreCart as jest.Mock ).mockImplementation( () => ( {
+			cartItems: [],
+		} ) );
+	} );
+
 	const TestComponent = ( {
 		onChange,
+		renderPickupLocation = defaultRenderPickupLocation,
+		pickupLocations = defaultPickupLocations,
+		packageCount = 1,
 	}: {
 		onChange?: ( value: string ) => void;
+		renderPickupLocation?: typeof defaultRenderPickupLocation;
+		pickupLocations?: typeof defaultPickupLocations;
+		packageCount?: number;
 	} ) => (
 		<LocalPickupSelect
 			title="Package 1"
 			onChange={ onChange ?? jest.fn() }
 			selectedOption=""
-			pickupLocations={ [
-				generateShippingRate( {
-					rateId: '1',
-					name: 'Store 1',
-					instanceID: 1,
-					price: '0',
-				} ),
-				generateShippingRate( {
-					rateId: '2',
-					name: 'Store 2',
-					instanceID: 1,
-					price: '0',
-				} ),
-			] }
-			packageCount={ 1 }
-			renderPickupLocation={ ( location ) => {
-				return {
-					value: `${ location.rate_id }`,
-					onChange: jest.fn(),
-					label: `${ location.name }`,
-					description: `${ location.description }`,
-				};
-			} }
+			pickupLocations={ pickupLocations }
+			packageCount={ packageCount }
+			renderPickupLocation={ renderPickupLocation }
 		/>
 	);
 	it( 'Does not render the title if only one package is present on the page', () => {
@@ -86,4 +122,262 @@ describe( 'LocalPickupSelect', () => {
 		await user.click( screen.getByText( 'Store 1' ) );
 		expect( onChange ).toHaveBeenLastCalledWith( '1' );
 	} );
+
+	describe( 'packageData prop', () => {
+		it( 'Renders package name when multiple packages are present', () => {
+			const packageDataWithName = {
+				...generateShippingPackage( {
+					packageId: 0,
+					shippingRates: [],
+				} ),
+				name: 'Test Package Name',
+			};
+
+			mockShippingData( packageDataWithName );
+
+			const { rerender } = render(
+				<div className="wc-block-components-local-pickup-select">
+					<div className="wc-block-components-radio-control"></div>
+				</div>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithName }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ [
+							generateShippingRate( {
+								rateId: '1',
+								name: 'Store 1',
+								instanceID: 1,
+								price: '0',
+							} ),
+						] }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithName }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ defaultPickupLocations }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			expect(
+				screen.getByText( 'Test Package Name' )
+			).toBeInTheDocument();
+		} );
+
+		it( 'Renders package header with items when showItems is true', () => {
+			const packageDataWithItems = {
+				...generateShippingPackage( {
+					packageId: 0,
+					shippingRates: [],
+				} ),
+				name: 'Package with Items',
+			};
+
+			mockShippingData( packageDataWithItems );
+
+			render(
+				<LocalPickupSelect
+					title="Package 1"
+					packageData={ packageDataWithItems }
+					showItems={ true }
+					onChange={ jest.fn() }
+					selectedOption=""
+					pickupLocations={ defaultPickupLocations.slice( 0, 1 ) }
+					packageCount={ 1 }
+					renderPickupLocation={ defaultRenderPickupLocation }
+				/>
+			);
+
+			expect(
+				document.querySelector(
+					'.wc-block-components-shipping-rates-control__package-header'
+				)
+			).toBeInTheDocument();
+		} );
+
+		it( 'Does not render package header when showItems is false and single package', () => {
+			const packageDataWithItems = {
+				...generateShippingPackage( {
+					packageId: 0,
+					shippingRates: [],
+				} ),
+				name: 'Single Package',
+			};
+
+			mockShippingData( packageDataWithItems );
+
+			render(
+				<LocalPickupSelect
+					title="Package 1"
+					packageData={ packageDataWithItems }
+					showItems={ false }
+					onChange={ jest.fn() }
+					selectedOption=""
+					pickupLocations={ defaultPickupLocations.slice( 0, 1 ) }
+					packageCount={ 1 }
+					renderPickupLocation={ defaultRenderPickupLocation }
+				/>
+			);
+
+			expect(
+				document.querySelector(
+					'.wc-block-components-shipping-rates-control__package-header'
+				)
+			).not.toBeInTheDocument();
+		} );
+
+		it( 'Renders package thumbnails when multiple packages are present', () => {
+			const packageDataWithItems = {
+				...generateShippingPackage( {
+					packageId: 0,
+					shippingRates: [],
+				} ),
+				name: 'Package with Thumbnails',
+			};
+
+			mockShippingData( packageDataWithItems );
+
+			const { rerender } = render(
+				<div className="wc-block-components-local-pickup-select">
+					<div className="wc-block-components-radio-control"></div>
+				</div>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithItems }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ defaultPickupLocations }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithItems }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ defaultPickupLocations }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			expect(
+				document.querySelector(
+					'.wc-block-components-shipping-rates-control__package-thumbnails'
+				)
+			).toBeInTheDocument();
+		} );
+
+		it( 'Limits package thumbnails to first 3 items', () => {
+			const packageDataWithManyItems = {
+				...generateShippingPackage( {
+					packageId: 0,
+					shippingRates: [],
+				} ),
+				name: 'Package with Many Items',
+			};
+
+			// Add extra items to test the slice functionality
+			const extraItems = Array.from( { length: 5 }, ( _, i ) => ( {
+				key: `extra-${ i }`,
+				name: `Extra Item ${ i }`,
+				quantity: 1,
+			} ) );
+			packageDataWithManyItems.items = [
+				...packageDataWithManyItems.items,
+				...extraItems,
+			];
+
+			mockShippingData( packageDataWithManyItems );
+
+			const { rerender } = render(
+				<div className="wc-block-components-local-pickup-select">
+					<div className="wc-block-components-radio-control"></div>
+				</div>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithManyItems }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ defaultPickupLocations }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			rerender(
+				<>
+					<div className="wc-block-components-local-pickup-select">
+						<div className="wc-block-components-radio-control"></div>
+					</div>
+					<LocalPickupSelect
+						title="Package 1"
+						packageData={ packageDataWithManyItems }
+						onChange={ jest.fn() }
+						selectedOption=""
+						pickupLocations={ defaultPickupLocations }
+						packageCount={ 2 }
+						renderPickupLocation={ defaultRenderPickupLocation }
+					/>
+				</>
+			);
+
+			const thumbnailsContainer = document.querySelector(
+				'.wc-block-components-shipping-rates-control__package-thumbnails'
+			);
+			// Should only render 3 thumbnails even though there are more items
+			expect(
+				thumbnailsContainer?.querySelectorAll(
+					'.wc-block-components-shipping-package-item-icon'
+				).length
+			).toBeLessThanOrEqual( 3 );
+		} );
+	} );
 } );
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-items.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/package-items/index.tsx
similarity index 93%
rename from plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-items.tsx
rename to plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/package-items/index.tsx
index 5de961e41f..8592c72ae9 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-items.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/package-items/index.tsx
@@ -8,7 +8,10 @@ import { Label } from '@woocommerce/blocks-components';
 /**
  * Internal dependencies
  */
-import type { PackageData, PackageItem } from './types';
+import type {
+	PackageData,
+	PackageItem,
+} from '../shipping-rates-control-package/types';

 export const PackageItems = ( {
 	packageData,
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/index.tsx
new file mode 100644
index 0000000000..2f0efc38cb
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/index.tsx
@@ -0,0 +1,42 @@
+/**
+ * External dependencies
+ */
+import type { CartItem } from '@woocommerce/types';
+
+/**
+ * Internal dependencies
+ */
+import type { PackageItem } from '../shipping-rates-control-package/types';
+import ProductImage from '../product-image';
+
+interface ShippingPackageItemIconProps {
+	packageItem: PackageItem;
+	cartItems: CartItem[];
+}
+/**
+ * Renders a product image for a package item.
+ *
+ * @param {Object} props             Incoming props for the component.
+ * @param {Object} props.packageItem The package item.
+ * @param {Object} props.cartItems   The cartItems to get the image from via the packageItem key.
+ * @return {JSX.Element} React node.
+ */
+
+const ShippingPackageItemIcon = ( {
+	packageItem,
+	cartItems = [],
+}: ShippingPackageItemIconProps ): JSX.Element => {
+	const cartItem = cartItems?.find(
+		( item ) => item.key === packageItem.key
+	);
+	const images = cartItem?.images || [];
+
+	return (
+		<ProductImage
+			image={ images.length ? images[ 0 ] : {} }
+			fallbackAlt={ cartItem?.name || '' }
+		/>
+	);
+};
+
+export default ShippingPackageItemIcon;
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/test/index.test.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/test/index.test.tsx
new file mode 100644
index 0000000000..22c421fb4b
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-package-item-icon/test/index.test.tsx
@@ -0,0 +1,230 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+import type { CartItem } from '@woocommerce/types';
+
+/**
+ * Internal dependencies
+ */
+import ShippingPackageItemIcon from '../index';
+import type { PackageItem } from '../../shipping-rates-control-package/types';
+
+// Mock the ProductImage component
+jest.mock( '../../product-image', () => {
+	return function ProductImage( {
+		image,
+		fallbackAlt,
+		width,
+		height,
+	}: {
+		image: { alt?: string; thumbnail?: string };
+		fallbackAlt: string;
+		width?: number;
+		height?: number;
+	} ) {
+		return (
+			<img
+				data-testid="product-image"
+				src={ image.thumbnail || '' }
+				alt={ image.alt || fallbackAlt }
+				width={ width }
+				height={ height }
+			/>
+		);
+	};
+} );
+
+const mockPackageItem: PackageItem = {
+	key: 'test-item-key',
+	name: 'Test Product',
+	quantity: 1,
+};
+
+const mockCartItemWithImages: CartItem = {
+	key: 'test-item-key',
+	name: 'Test Product',
+	images: [
+		{
+			id: 1,
+			src: 'https://example.com/image1.jpg',
+			thumbnail: 'https://example.com/image1-thumb.jpg',
+			srcset: '',
+			sizes: '',
+			name: 'image1',
+			alt: 'Test Product Image',
+		},
+		{
+			id: 2,
+			src: 'https://example.com/image2.jpg',
+			thumbnail: 'https://example.com/image2-thumb.jpg',
+			srcset: '',
+			sizes: '',
+			name: 'image2',
+			alt: 'Second Image',
+		},
+	],
+	id: 1,
+	quantity: 1,
+	quantity_limits: {
+		minimum: 1,
+		maximum: 10,
+		multiple_of: 1,
+		editable: true,
+	},
+	catalog_visibility: 'visible',
+	prices: {
+		currency_code: 'USD',
+		currency_symbol: '$',
+		currency_minor_unit: 2,
+		currency_decimal_separator: '.',
+		currency_thousand_separator: ',',
+		currency_prefix: '$',
+		currency_suffix: '',
+		price: '1000',
+		regular_price: '1000',
+		sale_price: '1000',
+		price_range: null,
+		raw_prices: {
+			precision: 2,
+			price: '1000',
+			regular_price: '1000',
+			sale_price: '1000',
+		},
+	},
+	totals: {
+		currency_code: 'USD',
+		currency_symbol: '$',
+		currency_minor_unit: 2,
+		currency_decimal_separator: '.',
+		currency_thousand_separator: ',',
+		currency_prefix: '$',
+		currency_suffix: '',
+		line_subtotal: '1000',
+		line_subtotal_tax: '0',
+		line_total: '1000',
+		line_total_tax: '0',
+	},
+	variation: [],
+	item_data: [],
+	low_stock_remaining: null,
+	show_backorder_badge: false,
+	sold_individually: false,
+	permalink: 'https://example.com/product',
+	short_description: '',
+	description: '',
+	sku: '',
+	backorders_allowed: false,
+	type: 'simple',
+	summary: '',
+	extensions: {},
+};
+
+const mockCartItemWithoutImages: CartItem = {
+	...mockCartItemWithImages,
+	images: [],
+};
+
+describe( 'ShippingPackageItemIcon', () => {
+	it( 'renders ProductImage with the first image and correct props when cart item has images', () => {
+		render(
+			<ShippingPackageItemIcon
+				packageItem={ mockPackageItem }
+				cartItems={ [ mockCartItemWithImages ] }
+			/>
+		);
+
+		const image = screen.getByTestId( 'product-image' );
+		expect( image ).toBeInTheDocument();
+		expect( image ).toHaveAttribute(
+			'src',
+			'https://example.com/image1-thumb.jpg'
+		);
+		expect( image ).toHaveAttribute( 'alt', 'Test Product Image' );
+	} );
+
+	it.each( [
+		[
+			'cart item has no images',
+			mockPackageItem,
+			[ mockCartItemWithoutImages ],
+			'Test Product',
+		],
+		[
+			'cart item is not found',
+			{ ...mockPackageItem, key: 'non-existent' },
+			[ mockCartItemWithImages ],
+			'',
+		],
+		[ 'cartItems is empty', mockPackageItem, [], '' ],
+		[
+			'cartItems is undefined',
+			mockPackageItem,
+			undefined as unknown as CartItem[],
+			'',
+		],
+	] )(
+		'renders placeholder when %s',
+		( _, packageItem, cartItems, expectedAlt ) => {
+			render(
+				<ShippingPackageItemIcon
+					packageItem={ packageItem }
+					cartItems={ cartItems }
+				/>
+			);
+
+			const image = screen.getByTestId( 'product-image' );
+			expect( image ).toBeInTheDocument();
+			expect( image ).toHaveAttribute( 'src', '' );
+			expect( image ).toHaveAttribute( 'alt', expectedAlt );
+		}
+	);
+
+	it( 'uses cart item name as fallback alt text', () => {
+		const cartItemWithImageNoAlt: CartItem = {
+			...mockCartItemWithImages,
+			images: [
+				{
+					id: 1,
+					src: 'https://example.com/image.jpg',
+					thumbnail: 'https://example.com/image-thumb.jpg',
+					srcset: '',
+					sizes: '',
+					name: 'image',
+					alt: '',
+				},
+			],
+		};
+
+		render(
+			<ShippingPackageItemIcon
+				packageItem={ mockPackageItem }
+				cartItems={ [ cartItemWithImageNoAlt ] }
+			/>
+		);
+
+		const image = screen.getByTestId( 'product-image' );
+		expect( image ).toHaveAttribute( 'alt', 'Test Product' );
+	} );
+
+	it( 'correctly matches cart item by key', () => {
+		const cartItems: CartItem[] = [
+			{ ...mockCartItemWithoutImages, key: 'item-1' },
+			{ ...mockCartItemWithImages, key: 'test-item-key' },
+			{ ...mockCartItemWithoutImages, key: 'item-3' },
+		];
+
+		render(
+			<ShippingPackageItemIcon
+				packageItem={ mockPackageItem }
+				cartItems={ cartItems }
+			/>
+		);
+
+		const image = screen.getByTestId( 'product-image' );
+		expect( image ).toHaveAttribute(
+			'src',
+			'https://example.com/image1-thumb.jpg'
+		);
+	} );
+} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx
index 53cd50184e..031b28d05a 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx
@@ -5,15 +5,18 @@ import clsx from 'clsx';
 import { decodeEntities } from '@wordpress/html-entities';
 import { Panel } from '@woocommerce/blocks-components';
 import { useCallback, useEffect, useMemo, useState } from '@wordpress/element';
-import { useShippingData } from '@woocommerce/base-context/hooks';
+import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';
 import { sanitizeHTML } from '@woocommerce/sanitize';
 import { CartShippingPackageShippingRate } from '@woocommerce/types';
+import {
+	PackageItems,
+	ShippingPackageItemIcon,
+} from '@woocommerce/base-components/cart-checkout';

 /**
  * Internal dependencies
  */
 import PackageRates from './package-rates';
-import PackageItems from './package-items';
 import type { PackageProps } from './types';
 import './style.scss';

@@ -28,6 +31,7 @@ export const ShippingRatesControlPackage = ( {
 	highlightChecked = false,
 }: PackageProps ) => {
 	const { selectShippingRate, shippingRates } = useShippingData();
+	const { cartItems } = useStoreCart();

 	const internalPackageCount = shippingRates?.length || 1;

@@ -67,13 +71,17 @@ export const ShippingRatesControlPackage = ( {
 	);

 	// Collapsible and non-collapsible header handling.
-	const header =
-		shouldBeCollapsible || shouldShowItems ? (
+	let header = null;
+
+	if ( shouldBeCollapsible || shouldShowItems ) {
+		header = (
 			<div className="wc-block-components-shipping-rates-control__package-header">
 				<div
 					className="wc-block-components-shipping-rates-control__package-title"
 					dangerouslySetInnerHTML={ {
-						__html: sanitizeHTML( packageData.name ),
+						__html: sanitizeHTML(
+							String( packageData.name ?? '' )
+						),
 					} }
 				/>
 				{ shouldBeCollapsible && (
@@ -85,7 +93,27 @@ export const ShippingRatesControlPackage = ( {
 					<PackageItems packageData={ packageData } />
 				) }
 			</div>
-		) : null;
+		);
+
+		if ( multiplePackages ) {
+			const packageItems = packageData.items || [];
+
+			header = (
+				<div className="wc-block-components-shipping-rates-control__package-container">
+					{ header }
+					<div className="wc-block-components-shipping-rates-control__package-thumbnails">
+						{ packageItems.slice( 0, 3 ).map( ( item ) => (
+							<ShippingPackageItemIcon
+								key={ item.key }
+								packageItem={ item }
+								cartItems={ cartItems }
+							/>
+						) ) }
+					</div>
+				</div>
+			);
+		}
+	}

 	const onSelectRate = useCallback(
 		( newShippingRateId: string ) => {
@@ -111,6 +139,8 @@ export const ShippingRatesControlPackage = ( {
 			<Panel
 				className={ clsx(
 					'wc-block-components-shipping-rates-control__package',
+					multiplePackages &&
+						'wc-block-components-shipping-rates-control__package--multiple',
 					className
 				) }
 				// initialOpen remembers only the first value provided to it, so by the
@@ -129,6 +159,8 @@ export const ShippingRatesControlPackage = ( {
 		<div
 			className={ clsx(
 				'wc-block-components-shipping-rates-control__package',
+				multiplePackages &&
+					'wc-block-components-shipping-rates-control__package--multiple',
 				className
 			) }
 		>
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss
index 8b6323c379..addcf9567e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss
@@ -28,22 +28,77 @@
 		padding-bottom: em($gap-small);
 	}

-	.wc-block-components-radio-control,
-	.wc-block-components-radio-control__option-layout {
+	.wc-block-components-radio-control
+		.wc-block-components-radio-control__option-layout {
 		padding-bottom: 0;
 	}

-	.wc-block-components-radio-control
-		.wc-block-components-radio-control__option-layout {
+	.wc-block-components-radio-control__description,
+	.wc-block-components-radio-control__secondary-description {
+		@include font-small-locked;
+		width: 100%;
+		text-align: left;
+		margin: 0;
+		display: block;
+	}
+
+	.wc-block-components-panel__button[aria-expanded="true"] {
 		padding-bottom: 0;
+		margin-bottom: 0;
+	}
+
+	.wc-block-components-shipping-rates-control__package-header {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+
+		.wc-block-components-shipping-rates-control__package-header:last-child {
+			margin-bottom: 0;
+		}
 	}
 }

-.wc-block-components-shipping-rates-control__package-header {
-	margin: 0 0 $gap-small;
+.wc-block-components-shipping-rates-control__package-container {
+	display: flex;
+    width: 100%;
+    justify-content: space-between;

-	&:last-child {
-		margin-bottom: 0;
+	.wc-block-components-shipping-rates-control__package-thumbnails {
+		display: flex;
+        gap: $gap-smallest;
+		align-items: center;
+
+		> img {
+			object-fit: cover;
+			height: 40px;
+			width: 40px;
+		}
+
+		&:first-child {
+			margin-left: $gap-smallest;
+		}
+
+		@media screen and ( max-width: 480px ) {
+			img:nth-child(3) {
+				display: none;
+			}
+		}
+	}
+}
+
+.wc-block-components-shipping-rates-control__package--multiple {
+	@include card-styles();
+
+	.wc-block-components-panel__content,
+	.wc-block-components-radio-control,
+	.wc-block-components-radio-control__option-layout {
+		padding-bottom: 0;
+		width: 100%;
+	}
+
+	.wc-block-components-panel__button {
+		padding-bottom: 0;
+		padding-top: 0;
 	}
 }

diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/test/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/test/index.tsx
index c43ab5f906..bb5f3a3352 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/test/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/test/index.tsx
@@ -3,7 +3,7 @@
  */
 import { act, render, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { useShippingData } from '@woocommerce/base-context/hooks';
+import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';

 /**
  * Internal dependencies
@@ -43,6 +43,12 @@ test( 'renders available shipping rates', async () => {
 		};
 	} );

+	( useStoreCart as jest.Mock ).mockImplementation( () => {
+		return {
+			cartItems: [],
+		};
+	} );
+
 	render(
 		<ShippingRatesControlPackage
 			packageData={ testPackageData }
@@ -77,6 +83,12 @@ test( 'changes rate selection locally and informs API about it', async () => {
 		};
 	} );

+	( useStoreCart as jest.Mock ).mockImplementation( () => {
+		return {
+			cartItems: [],
+		};
+	} );
+
 	render(
 		<ShippingRatesControlPackage
 			packageData={ testPackageData }
@@ -134,6 +146,12 @@ test( 'upstream rate selection updates are properly reflected in local state', a
 		};
 	} );

+	( useStoreCart as jest.Mock ).mockImplementation( () => {
+		return {
+			cartItems: [],
+		};
+	} );
+
 	const { rerender } = render(
 		<ShippingRatesControlPackage
 			packageData={ packageData }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/address-card/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/address-card/style.scss
index d7f2f080b2..88946da68e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/address-card/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/address-card/style.scss
@@ -1,12 +1,5 @@
 .wc-block-components-address-card {
-	@include font-regular-locked;
-	border: 1px solid $universal-border-light;
-	padding: $gap;
-	margin: 0;
-	border-radius: $universal-border-radius;
-	display: flex;
-	justify-content: flex-start;
-	align-items: flex-start;
+	@include card-styles();

 	.has-dark-controls & {
 		border-color: $input-border-dark;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
index e0a66d28e3..5c9e217aa3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/block.tsx
@@ -209,12 +209,15 @@ const Block = () => {
 		renderPickupLocation,
 	};

+	const packageData = shippingRates[ 0 ] || null;
+
 	return (
 		<>
 			<ExperimentalOrderLocalPickupPackages.Slot { ...slotFillProps } />
 			<ExperimentalOrderLocalPickupPackages>
 				<LocalPickupSelect
-					title={ shippingRates[ 0 ].name }
+					title={ packageData?.name }
+					packageData={ packageData }
 					selectedOption={ selectedOption ?? '' }
 					renderPickupLocation={ renderPickupLocation }
 					pickupLocations={ pickupLocations }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/style.scss
index 0a20ee882b..4dda989fbb 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-pickup-options-block/style.scss
@@ -60,6 +60,7 @@

 		.wc-block-components-radio-control__description,
 		.wc-block-components-radio-control__secondary-description {
+			@include font-small-locked;
 			width: 100%;
 			text-align: left;
 			margin: 0;
@@ -71,8 +72,6 @@
 			align-items: center;
 			gap: $gap-smallest;

-			color: $gray-700;
-
 			.has-dark-controls & {
 				color: $gray-300;
 			}
@@ -81,7 +80,6 @@
 				fill: currentColor;
 				// Pixel perfect alignement needed as internal padding of the SVG
 				// is inconsistent (top one bigger that the bottom one).
-				margin-top: -2px;
 				margin-left: -3px;
 			}
 		}
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/style.scss
index 24cf3cbf1d..9a2b15a5fd 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/style.scss
@@ -5,6 +5,8 @@

 	.wc-block-components-shipping-rates-control__no-results-notice {
 		margin: 0;
+		width: 100%;
+		box-sizing: border-box;
 	}

 	.wc-block-components-shipping-rates-control__no-shipping-address-message {
@@ -14,6 +16,8 @@
 		color: color-mix(in srgb, currentColor 66%, transparent);
 		text-align: center;
 		margin: 0;
+		width: 100%;
+		box-sizing: border-box;
 		border-radius: $universal-border-radius;
 	}
 }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/styles/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/styles/style.scss
index f73d2645fd..c0a4faec47 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/styles/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/styles/style.scss
@@ -10,8 +10,10 @@
 		top: -96px;
 	}
 	.wc-block-components-shipping-rates-control__package {
-		border-bottom: 0;
-		margin: 0 0 1em;
+		margin: 0 0 $gap;
+		display: flex;
+		flex-direction: column;
+		gap: $gap-small;

 		&:last-child {
 			margin-bottom: 0;
diff --git a/plugins/woocommerce/client/blocks/assets/js/data/shared-controls.ts b/plugins/woocommerce/client/blocks/assets/js/data/shared-controls.ts
index 1bb608863b..1a13b2727d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/data/shared-controls.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/data/shared-controls.ts
@@ -127,7 +127,6 @@ export const apiFetchWithHeadersControl = ( options: APIFetchOptions ) =>

 // List of paths which should not be batched.
 const preventBatching = [
-	'/wc/store/v1/cart/select-shipping-rate',
 	'/wc/store/v1/checkout',
 	'/wc/store/v1/checkout?__experimental_calc_totals=true',
 ];