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;