Commit fca07e30513 for woocommerce

commit fca07e30513d4dd9205f6e2cce7835548ff5a95d
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Fri May 15 12:51:03 2026 +0200

    Update product list header layout (#64994)

    * Update product list header layout

    * improve css

    * lint code

diff --git a/packages/js/experimental-products-app/changelog/update-product-list-header-layout b/packages/js/experimental-products-app/changelog/update-product-list-header-layout
new file mode 100644
index 00000000000..b9296fbdb8c
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/update-product-list-header-layout
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Update the experimental product list header layout to match the compact prototype.
diff --git a/packages/js/experimental-products-app/src/product-list/index.tsx b/packages/js/experimental-products-app/src/product-list/index.tsx
index c32a90e1f14..b3eceb75db6 100644
--- a/packages/js/experimental-products-app/src/product-list/index.tsx
+++ b/packages/js/experimental-products-app/src/product-list/index.tsx
@@ -10,7 +10,6 @@ import clsx from 'clsx';
 import { Button, Icon, Stack, Tabs } from '@wordpress/ui';
 import { privateApis as componentsPrivateApis } from '@wordpress/components';
 import { privateApis as editorPrivateApis } from '@wordpress/editor';
-import { Page } from '@wordpress/admin-ui';
 import { addQueryArgs } from '@wordpress/url';
 import { getAdminLink } from '@woocommerce/settings';
 import { __ } from '@wordpress/i18n';
@@ -46,6 +45,7 @@ import {
 } from './utils';
 import { useProductActions } from '../dataviews-actions';
 import { ProductListEmptyState } from './empty-state';
+import { ProductListPage, ProductListPageHeader } from './page';

 const { Menu } = unlock( componentsPrivateApis );
 const { usePostActions } = unlock( editorPrivateApis );
@@ -309,16 +309,43 @@ export default function ProductList( {
 		</Stack>
 	);

+	const toolbar = (
+		<Stack
+			direction="row"
+			align="center"
+			justify="space-between"
+			gap="sm"
+			className="woocommerce-product-list__toolbar"
+		>
+			{ /* Tabs component should not be used: https://github.com/woocommerce/woocommerce/issues/64478 */ }
+			<Tabs.Root value={ selectedTab } onValueChange={ onChangeTab }>
+				<Tabs.List
+					variant="minimal"
+					aria-label={ __(
+						'Filter products by status',
+						'woocommerce'
+					) }
+				>
+					{ PRODUCT_LIST_TABS.map( ( tab ) => (
+						<Tabs.Tab key={ tab.value } value={ tab.value }>
+							{ tab.label }
+						</Tabs.Tab>
+					) ) }
+				</Tabs.List>
+			</Tabs.Root>
+			<Stack direction="row" align="center" gap="xs">
+				<DataViews.Search label={ __( 'Search', 'woocommerce' ) } />
+				<DataViews.FiltersToggle />
+				<DataViews.LayoutSwitcher />
+				<DataViews.ViewConfig />
+			</Stack>
+		</Stack>
+	);
+
 	return (
-		<Page
+		<ProductListPage
 			className={ classes }
 			ariaLabel={ __( 'Products', 'woocommerce' ) }
-			subTitle={ __(
-				'Add, edit, and manage the products you sell in your store.',
-				'woocommerce'
-			) }
-			title={ __( 'Products', 'woocommerce' ) }
-			actions={ pageActions }
 		>
 			<DataViews
 				key={ activeView }
@@ -350,45 +377,19 @@ export default function ProductList( {
 					</a>
 				) }
 			>
-				<Stack
-					direction="row"
-					align="center"
-					justify="space-between"
-					gap="sm"
-					className="woocommerce-product-list__toolbar"
-				>
-					{ /* Tabs component should not be used: https://github.com/woocommerce/woocommerce/issues/64478 */ }
-					<Tabs.Root
-						value={ selectedTab }
-						onValueChange={ onChangeTab }
-					>
-						<Tabs.List
-							variant="minimal"
-							aria-label={ __(
-								'Filter products by status',
-								'woocommerce'
-							) }
-						>
-							{ PRODUCT_LIST_TABS.map( ( tab ) => (
-								<Tabs.Tab key={ tab.value } value={ tab.value }>
-									{ tab.label }
-								</Tabs.Tab>
-							) ) }
-						</Tabs.List>
-					</Tabs.Root>
-					<Stack direction="row" align="center" gap="xs">
-						<DataViews.Search
-							label={ __( 'Search', 'woocommerce' ) }
-						/>
-						<DataViews.FiltersToggle />
-						<DataViews.LayoutSwitcher />
-						<DataViews.ViewConfig />
-					</Stack>
-				</Stack>
+				<ProductListPageHeader
+					title={ __( 'Products', 'woocommerce' ) }
+					subTitle={ __(
+						'Add, edit, and manage the products you sell in your store.',
+						'woocommerce'
+					) }
+					actions={ pageActions }
+					toolbar={ toolbar }
+				/>
 				<DataViews.FiltersToggled className="woocommerce-product-list__filters" />
 				<DataViews.Layout />
 				<DataViews.Footer />
 			</DataViews>
-		</Page>
+		</ProductListPage>
 	);
 }
diff --git a/packages/js/experimental-products-app/src/product-list/page/README.md b/packages/js/experimental-products-app/src/product-list/page/README.md
new file mode 100644
index 00000000000..90f10746364
--- /dev/null
+++ b/packages/js/experimental-products-app/src/product-list/page/README.md
@@ -0,0 +1,7 @@
+# Product List Page
+
+This is a temporary wrapper around `@wordpress/admin-ui`'s `Page` for the experimental product list.
+
+The product list prototype uses a compact two-line header: title and actions in the first row, subtitle in the second row, then the DataViews toolbar immediately below. The current `Page` component does not expose a public option for this compact header spacing, so this wrapper lets the product list own the header markup without changing `@wordpress/admin-ui`.
+
+Remove this wrapper once `Page` supports this layout directly.
diff --git a/packages/js/experimental-products-app/src/product-list/page/index.tsx b/packages/js/experimental-products-app/src/product-list/page/index.tsx
new file mode 100644
index 00000000000..1d47652b9be
--- /dev/null
+++ b/packages/js/experimental-products-app/src/product-list/page/index.tsx
@@ -0,0 +1,63 @@
+/**
+ * External dependencies
+ */
+import { Page } from '@wordpress/admin-ui';
+import { Stack } from '@wordpress/ui';
+
+type ProductListPageProps = {
+	ariaLabel: string;
+	children: React.ReactNode;
+	className?: string;
+};
+
+type ProductListPageHeaderProps = {
+	actions?: React.ReactNode;
+	subTitle?: React.ReactNode;
+	title: React.ReactNode;
+	toolbar?: React.ReactNode;
+};
+
+export function ProductListPage( {
+	ariaLabel,
+	children,
+	className,
+}: ProductListPageProps ) {
+	return (
+		<Page className={ className } ariaLabel={ ariaLabel }>
+			{ children }
+		</Page>
+	);
+}
+
+export function ProductListPageHeader( {
+	actions,
+	subTitle,
+	title,
+	toolbar,
+}: ProductListPageHeaderProps ) {
+	return (
+		<header className="woocommerce-product-list-page__header">
+			<Stack direction="row" justify="space-between" gap="sm">
+				<Stack direction="row" gap="sm" align="center" justify="start">
+					<h2 className="woocommerce-product-list-page__header-title">
+						{ title }
+					</h2>
+				</Stack>
+				<Stack
+					direction="row"
+					gap="sm"
+					className="woocommerce-product-list-page__header-actions"
+					align="center"
+				>
+					{ actions }
+				</Stack>
+			</Stack>
+			{ subTitle && (
+				<p className="woocommerce-product-list-page__header-subtitle">
+					{ subTitle }
+				</p>
+			) }
+			{ toolbar }
+		</header>
+	);
+}
diff --git a/packages/js/experimental-products-app/src/style.scss b/packages/js/experimental-products-app/src/style.scss
index 321c6d1618e..2eaa0356570 100644
--- a/packages/js/experimental-products-app/src/style.scss
+++ b/packages/js/experimental-products-app/src/style.scss
@@ -8,6 +8,18 @@
 	background-color: unset;
 }

