Repository: woocommerce/woocommerce
Pull request #57379: Add new Bank Details Table Component
Author: login: dmallory42
Labels: name: plugin: woocommerce
description: Issues related to the WooCommerce Core plugin.
name: needs: analysis
description: Indicates if the PR requires a PR testing scrub session.
Created: 2025-04-17T19:48:26Z | Merged: 2025-04-25T12:44:53Z

### Submission Review Guidelines:

-   I have followed the [WooCommerce Contributing Guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) and the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/).
-   I have checked to ensure there aren't other open [Pull Requests](https://github.com/woocommerce/woocommerce/pulls) for the same update/change.
-   I have reviewed my code for [security best practices](https://developer.wordpress.org/apis/security/).
-   Following the above guidelines will result in quick merges and clear and detailed feedback when appropriate.

<!-- You can erase any parts of this template not applicable to your Pull Request. -->

### Changes proposed in this Pull Request:

<!-- If necessary, indicate if this PR is part of a bigger feature. Add a label with the format `focus: name of the feature [team:name of the team]`. -->

<!-- Describe the changes made to this Pull Request and the reason for such changes. -->

<!-- For bug fixes: If known, please provide links to help with traceability and escape analysis. -->
<!-- Please include a link to the issue of the bug being fixed, if one doesn't exist please create it. -->
<!-- If the PR that introduced the bug is known, please also add its link below. -->

Closes WOOPMNT-4906

### Screenshots or screen recordings:

<!-- If this PR includes UI changes, please provide screenshots or a screen recording for clarity. -->
<!-- This section can be removed if not applicable. -->

#### New component

<img width="816" alt="Screenshot 2025-04-23 at 19 22 38" src="https://github.com/user-attachments/assets/1606fa7f-5993-42fb-ac69-33b41eb0cd8b" />

#### Add account modal

<img width="649" alt="Screenshot 2025-04-23 at 19 22 43" src="https://github.com/user-attachments/assets/c3fda026-4bb3-4eb1-b03a-0f19cd84e73b" />

#### Edit account modal

<img width="622" alt="Screenshot 2025-04-23 at 19 22 49" src="https://github.com/user-attachments/assets/168472d3-6d42-4116-a548-ef80968780a1" />

<!-- Begin testing instructions -->

### How to test the changes in this Pull Request:

<!-- Include detailed instructions on how these changes can be tested. Review and follow the guide for how to write high-quality testing instructions. -->

Using the [WooCommerce Testing Instructions Guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions), include your detailed testing instructions:

Note: This PR implements the component logic. There are a couple of things from the design not implemented yet, like the country toggle logic. These will be fixed in https://github.com/woocommerce/woocommerce/pull/56985 as they rely on ingesting the real data in order to fix properly. 

1. Checkout this branch.
2. Apply this patch: https://gist.github.com/dmallory42/d9c6a578c0939eda1699f8b40c5b33b7
3. Run the following command: `pnpm install --frozen-lockfile && pnpm build `
4. Ensure you have enabled the new payment settings experience.
5. Go to the Payment Settings page, then go to the offline methods list.
6. You should see the new component at the bottom of the offline methods page (this is only for testing) with some sample data.
7. Confirm that you can add, edit, and delete from the list. At the moment, dragging is not implemented properly. 
8. Confirm that the component broadly matches the design: LK697bTM8sHMU95BS4owef-fi-2804_54152 - if there are some inconsistencies, please call them out but as long as they are non-blocking I will likely implement them in https://github.com/woocommerce/woocommerce/pull/56985 and do a full design review at the end. 

<!-- End testing instructions -->

### Testing that has already taken place:

<!-- Detail any testing that has already been conducted. -->
<!-- Include environment details such as hosting type, plugins, theme, store size, store age, and relevant settings. -->
<!-- Mention any analysis performed, such as assessing potential impacts on environment attributes and other plugins, performance profiling, or LLM/AI-based analysis. -->
<!-- Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public pull request. -->

### Changelog entry

<!-- You can optionally choose to enter a changelog entry by checking the box below and supplying data. -->
<!-- It will trigger the 'Add changelog to PR' CI job to create and push the entry into the branch. -->

<!-- Due to org permissions, the job might fail for PRs crated from a fork under GitHub organizations. Possible solutions: -->
<!-- * Create entry manually with `pnpm --filter='@woocommerce/plugin-woocommerce' changelog add` and push it into the branch (replace `@woocommerce/plugin-woocommerce` with package name from nearest `package.json` file) -->
<!-- * Create entry from supplied PR data and push it automatically `pnpm utils changefile pr-number-here -o github-org-name-here` -->

-   [ ] Automatically create a changelog entry from the details below.

<!-- If no changelog entry is required for this PR, you can specify that below and provide a comment explaining why. This cannot be used if you selected the option to automatically create a changelog entry above. -->

-   [ ] This Pull Request does not require a changelog entry. (Comment required below)

<details>

<summary>Changelog Entry Details</summary>

#### Significance

<!-- Choose only one -->

-   [ ] Patch
-   [ ] Minor
-   [ ] Major

#### Type

<!-- Choose only one -->

-   [ ] Fix - Fixes an existing bug
-   [ ] Add - Adds functionality
-   [ ] Update - Update existing functionality
-   [ ] Dev - Development related task
-   [ ] Tweak - A minor adjustment to the codebase
-   [ ] Performance - Address performance issues
-   [ ] Enhancement - Improvement to existing functionality

#### Message <!-- Add a changelog message here -->

</details>

<details>

<summary>Changelog Entry Comment</summary>

#### Comment <!-- If your Pull Request doesn't require a changelog entry, a comment explaining why is required instead -->

</details>


Diff:
diff --git a/plugins/woocommerce/changelog/woopmnt-4906-payment-settings-offline-payment-methods-modernisation b/plugins/woocommerce/changelog/woopmnt-4906-payment-settings-offline-payment-methods-modernisation
new file mode 100644
index 0000000000000..ceaff2d28442f
--- /dev/null
+++ b/plugins/woocommerce/changelog/woopmnt-4906-payment-settings-offline-payment-methods-modernisation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Introduce new component to manage offline methods bank accounts.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-list.scss b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-list.scss
new file mode 100644
index 0000000000000..e966a1b7c28b0
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-list.scss
@@ -0,0 +1,110 @@
+.bank-accounts__list {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+	border: 1px solid #dcdcde;
+	border-radius: 4px;
+	overflow: hidden;
+}
+
+.bank-accounts__add-button {
+	display: flex;
+	justify-content: flex-end;
+	padding: 12px 16px;
+	border-top: 1px solid #e0e0e0;
+}
+
+.bank-accounts__list-header {
+	display: flex;
+	align-items: center;
+	padding: 12px 16px;
+	background-color: #f8f9fa;
+	border-bottom: 1px solid #dcdcde;
+	font-weight: 600;
+	font-size: 11px;
+	color: $gray-900;
+	text-transform: uppercase;
+	border-top: none;
+
+	> .bank-accounts__list-item-inner {
+		display: flex;
+		width: 100%;
+		align-items: center;
+
+		> .bank-accounts__list-item-before {
+			width: 24px;
+			height: 20px;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			margin-right: 16px;
+		}
+
+		> .bank-accounts__list-item-text {
+			display: grid;
+			grid-template-columns: 1fr 1fr 1fr;
+			flex-grow: 1;
+			column-gap: 24px;
+		}
+
+		> .bank-accounts__list-item-after {
+			width: 32px;
+			margin-left: 16px;
+		}
+	}
+}
+
+.bank-accounts__list-item {
+	display: flex;
+	align-items: center;
+	padding: 12px 16px;
+	border-top: 1px solid #e0e0e0;
+
+	&.first-item {
+		border-top: none;
+	}
+
+	&:not(.action):hover {
+		background-color: #f8f9fa;
+	}
+
+	&.action {
+		display: flex;
+		justify-content: flex-end;
+		padding: 12px 16px;
+	}
+
+	> .bank-accounts__list-item-inner {
+		display: flex;
+		width: 100%;
+		align-items: center;
+
+		> .bank-accounts__list-item-before {
+			width: 24px;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			margin-right: 16px;
+		}
+
+		> .bank-accounts__list-item-text {
+			display: grid;
+			grid-template-columns: 1fr 1fr 1fr;
+			flex-grow: 1;
+			column-gap: 24px;
+
+			div {
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+		}
+
+		> .bank-accounts__list-item-after {
+			display: flex;
+			justify-content: flex-end;
+			align-items: center;
+			margin-left: 16px;
+		}
+	}
+}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.scss b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.scss
new file mode 100644
index 0000000000000..02ae148277d6c
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.scss
@@ -0,0 +1,24 @@
+.bank-account-modal {
+	width: 512px;
+	max-width: 512px;
+	max-height: 90%;
+
+	&__description {
+		margin-top: 0;
+		margin-bottom: 24px;
+	}
+
+	&__field {
+		margin-bottom: 24px;
+	}
+
+	&__actions {
+		display: flex;
+		justify-content: flex-end;
+		margin-top: 16px;
+
+		> .bank-account-modal__save {
+			margin-left: 8px;
+		}
+	}
+}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.tsx
new file mode 100644
index 0000000000000..e8a32ab0b2044
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.tsx
@@ -0,0 +1,261 @@
+/**
+ * External dependencies
+ */
+import { Modal, TextControl, Button } from '@wordpress/components';
+import { useEffect, useState } from 'react';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { getDefaultRoutingField } from './utils';
+import { validateRequiredField, validateNumericField } from './validation';
+import './bank-account-modal.scss';
+
+/**
+ * Props for the BankAccountModal component.
+ *
+ * @property {BankAccount | null}             account        - The bank account to edit, or null to add a new account.
+ * @property {() => void}                     onClose        - Callback invoked when the modal should be closed.
+ * @property {(account: BankAccount) => void} onSave         - Callback invoked when the bank account is saved.
+ * @property {string}                         defaultCountry - The default country used to determine the routing field.
+ */
+interface Props {
+	account: BankAccount | null;
+	onClose: () => void;
+	onSave: ( account: BankAccount ) => void;
+	defaultCountry: string;
+}
+
+/**
+ * BankAccountModal component renders a modal dialog for adding or editing a bank account.
+ * It manages form state, validation, and invokes callbacks to save or close the modal.
+ *
+ * @param {Props} props - Component props.
+ * @return {Element} The rendered modal component.
+ */
+export const BankAccountModal = ( {
+	account,
+	onClose,
+	onSave,
+	defaultCountry,
+}: Props ) => {
+	const [ formData, setFormData ] = useState< BankAccount >(
+		account || {
+			id: '',
+			account_name: '',
+			account_number: '',
+			bank_name: '',
+			routing_number: '',
+			sort_code: '',
+			iban: '',
+			bic: '',
+		}
+	);
+	const [ routingField, setRoutingField ] = useState<
+		'routing_number' | 'sort_code' | 'iban'
+	>( 'iban' );
+
+	const [ errors, setErrors ] = useState<
+		Partial< Record< keyof BankAccount, string > >
+	>( {} );
+
+	/**
+	 * Validates the form fields and sets error messages accordingly.
+	 *
+	 * @return {boolean} True if the form is valid, false otherwise.
+	 */
+	const validate = () => {
+		const newErrors: Partial< Record< keyof BankAccount, string > > = {};
+
+		newErrors.account_name = validateRequiredField( formData.account_name );
+		newErrors.account_number =
+			validateRequiredField( formData.account_number ) ||
+			validateNumericField( formData.account_number );
+
+		if ( routingField === 'routing_number' ) {
+			newErrors.routing_number = validateRequiredField(
+				formData.routing_number
+			);
+		}
+
+		if ( routingField === 'sort_code' ) {
+			newErrors.sort_code = validateRequiredField( formData.sort_code );
+		}
+
+		if ( routingField === 'iban' ) {
+			newErrors.iban = validateRequiredField( formData.iban );
+		}
+
+		newErrors.bic = validateRequiredField( formData.bic );
+
+		const filteredErrors = Object.fromEntries(
+			Object.entries( newErrors ).filter( ( [ , v ] ) => v )
+		);
+		setErrors( filteredErrors );
+
+		return Object.keys( filteredErrors ).length === 0;
+	};
+
+	useEffect( () => {
+		if ( account ) {
+			if ( account.routing_number ) setRoutingField( 'routing_number' );
+			else if ( account.sort_code ) setRoutingField( 'sort_code' );
+			else if ( account.iban ) setRoutingField( 'iban' );
+			else setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		} else {
+			setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		}
+	}, [ account, defaultCountry ] );
+
+	/**
+	 * Updates a specific field in the form data state.
+	 *
+	 * @param {keyof BankAccount} field - The field name to update.
+	 * @param {string}            value - The new value for the field.
+	 */
+	const updateField = ( field: keyof BankAccount, value: string ) => {
+		setFormData( ( prev ) => ( { ...prev, [ field ]: value } ) );
+	};
+
+	return (
+		<Modal
+			className="bank-account-modal"
+			title={
+				account
+					? __( 'Edit bank account', 'woocommerce' )
+					: __( 'Add a bank account', 'woocommerce' )
+			}
+			onRequestClose={ onClose }
+			shouldCloseOnClickOutside={ false }
+		>
+			<p className={ 'bank-account-modal__description' }>
+				{ account
+					? __( 'Edit your bank account details.', 'woocommerce' )
+					: __( 'Add your bank account details.', 'woocommerce' ) }
+			</p>
+
+			<TextControl
+				className={ 'bank-account-modal__field' }
+				label={ __( 'Account Name *', 'woocommerce' ) }
+				required
+				value={ formData.account_name }
+				onChange={ ( value ) => updateField( 'account_name', value ) }
+				help={
+					errors.account_name ? (
+						<span className="bank-account-modal__error">
+							{ errors.account_name }
+						</span>
+					) : undefined
+				}
+			/>
+
+			<TextControl
+				className={ 'bank-account-modal__field' }
+				label={ __( 'Account Number *', 'woocommerce' ) }
+				required
+				value={ formData.account_number }
+				onChange={ ( value ) => updateField( 'account_number', value ) }
+				help={
+					errors.account_number ? (
+						<span className="bank-account-modal__error">
+							{ errors.account_number }
+						</span>
+					) : undefined
+				}
+			/>
+
+			<TextControl
+				className={ 'bank-account-modal__field' }
+				label={ __( 'Bank Name', 'woocommerce' ) }
+				value={ formData.bank_name }
+				onChange={ ( value ) => updateField( 'bank_name', value ) }
+			/>
+
+			{ routingField === 'routing_number' && (
+				<TextControl
+					className={ 'bank-account-modal__field' }
+					label={ __( 'Routing Number', 'woocommerce' ) }
+					required
+					value={ formData.routing_number }
+					onChange={ ( value ) =>
+						updateField( 'routing_number', value )
+					}
+					help={
+						errors.routing_number ? (
+							<span className="bank-account-modal__error">
+								{ errors.routing_number }
+							</span>
+						) : undefined
+					}
+				/>
+			) }
+
+			{ routingField === 'sort_code' && (
+				<TextControl
+					className={ 'bank-account-modal__field' }
+					label={ __( 'BSB', 'woocommerce' ) }
+					required
+					value={ formData.sort_code }
+					onChange={ ( value ) => updateField( 'sort_code', value ) }
+					help={
+						errors.sort_code ? (
+							<span className="bank-account-modal__error">
+								{ errors.sort_code }
+							</span>
+						) : undefined
+					}
+				/>
+			) }
+
+			{ routingField === 'iban' && (
+				<TextControl
+					className={ 'bank-account-modal__field' }
+					label={ __( 'IBAN', 'woocommerce' ) }
+					required
+					value={ formData.iban }
+					onChange={ ( value ) => updateField( 'iban', value ) }
+					help={
+						errors.iban ? (
+							<span className="bank-account-modal__error">
+								{ errors.iban }
+							</span>
+						) : undefined
+					}
+				/>
+			) }
+
+			<TextControl
+				className={ 'bank-account-modal__field' }
+				label={ __( 'BIC / SWIFT', 'woocommerce' ) }
+				value={ formData.bic }
+				onChange={ ( value ) => updateField( 'bic', value ) }
+				help={
+					errors.bic ? (
+						<span className="bank-account-modal__error">
+							{ errors.bic }
+						</span>
+					) : undefined
+				}
+			/>
+
+			<div className={ 'bank-account-modal__actions' }>
+				<Button variant={ 'tertiary' } onClick={ onClose }>
+					{ __( 'Cancel', 'woocommerce' ) }
+				</Button>
+				<Button
+					className={ 'bank-account-modal__save' }
+					variant={ 'primary' }
+					onClick={ () => {
+						if ( validate() ) {
+							onSave( formData );
+						}
+					} }
+				>
+					{ __( 'Save', 'woocommerce' ) }
+				</Button>
+			</div>
+		</Modal>
+	);
+};
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
new file mode 100644
index 0000000000000..c6f94dee136ce
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
@@ -0,0 +1,239 @@
+/**
+ * External dependencies
+ */
+import { Button, MenuGroup, MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { EllipsisMenu } from '@woocommerce/components';
+import { useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { BankAccountModal } from './bank-account-modal';
+import {
+	DefaultDragHandle,
+	SortableContainer,
+	SortableItem,
+} from '~/settings-payments/components/sortable';
+import './bank-account-list.scss';
+
+/**
+ * Generates a random string ID for new bank accounts.
+ *
+ * @return {string} A unique identifier string.
+ */
+function generateId() {
+	return Math.random().toString( 36 ).substring( 2, 10 );
+}
+
+/**
+ * Props for BankAccountsList component.
+ *
+ */
+interface Props {
+	initialAccounts: BankAccount[];
+	onChange: ( accounts: BankAccount[] ) => void;
+	updateOrdering: ( accounts: BankAccount[] ) => void;
+	defaultCountry: string;
+}
+
+/**
+ * BankAccountsList component renders a sortable list of bank accounts,
+ * allowing users to add, edit, reorder, and delete accounts.
+ *
+ * @param {Props} props Component props.
+ * @return {Element} The rendered component.
+ */
+export const BankAccountsList = ( {
+	initialAccounts,
+	onChange,
+	updateOrdering,
+	defaultCountry,
+}: Props ) => {
+	const [ accounts, setAccounts ] = useState( initialAccounts );
+	const [ selectedAccount, setSelectedAccount ] =
+		useState< BankAccount | null >( null );
+	const [ isModalOpen, setIsModalOpen ] = useState( false );
+	const [ accountToDelete, setAccountToDelete ] =
+		useState< BankAccount | null >( null );
+
+	/**
+	 * Opens the bank account modal for adding or editing an account.
+	 *
+	 * @param {BankAccount | null} account The account to edit, or null to add a new one.
+	 */
+	const openModal = ( account: BankAccount | null = null ) => {
+		setSelectedAccount( account );
+		setIsModalOpen( true );
+	};
+
+	/**
+	 * Handles saving of a bank account, either updating an existing one or adding a new one.
+	 *
+	 * @param {BankAccount} updated The updated or new bank account.
+	 */
+	const handleSave = ( updated: BankAccount ) => {
+		const newAccounts = accounts.some( ( acc ) => acc.id === updated.id )
+			? accounts.map( ( acc ) =>
+					acc.id === updated.id ? updated : acc
+			  )
+			: [ ...accounts, { ...updated, id: generateId() } ];
+		setAccounts( newAccounts );
+		onChange( newAccounts );
+		setIsModalOpen( false );
+	};
+
+	/**
+	 * Confirms and deletes the selected bank account.
+	 */
+	const confirmDelete = () => {
+		if ( ! accountToDelete ) return;
+		const newAccounts = accounts.filter(
+			( acc ) => acc.id !== accountToDelete.id
+		);
+		setAccounts( newAccounts );
+		onChange( newAccounts );
+		setAccountToDelete( null );
+	};
+
+	/**
+	 * Updates the ordering of bank accounts after drag-and-drop sorting.
+	 *
+	 * @param {BankAccount[]} newAccounts The reordered list of bank accounts.
+	 */
+	const handleUpdateOrdering = ( newAccounts: BankAccount[] ) => {
+		setAccounts( newAccounts );
+		updateOrdering( newAccounts );
+	};
+
+	return (
+		<>
+			<SortableContainer< BankAccount >
+				items={ accounts }
+				className={ 'bank-accounts__list' }
+				setItems={ handleUpdateOrdering }
+			>
+				<div className="bank-accounts__list-header">
+					<div className="bank-accounts__list-item-inner">
+						<div className="bank-accounts__list-item-before" />
+						<div className="bank-accounts__list-item-text">
+							<div>Account Name</div>
+							<div>Account Number</div>
+							<div>Bank Name</div>
+						</div>
+						<div className="bank-accounts__list-item-after" />
+					</div>
+				</div>
+				{ accounts.map( ( account, index ) => (
+					<SortableItem
+						key={ account.id }
+						id={ account.id }
+						className={ `bank-accounts__list-item${
+							index === 0 ? ' first-item' : ''
+						}` }
+					>
+						<div className="bank-accounts__list-item-inner">
+							<div className="bank-accounts__list-item-before">
+								<DefaultDragHandle />
+							</div>
+							<div className="bank-accounts__list-item-text">
+								<div>{ account.account_name }</div>
+								<div>{ account.account_number }</div>
+								<div>{ account.bank_name }</div>
+							</div>
+							<div className="bank-accounts__list-item-after">
+								<EllipsisMenu
+									label={ __( 'Options', 'woocommerce' ) }
+									placement={ 'bottom-right' }
+									renderContent={ () => (
+										<MenuGroup>
+											<MenuItem
+												role={ 'menuitem' }
+												onClick={ () =>
+													openModal( account )
+												}
+											>
+												{ __(
+													'View / edit',
+													'woocommerce'
+												) }
+											</MenuItem>
+											<MenuItem
+												isDestructive
+												onClick={ () =>
+													setAccountToDelete(
+														account
+													)
+												}
+											>
+												{ __(
+													'Delete',
+													'woocommerce'
+												) }
+											</MenuItem>
+										</MenuGroup>
+									) }
+								/>
+							</div>
+						</div>
+					</SortableItem>
+				) ) }
+				<li className="bank-accounts__list-item action">
+					<Button
+						variant={ 'secondary' }
+						onClick={ () => openModal( null ) }
+					>
+						{ __( '+ Add account', 'woocommerce' ) }
+					</Button>
+				</li>
+			</SortableContainer>
+
+			{ isModalOpen && (
+				<BankAccountModal
+					account={ selectedAccount }
+					onClose={ () => setIsModalOpen( false ) }
+					onSave={ handleSave }
+					defaultCountry={ defaultCountry }
+				/>
+			) }
+
+			{ accountToDelete && (
+				<Modal
+					title={ __( 'Delete account', 'woocommerce' ) }
+					onRequestClose={ () => setAccountToDelete( null ) }
+					shouldCloseOnClickOutside={ false }
+				>
+					<p>
+						{ __(
+							'Are you sure you want to delete this bank account?',
+							'woocommerce'
+						) }
+					</p>
+					<div
+						style={ {
+							display: 'flex',
+							justifyContent: 'flex-end',
+							gap: '8px',
+							marginTop: '16px',
+						} }
+					>
+						<Button
+							variant="secondary"
+							onClick={ () => setAccountToDelete( null ) }
+						>
+							{ __( 'Cancel', 'woocommerce' ) }
+						</Button>
+						<Button
+							variant="primary"
+							isDestructive
+							onClick={ confirmDelete }
+						>
+							{ __( 'Delete', 'woocommerce' ) }
+						</Button>
+					</div>
+				</Modal>
+			) }
+		</>
+	);
+};
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/index.ts b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/index.ts
new file mode 100644
index 0000000000000..d99372b148cbd
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/index.ts
@@ -0,0 +1 @@
+export { BankAccountsList } from './bank-accounts-list';
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/types.ts b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/types.ts
new file mode 100644
index 0000000000000..0351deddd7680
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/types.ts
@@ -0,0 +1,10 @@
+export interface BankAccount {
+	id: string;
+	account_name: string;
+	account_number: string;
+	bank_name: string;
+	routing_number: string;
+	sort_code: string;
+	iban: string;
+	bic: string;
+}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/utils.ts b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/utils.ts
new file mode 100644
index 0000000000000..17a314f762e65
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/utils.ts
@@ -0,0 +1,10 @@
+/**
+ * Determines the default routing field based on the country.
+ *
+ * @param country The selected country code.
+ */
+export const getDefaultRoutingField = ( country: string ) => {
+	if ( country === 'US' ) return 'routing_number';
+	if ( country === 'AU' ) return 'sort_code';
+	return 'iban';
+};
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/validation.ts b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/validation.ts
new file mode 100644
index 0000000000000..d284db8f2d6d8
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/validation.ts
@@ -0,0 +1,15 @@
+/**
+ * Returns an error message if the input is empty.
+ */
+export const validateRequiredField = ( value: string ): string | undefined => {
+	return value.trim() === '' ? 'This field is required.' : undefined;
+};
+
+/**
+ * Returns an error message if the input is not numeric.
+ */
+export const validateNumericField = ( value: string ): string | undefined => {
+	return /^\d+$/.test( value.trim() )
+		? undefined
+		: 'This field must be numeric.';
+};


Reviews:
user: login: oaratovskyi
body: @dmallory42 thanks for adding this !

I noticed a couple of things, feel free to address some of those in this issue and some of them in another one. I'd address at least validation + required fields here. Maybe there are some low hanging fruits in design too. Let me know WDYT

1. There is no validation when adding editing the data. We can add empty row or data like `sdf` everywhere.
1. Account name and account number should be with `*` as required fields.
1. The modal design spacings are incorrect. 
1. Save button should be disabled when no required info is provided. Cancel button styling is different.
1. Modal max width is different.
![image](https://github.com/user-attachments/assets/cd9b1cdc-90d4-4060-900d-795f82866b39)
1. Ellipsis items (View/edit) are cut. Also if you click view/edit, the ellipsis context menu isn't closed. I think the behavior and styling should be consistent to the NOX settings page.
![image](https://github.com/user-attachments/assets/8fbf9c60-f01a-43f8-b14b-d4f53b8eac61)

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T09:29:21Z
user: login: oaratovskyi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T09:42:25Z
user: login: dmallory42
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T20:47:16Z
user: login: dmallory42
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T20:47:57Z
user: login: oaratovskyi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T08:13:22Z
user: login: oaratovskyi
body: Thanks @dmallory42 , looks much better now! :shipit: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T09:21:48Z

Review comments:
diff_hunk: @@ -0,0 +1,147 @@
+/**
+ * External dependencies
+ */
+import { Modal, TextControl, Button } from '@wordpress/components';
+import { useEffect, useState } from 'react';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { getDefaultRoutingField } from './utils';
+import './bank-account-modal.scss';
+
+interface Props {
+	account: BankAccount | null;
+	onClose: () => void;
+	onSave: ( account: BankAccount ) => void;
+	defaultCountry: string;
+}
+
+export const BankAccountModal = ( {
+	account,
+	onClose,
+	onSave,
+	defaultCountry,
+}: Props ) => {
+	const [ formData, setFormData ] = useState< BankAccount >(
+		account || {
+			id: '',
+			account_name: '',
+			account_number: '',
+			bank_name: '',
+			routing_number: '',
+			sort_code: '',
+			iban: '',
+			bic: '',
+		}
+	);
+	const [ routingField, setRoutingField ] = useState<
+		'routing_number' | 'sort_code' | 'iban'
+	>( 'iban' );
+
+	useEffect( () => {
+		if ( account ) {
+			if ( account.routing_number ) setRoutingField( 'routing_number' );
+			else if ( account.sort_code ) setRoutingField( 'sort_code' );
+			else if ( account.iban ) setRoutingField( 'iban' );
+			else setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		} else {
+			setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		}
+	}, [ account, defaultCountry ] );
+
+	const updateField = ( field: keyof BankAccount, value: string ) => {
+		setFormData( ( prev ) => ( { ...prev, [ field ]: value } ) );
+	};
+
+	return (
+		<Modal
+			className="bank-account-modal"
+			title={
+				account
+					? __( 'Edit bank account', 'woocommerce' )
+					: __( 'Add a bank account', 'woocommerce' )
+			}
+			onRequestClose={ onClose }
+			shouldCloseOnClickOutside={ false }
+		>
+			<p>{ __( 'Add your bank account details.', 'woocommerce' ) }</p>
+
+			<TextControl
+				label={ __( 'Account Name', 'woocommerce' ) }
+				required
+				value={ formData.account_name }
+				onChange={ ( value ) => updateField( 'account_name', value ) }
+			/>
+
+			<TextControl
+				label={ __( 'Account Number', 'woocommerce' ) }
+				required
+				value={ formData.account_number }
+				onChange={ ( value ) => updateField( 'account_number', value ) }
+			/>
+
+			<TextControl
+				label={ __( 'Bank Name', 'woocommerce' ) }
+				value={ formData.bank_name }
+				onChange={ ( value ) => updateField( 'bank_name', value ) }
+			/>
+
+			{ routingField === 'routing_number' && (
+				<TextControl
+					label={ __( 'Routing Number', 'woocommerce' ) }
+					required
+					value={ formData.routing_number }
+					onChange={ ( value ) =>
+						updateField( 'routing_number', value )
+					}
+				/>
+			) }
+
+			{ routingField === 'sort_code' && (
+				<TextControl
+					label={ __( 'BSB', 'woocommerce' ) }
+					required
+					value={ formData.sort_code }
+					onChange={ ( value ) => updateField( 'sort_code', value ) }
+				/>
+			) }
+
+			{ routingField === 'iban' && (
+				<TextControl
+					label={ __( 'IBAN', 'woocommerce' ) }
+					required
+					value={ formData.iban }
+					onChange={ ( value ) => updateField( 'iban', value ) }
+				/>
+			) }
+
+			<TextControl
+				label={ __( 'BIC / SWIFT', 'woocommerce' ) }
+				value={ formData.bic }
+				onChange={ ( value ) => updateField( 'bic', value ) }
+			/>
+
+			<div
+				style={ {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.tsx
user: login: oaratovskyi
body: could we move this into the .scss file?
created_at: 2025-04-24T09:39:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,204 @@
+/**
+ * External dependencies
+ */
+import { Button, MenuGroup, MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { EllipsisMenu } from '@woocommerce/components';
+import { useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { BankAccountModal } from './bank-account-modal';
+import {
+	DefaultDragHandle,
+	SortableContainer,
+	SortableItem,
+} from '~/settings-payments/components/sortable';
+import './bank-account-list.scss';
+
+function generateId() {
+	return Math.random().toString( 36 ).substring( 2, 10 );
+}
+interface Props {
+	initialAccounts: BankAccount[];
+	onChange: ( accounts: BankAccount[] ) => void;
+	updateOrdering: ( accounts: BankAccount[] ) => void;
+	defaultCountry: string;
+}
+
+export const BankAccountsList = ( {
+	initialAccounts,
+	onChange,
+	updateOrdering,
+	defaultCountry,
+}: Props ) => {
+	const [ accounts, setAccounts ] = useState( initialAccounts );
+	const [ selectedAccount, setSelectedAccount ] =
+		useState< BankAccount | null >( null );
+	const [ isModalOpen, setIsModalOpen ] = useState( false );
+	const [ accountToDelete, setAccountToDelete ] =
+		useState< BankAccount | null >( null );
+
+	const openModal = ( account: BankAccount | null = null ) => {
+		setSelectedAccount( account );
+		setIsModalOpen( true );
+	};
+
+	const handleSave = ( updated: BankAccount ) => {
+		const newAccounts = accounts.some( ( acc ) => acc.id === updated.id )
+			? accounts.map( ( acc ) =>
+					acc.id === updated.id ? updated : acc
+			  )
+			: [ ...accounts, { ...updated, id: generateId() } ];
+		setAccounts( newAccounts );
+		onChange( newAccounts );
+		setIsModalOpen( false );
+	};
+
+	const confirmDelete = () => {
+		if ( ! accountToDelete ) return;
+		const newAccounts = accounts.filter(
+			( acc ) => acc.id !== accountToDelete.id
+		);
+		setAccounts( newAccounts );
+		onChange( newAccounts );
+		setAccountToDelete( null );
+	};
+
+	const handleUpdateOrdering = ( newAccounts: BankAccount[] ) => {
+		setAccounts( newAccounts );
+		updateOrdering( newAccounts );
+	};
+
+	return (
+		<>
+			<SortableContainer< BankAccount >
+				items={ accounts }
+				className={ 'bank-accounts__list' }
+				setItems={ handleUpdateOrdering }
+			>
+				<div className="bank-accounts__list-header">
+					<div className="bank-accounts__list-item-inner">
+						<div className="bank-accounts__list-item-before" />
+						<div className="bank-accounts__list-item-text">
+							<div>Account Name</div>
+							<div>Account Number</div>
+							<div>Bank Name</div>
+						</div>
+						<div className="bank-accounts__list-item-after" />
+					</div>
+				</div>
+				{ accounts.map( ( account, index ) => (
+					<SortableItem
+						key={ account.id }
+						id={ account.id }
+						className={ `bank-accounts__list-item${
+							index === 0 ? ' first-item' : ''
+						}` }
+					>
+						<div className="bank-accounts__list-item-inner">
+							<div className="bank-accounts__list-item-before">
+								<DefaultDragHandle />
+							</div>
+							<div className="bank-accounts__list-item-text">
+								<div>{ account.account_name }</div>
+								<div>{ account.account_number }</div>
+								<div>{ account.bank_name }</div>
+							</div>
+							<div className="bank-accounts__list-item-after">
+								<EllipsisMenu
+									label={ __( 'Options', 'woocommerce' ) }
+									placement={ 'bottom-right' }
+									renderContent={ () => (
+										<MenuGroup>
+											<MenuItem
+												role={ 'menuitem' }
+												onClick={ () =>
+													openModal( account )
+												}
+											>
+												{ __(
+													'View / edit',
+													'woocommerce'
+												) }
+											</MenuItem>
+											<MenuItem
+												isDestructive
+												onClick={ () =>
+													setAccountToDelete(
+														account
+													)
+												}
+											>
+												{ __(
+													'Delete',
+													'woocommerce'
+												) }
+											</MenuItem>
+										</MenuGroup>
+									) }
+								/>
+							</div>
+						</div>
+					</SortableItem>
+				) ) }
+				<li className="bank-accounts__list-item action">
+					<Button
+						variant={ 'secondary' }
+						onClick={ () => openModal( null ) }
+					>
+						{ __( '+ Add account', 'woocommerce' ) }
+					</Button>
+				</li>
+			</SortableContainer>
+
+			{ isModalOpen && (
+				<BankAccountModal
+					account={ selectedAccount }
+					onClose={ () => setIsModalOpen( false ) }
+					onSave={ handleSave }
+					defaultCountry={ defaultCountry }
+				/>
+			) }
+
+			{ accountToDelete && (
+				<Modal
+					title={ __( 'Delete account', 'woocommerce' ) }
+					onRequestClose={ () => setAccountToDelete( null ) }
+					shouldCloseOnClickOutside={ false }
+				>
+					<p>
+						{ __(
+							'Are you sure you want to delete this bank account?',
+							'woocommerce'
+						) }
+					</p>
+					<div
+						style={ {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
user: login: oaratovskyi
body: could we move this into .scss?
created_at: 2025-04-24T09:41:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,204 @@
+/**
+ * External dependencies
+ */
+import { Button, MenuGroup, MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { EllipsisMenu } from '@woocommerce/components';
+import { useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { BankAccountModal } from './bank-account-modal';
+import {
+	DefaultDragHandle,
+	SortableContainer,
+	SortableItem,
+} from '~/settings-payments/components/sortable';
+import './bank-account-list.scss';
+
+function generateId() {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
user: login: oaratovskyi
body: Suggestion: add comment about what ID we are generating here 🙂 
created_at: 2025-04-24T09:42:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,147 @@
+/**
+ * External dependencies
+ */
+import { Modal, TextControl, Button } from '@wordpress/components';
+import { useEffect, useState } from 'react';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { getDefaultRoutingField } from './utils';
+import './bank-account-modal.scss';
+
+interface Props {
+	account: BankAccount | null;
+	onClose: () => void;
+	onSave: ( account: BankAccount ) => void;
+	defaultCountry: string;
+}
+
+export const BankAccountModal = ( {
+	account,
+	onClose,
+	onSave,
+	defaultCountry,
+}: Props ) => {
+	const [ formData, setFormData ] = useState< BankAccount >(
+		account || {
+			id: '',
+			account_name: '',
+			account_number: '',
+			bank_name: '',
+			routing_number: '',
+			sort_code: '',
+			iban: '',
+			bic: '',
+		}
+	);
+	const [ routingField, setRoutingField ] = useState<
+		'routing_number' | 'sort_code' | 'iban'
+	>( 'iban' );
+
+	useEffect( () => {
+		if ( account ) {
+			if ( account.routing_number ) setRoutingField( 'routing_number' );
+			else if ( account.sort_code ) setRoutingField( 'sort_code' );
+			else if ( account.iban ) setRoutingField( 'iban' );
+			else setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		} else {
+			setRoutingField( getDefaultRoutingField( defaultCountry ) );
+		}
+	}, [ account, defaultCountry ] );
+
+	const updateField = ( field: keyof BankAccount, value: string ) => {
+		setFormData( ( prev ) => ( { ...prev, [ field ]: value } ) );
+	};
+
+	return (
+		<Modal
+			className="bank-account-modal"
+			title={
+				account
+					? __( 'Edit bank account', 'woocommerce' )
+					: __( 'Add a bank account', 'woocommerce' )
+			}
+			onRequestClose={ onClose }
+			shouldCloseOnClickOutside={ false }
+		>
+			<p>{ __( 'Add your bank account details.', 'woocommerce' ) }</p>
+
+			<TextControl
+				label={ __( 'Account Name', 'woocommerce' ) }
+				required
+				value={ formData.account_name }
+				onChange={ ( value ) => updateField( 'account_name', value ) }
+			/>
+
+			<TextControl
+				label={ __( 'Account Number', 'woocommerce' ) }
+				required
+				value={ formData.account_number }
+				onChange={ ( value ) => updateField( 'account_number', value ) }
+			/>
+
+			<TextControl
+				label={ __( 'Bank Name', 'woocommerce' ) }
+				value={ formData.bank_name }
+				onChange={ ( value ) => updateField( 'bank_name', value ) }
+			/>
+
+			{ routingField === 'routing_number' && (
+				<TextControl
+					label={ __( 'Routing Number', 'woocommerce' ) }
+					required
+					value={ formData.routing_number }
+					onChange={ ( value ) =>
+						updateField( 'routing_number', value )
+					}
+				/>
+			) }
+
+			{ routingField === 'sort_code' && (
+				<TextControl
+					label={ __( 'BSB', 'woocommerce' ) }
+					required
+					value={ formData.sort_code }
+					onChange={ ( value ) => updateField( 'sort_code', value ) }
+				/>
+			) }
+
+			{ routingField === 'iban' && (
+				<TextControl
+					label={ __( 'IBAN', 'woocommerce' ) }
+					required
+					value={ formData.iban }
+					onChange={ ( value ) => updateField( 'iban', value ) }
+				/>
+			) }
+
+			<TextControl
+				label={ __( 'BIC / SWIFT', 'woocommerce' ) }
+				value={ formData.bic }
+				onChange={ ( value ) => updateField( 'bic', value ) }
+			/>
+
+			<div
+				style={ {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-account-modal.tsx
user: login: dmallory42
body: Yes, good catch! Updated.
created_at: 2025-04-24T20:47:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,204 @@
+/**
+ * External dependencies
+ */
+import { Button, MenuGroup, MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { EllipsisMenu } from '@woocommerce/components';
+import { useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { BankAccountModal } from './bank-account-modal';
+import {
+	DefaultDragHandle,
+	SortableContainer,
+	SortableItem,
+} from '~/settings-payments/components/sortable';
+import './bank-account-list.scss';
+
+function generateId() {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
user: login: dmallory42
body: This code will end up getting removed when I move onto implementing this in the offline methods, as we'll rely on the internal IDs 🙂 
created_at: 2025-04-24T20:47:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,204 @@
+/**
+ * External dependencies
+ */
+import { Button, MenuGroup, MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { EllipsisMenu } from '@woocommerce/components';
+import { useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { BankAccount } from './types';
+import { BankAccountModal } from './bank-account-modal';
+import {
+	DefaultDragHandle,
+	SortableContainer,
+	SortableItem,
+} from '~/settings-payments/components/sortable';
+import './bank-account-list.scss';
+
+function generateId() {
path: plugins/woocommerce/client/admin/client/settings-payments/components/bank-accounts-list/bank-accounts-list.tsx
user: login: oaratovskyi
body: Gotcha, thanks!
created_at: 2025-04-25T08:13:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-23T18:31:00Z
body: ## Testing Guidelines
Hi @oaratovskyi ,

Apart from reviewing the code changes, please make sure to **review the testing instructions** ([Guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions)) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed.

**Reminder:** PR reviewers are required to document testing performed. This includes:
- 🖼️ **Screenshots or screen recordings**.
- 📝 **List of functionality tested / steps followed**.
- 🌐 **Site details** (environment attributes such as hosting type, plugins, theme, store size, store age, and relevant settings).
- 🔍 **Any analysis performed**, such as assessing potential impacts on environment attributes and other plugins, conducting performance profiling, or using LLM/AI-based analysis.

⚠️ Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public issue.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-23T18:36:47Z
body: **Size Change:** -1.09 kB (-0.02%) 

**Total Size:** 5.88 MB

| Filename | Size | Change |  |
| :--- | :---: | :---: | :---: |
| `./plugins/woocommerce/client/admin/build/wp-admin-scripts/settings-embed.js` | 372 kB | +448 B (+0.12%) |  |
| `./plugins/woocommerce/client/blocks/build/add-to-cart-with-options-grouped-product-selector-item-cta.js` | 2.11 kB | -122 B (-5.47%) | ✅ |
| `./plugins/woocommerce/client/blocks/build/add-to-cart-with-options-quantity-selector.js` | 1.74 kB | -649 B (-27.14%) | 🎉 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-quantity-selector-editor-rtl.css` | 0 B | -199 B (removed) | 🏆 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-quantity-selector-editor.css` | 0 B | -197 B (removed) | 🏆 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-quantity-selector-style-rtl.css` | 0 B | -391 B (removed) | 🏆 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-quantity-selector-style.css` | 0 B | -391 B (removed) | 🏆 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-quantity-selector.js` | 0 B | -620 B (removed) | 🏆 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-style-rtl.css` | 450 B | +429 B (+2042.86%) | 🆘 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options-style.css` | 449 B | +428 B (+2038.1%) | 🆘 |
| `./plugins/woocommerce/client/blocks/build/woocommerce/add-to-cart-with-options.js` | 980 B | +271 B (+38.22%) | 🚨 |

<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-23T18:40:00Z
body: 
## Test using WordPress Playground
The changes in this pull request can be previewed and tested using a [WordPress Playground](https://developer.wordpress.org/playground/) instance.
[WordPress Playground](https://developer.wordpress.org/playground/) is an experimental project that creates a full WordPress instance entirely within the browser.

[Test this pull request with WordPress Playground](https://playground.wordpress.net/#{"landingPage":"/wp-admin/admin.php?page=wc-admin","preferredVersions":{"php":"8.0","wp":"latest"},"phpExtensionBundles":["kitchen-sink"],"features":{"networking":true},"steps":[{"step":"installPlugin","pluginZipFile":{"resource":"url","url":"https://playground.wordpress.net/plugin-proxy.php?org=woocommerce&repo=woocommerce&workflow=Build%20Live%20Branch&artifact=plugins-14664641292&pr=57379"},"options":{"activate":true}},{"step":"installPlugin","pluginZipFile":{"resource":"url","url":"https://github-proxy.com/https://github.com/woocommerce/woocommerce/releases/download/wc-beta-tester-2.3.1/woocommerce-beta-tester.zip"},"options":{"activate":true}},{"step":"login","username":"admin","password":"password"}],"plugins":[]}).

Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a new commit.

author_association: CONTRIBUTOR