+.product_page_woocommerce-products-dashboard #wpcontent {
+	padding: 0;
+}
+
+.product_page_woocommerce-products-dashboard #wpbody-content {
+	padding-bottom: 0;
+}
+
+.product_page_woocommerce-products-dashboard #wpfooter {
+	display: none;
+}
+
 .product_page_woocommerce-products-dashboard,
 #woocommerce-variations-classic-root,
 .woocommerce-product-edit__drawer-portal {
@@ -36,10 +48,12 @@
 }

 .woocommerce-product-list {
-	height: 85vh;
+	height: calc(100vh - var(--wp-admin--admin-bar--height, 32px));
 }

 .woocommerce-product-list__toolbar {
+	margin: var(--wpds-dimension-padding-xs, 4px)
+		calc(var(--wpds-dimension-padding-2xl, 24px) * -1) 0;
 	padding: 0 var(--wpds-dimension-padding-2xl, 24px);
 }

@@ -47,6 +61,40 @@
 	height: 100%;
 }

+.woocommerce-product-list-page__header {
+	position: sticky;
+	top: 0;
+	z-index: 1;
+	padding: var(--wpds-dimension-padding-lg, 16px)
+		var(--wpds-dimension-padding-2xl, 24px)
+		0;
+	border-bottom: var(--wpds-border-width-xs, 1px) solid
+		var(--wpds-color-stroke-surface-neutral-weak, #ddd);
+}
+
+.woocommerce-product-list-page__header-title {
+	margin: 0;
+	overflow: hidden;
+	color: var(--wpds-color-fg-content-neutral, #1e1e1e);
+	font-family: var(--wpds-typography-font-family-heading);
+	font-size: var(--wpds-typography-font-size-lg, 15px);
+	font-weight: var(--wpds-typography-font-weight-medium, 500);
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.woocommerce-product-list-page__header-actions {
+	width: auto;
+	flex-shrink: 0;
+}
+
+.woocommerce-product-list-page__header-subtitle {
+	margin: 0;
+	color: var(--wpds-color-fg-content-neutral-weak, #707070);
+	font-size: var(--wpds-typography-font-size-md, 13px);
+	line-height: var(--wpds-typography-line-height-md, 24px);
+}
+
 // Temporary override until DataViews supports title links without default underlines.
 #woocommerce-products-dashboard a.dataviews-title-field {
 	text-decoration: none;