Commit 3e9831e93c1 for woocommerce
commit 3e9831e93c1fa7930aae55d01c6bb95507a9c38d
Author: Luigi Teschio <gigitux@gmail.com>
Date: Fri Apr 24 10:35:32 2026 +0200
[DataViews - All Products] Integrate field registry (#64273)
* Add shared controls for products app fields
* Add pricing fields to products app
* Add inventory fields to products app
* Add organization fields to products app
* Add media and SEO fields to products app
* Integrate products app field registry
* remove not necessary code
* clean up code
* Update packages/js/experimental-products-app/tsconfig.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update packages/js/experimental-products-app/src/fields/components/date-picker.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix list-item: import style.scss and default alt to empty string
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/f3cf7f22-fc05-4c03-a256-0b68bdb81cd5
Co-authored-by: gigitux <4463174+gigitux@users.noreply.github.com>
* fix build
* fix build
* lint code
* fix tsconfig
* lint code
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* lint code
* fix ts errors
* fix ts errors
* fix types
* fix naming
* remove test
* remove not necessary field
* remove no existent style
* improve types
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gigitux <4463174+gigitux@users.noreply.github.com>
diff --git a/packages/js/experimental-products-app/changelog/add-products-fields-integration b/packages/js/experimental-products-app/changelog/add-products-fields-integration
new file mode 100644
index 00000000000..0cf4a143696
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/add-products-fields-integration
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Integrate products app field modules into the product list
diff --git a/packages/js/experimental-products-app/src/fields/images/style.scss b/packages/js/experimental-products-app/src/fields/images/style.scss
index fe1b79ba7c6..a20e723763e 100644
--- a/packages/js/experimental-products-app/src/fields/images/style.scss
+++ b/packages/js/experimental-products-app/src/fields/images/style.scss
@@ -1,5 +1,3 @@
-@use "@wordpress/base-styles/variables";
-
.woocommerce-fields-control__featured-image {
display: grid;
gap: 10px;
@@ -105,8 +103,8 @@
border: 1px solid var( --wpds-color-stroke-interactive-neutral );
background-color: var( --wpds-color-bg-surface-neutral-strong );
overflow: hidden;
- width: variables.$button-size-small;
- height: variables.$button-size-small;
+ width: $button-size-small;
+ height: $button-size-small;
opacity: 0;
transition: opacity 0.2s ease-in-out;
z-index: 1;
diff --git a/packages/js/experimental-products-app/src/product-list/fields.tsx b/packages/js/experimental-products-app/src/product-list/fields.tsx
index fce43ad2738..dd48aab940a 100644
--- a/packages/js/experimental-products-app/src/product-list/fields.tsx
+++ b/packages/js/experimental-products-app/src/product-list/fields.tsx
@@ -1,64 +1,146 @@
/**
* External dependencies
*/
-import { createElement, Fragment } from '@wordpress/element';
-import { Product } from '@woocommerce/data';
-import { __ } from '@wordpress/i18n';
-import { Field } from '@wordpress/dataviews';
+import type { Field } from '@wordpress/dataviews';
/**
* Internal dependencies
*/
-import { OPERATOR_IS } from '../constants';
+import { fieldExtensions as buttonTextFieldExtensions } from '../fields/button_text/field';
+import { fieldExtensions as catalogVisibilityFieldExtensions } from '../fields/catalog_visibility/field';
+import { fieldExtensions as categoriesFieldExtensions } from '../fields/categories/field';
+import { fieldExtensions as crossSellIdsFieldExtensions } from '../fields/cross_sell_ids/field';
+import { fieldExtensions as dateOnSaleFromFieldExtensions } from '../fields/date_on_sale_from/field';
+import { fieldExtensions as dateOnSaleToFieldExtensions } from '../fields/date_on_sale_to/field';
+import { fieldExtensions as descriptionFieldExtensions } from '../fields/description/field';
+import { fieldExtensions as downloadableFieldExtensions } from '../fields/downloadable/field';
+import { fieldExtensions as downloadableCountFieldExtensions } from '../fields/downloadable_count/field';
+import { fieldExtensions as externalUrlFieldExtensions } from '../fields/external_url/field';
+import { fieldExtensions as featuredFieldExtensions } from '../fields/featured/field';
+import { fieldExtensions as heightFieldExtensions } from '../fields/height/field';
+import { fieldExtensions as imagesFieldExtensions } from '../fields/images/field';
+import { fieldExtensions as imagesCountFieldExtensions } from '../fields/images_count/field';
+import { fieldExtensions as inventorySummaryFieldExtensions } from '../fields/inventory_summary/field';
+import { fieldExtensions as lengthFieldExtensions } from '../fields/length/field';
+import { fieldExtensions as linkedProductsCountFieldExtensions } from '../fields/linked_products_count/field';
+import { fieldExtensions as manageStockFieldExtensions } from '../fields/manage_stock/field';
+import { fieldExtensions as nameFieldExtensions } from '../fields/name/field';
+import { fieldExtensions as onSaleFieldExtensions } from '../fields/on_sale/field';
+import { fieldExtensions as organizationSummaryFieldExtensions } from '../fields/organization_summary/field';
+import { fieldExtensions as priceFieldExtensions } from '../fields/price/field';
+import { fieldExtensions as priceSummaryFieldExtensions } from '../fields/price_summary/field';
+import { fieldExtensions as productStatusFieldExtensions } from '../fields/product_status/field';
+import { fieldExtensions as regularPriceFieldExtensions } from '../fields/regular_price/field';
+import { fieldExtensions as salePriceFieldExtensions } from '../fields/sale_price/field';
+import { fieldExtensions as scheduleSaleFieldExtensions } from '../fields/schedule_sale/field';
+import { fieldExtensions as shippingClassFieldExtensions } from '../fields/shipping_class/field';
+import { fieldExtensions as shippingSummaryFieldExtensions } from '../fields/shipping_summary/field';
+import { fieldExtensions as shortDescriptionFieldExtensions } from '../fields/short_description/field';
+import { fieldExtensions as skuFieldExtensions } from '../fields/sku/field';
+import { fieldExtensions as stockFieldExtensions } from '../fields/stock/field';
+import { fieldExtensions as stockQuantityFieldExtensions } from '../fields/stock_quantity/field';
+import { fieldExtensions as tagsFieldExtensions } from '../fields/tags/field';
+import { fieldExtensions as taxStatusFieldExtensions } from '../fields/tax_status/field';
+import type { ProductEntityRecord } from '../fields/types';
+import { fieldExtensions as upsellIdsFieldExtensions } from '../fields/upsell_ids/field';
+import { fieldExtensions as visibilitySummaryFieldExtensions } from '../fields/visibility_summary/field';
+import { fieldExtensions as weightFieldExtensions } from '../fields/weight/field';
+import { fieldExtensions as widthFieldExtensions } from '../fields/width/field';
-const STATUSES = [
- { value: 'draft', label: __( 'Draft', 'woocommerce' ) },
- { value: 'future', label: __( 'Scheduled', 'woocommerce' ) },
- { value: 'private', label: __( 'Private', 'woocommerce' ) },
- { value: 'publish', label: __( 'Published', 'woocommerce' ) },
- { value: 'trash', label: __( 'Trash', 'woocommerce' ) },
-];
+type ProductField = Field< ProductEntityRecord >;
+type ProductFieldExtensions = Partial< ProductField >;
-/**
- * TODO: auto convert some of the product editor blocks ( from the blocks directory ) to this format.
- * The edit function should work relatively well with the edit from the blocks, the only difference is that the blocks rely on getEntityProp to get the value
- */
-export const productFields: Field< Product >[] = [
- {
- id: 'name',
- label: __( 'Name', 'woocommerce' ),
- enableHiding: false,
- type: 'text',
- render: function nameRender( { item }: { item: Product } ) {
- return <>{ item.name }</>;
- },
- },
- {
- id: 'sku',
- label: __( 'SKU', 'woocommerce' ),
- enableHiding: false,
- enableSorting: false,
- render: ( { item }: { item: Product } ) => {
- return <>{ item.sku }</>;
- },
- },
- {
- id: 'date',
- label: __( 'Date', 'woocommerce' ),
- render: ( { item }: { item: Product } ) => {
- return <time>{ item.date_created }</time>;
- },
- },
+const PRODUCT_LIST_FIELD_IDS = [
+ 'name',
+ 'short_description',
+ 'description',
+ 'images',
+ 'images_count',
+ 'product_status',
+ 'sku',
+ 'price',
+ 'regular_price',
+ 'sale_price',
+ 'schedule_sale',
+ 'date_on_sale_from',
+ 'date_on_sale_to',
+ 'on_sale',
+ 'price_summary',
+ 'stock',
+ 'stock_quantity',
+ 'manage_stock',
+ 'inventory_summary',
+ 'categories',
+ 'tags',
+ 'organization_summary',
+ 'featured',
+ 'catalog_visibility',
+ 'visibility_summary',
+ 'downloadable',
+ 'downloadable_count',
+ 'external_url',
+ 'button_text',
+ 'weight',
+ 'length',
+ 'width',
+ 'height',
+ 'shipping_class',
+ 'shipping_summary',
+ 'tax_status',
+ 'upsell_ids',
+ 'cross_sell_ids',
+ 'linked_products_count',
+ 'seo_title',
+ 'seo_description',
+ 'seo_preview',
+] as const;
+
+const PRODUCT_LIST_FIELD_EXTENSIONS: Record< string, ProductFieldExtensions > =
{
- label: __( 'Status', 'woocommerce' ),
- id: 'status',
- getValue: ( { item }: { item: Product } ) =>
- STATUSES.find( ( { value } ) => value === item.status )?.label ??
- item.status,
- elements: STATUSES,
- filterBy: {
- operators: [ OPERATOR_IS ],
- },
- enableSorting: false,
- },
-];
+ name: nameFieldExtensions,
+ short_description: shortDescriptionFieldExtensions,
+ description: descriptionFieldExtensions,
+ images: imagesFieldExtensions,
+ images_count: imagesCountFieldExtensions,
+ product_status: productStatusFieldExtensions,
+ sku: skuFieldExtensions,
+ price: priceFieldExtensions as ProductFieldExtensions,
+ regular_price: regularPriceFieldExtensions,
+ sale_price: salePriceFieldExtensions,
+ schedule_sale: scheduleSaleFieldExtensions,
+ date_on_sale_from: dateOnSaleFromFieldExtensions,
+ date_on_sale_to: dateOnSaleToFieldExtensions,
+ on_sale: onSaleFieldExtensions,
+ price_summary: priceSummaryFieldExtensions,
+ stock: stockFieldExtensions,
+ stock_quantity: stockQuantityFieldExtensions,
+ manage_stock: manageStockFieldExtensions,
+ inventory_summary: inventorySummaryFieldExtensions,
+ categories: categoriesFieldExtensions,
+ tags: tagsFieldExtensions,
+ organization_summary: organizationSummaryFieldExtensions,
+ featured: featuredFieldExtensions,
+ catalog_visibility: catalogVisibilityFieldExtensions,
+ visibility_summary: visibilitySummaryFieldExtensions,
+ downloadable: downloadableFieldExtensions,
+ downloadable_count: downloadableCountFieldExtensions,
+ external_url: externalUrlFieldExtensions,
+ button_text: buttonTextFieldExtensions,
+ weight: weightFieldExtensions,
+ length: lengthFieldExtensions,
+ width: widthFieldExtensions,
+ height: heightFieldExtensions,
+ shipping_class: shippingClassFieldExtensions,
+ shipping_summary: shippingSummaryFieldExtensions,
+ tax_status: taxStatusFieldExtensions,
+ upsell_ids: upsellIdsFieldExtensions,
+ cross_sell_ids: crossSellIdsFieldExtensions,
+ linked_products_count: linkedProductsCountFieldExtensions,
+ };
+
+export const productFields: ProductField[] = PRODUCT_LIST_FIELD_IDS.map(
+ ( id ) => ( {
+ id,
+ ...PRODUCT_LIST_FIELD_EXTENSIONS[ id ],
+ } )
+);
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 89078b3c9fa..35bcc12e916 100644
--- a/packages/js/experimental-products-app/src/product-list/index.tsx
+++ b/packages/js/experimental-products-app/src/product-list/index.tsx
@@ -3,14 +3,13 @@
*/
import { DataViews, View } from '@wordpress/dataviews';
import {
- createElement,
useState,
useMemo,
useCallback,
useEffect,
Fragment,
} from '@wordpress/element';
-import { Product, ProductQuery, productsStore } from '@woocommerce/data';
+import { ProductQuery, productsStore } from '@woocommerce/data';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { store as coreStore } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
@@ -24,7 +23,12 @@ import { Page } from '@wordpress/admin-ui';
* Internal dependencies
*/
import { unlock } from '../lock-unlock';
+import type { ProductEntityRecord } from '../fields/types';
import { productFields } from './fields';
+import {
+ DEFAULT_PRODUCT_TABLE_LAYOUT,
+ DEFAULT_PRODUCT_TABLE_VIEW,
+} from './layouts';
import { useEditProductAction } from '../dataviews-actions';
const { usePostActions } = unlock( editorPrivateApis );
@@ -37,16 +41,14 @@ export type ProductListProps = {
postType?: string;
};
-const PAGE_SIZE = 25;
-const EMPTY_ARRAY: Product[] = [];
+const PAGE_SIZE = 20;
+const EMPTY_ARRAY: ProductEntityRecord[] = [];
const DEFAULT_LAYOUTS = {
- table: {} as const,
+ table: DEFAULT_PRODUCT_TABLE_LAYOUT,
};
const DEFAULT_VIEW: View = {
- type: 'table',
+ ...DEFAULT_PRODUCT_TABLE_VIEW,
page: 1,
- perPage: PAGE_SIZE,
- fields: [ 'name', 'sku', 'status', 'date' ],
};
/**
@@ -73,7 +75,7 @@ function useView( postType: string ): [ View, ( view: View ) => void ] {
return [ view, setView ];
}
-function getItemId( item: Product ) {
+function getItemId( item: ProductEntityRecord ) {
return item.id.toString();
}
@@ -96,7 +98,10 @@ export default function ProductList( {
const queryParams = useMemo( () => {
const filters: Partial< ProductQuery > = {};
view.filters?.forEach( ( filter ) => {
- if ( filter.field === 'status' ) {
+ if (
+ filter.field === 'status' ||
+ filter.field === 'product_status'
+ ) {
filters.status = Array.isArray( filter.value )
? filter.value.join( ',' )
: filter.value;
@@ -135,7 +140,7 @@ export default function ProductList( {
const { getProducts, getProductsTotalCount, isResolving } =
select( productsStore );
return {
- records: getProducts( queryParams ) as Product[],
+ records: getProducts( queryParams ) as ProductEntityRecord[],
totalCount: getProductsTotalCount( queryParams ),
isLoading: isResolving( 'getProducts', [ queryParams ] ),
};
diff --git a/packages/js/experimental-products-app/src/product-list/layouts.ts b/packages/js/experimental-products-app/src/product-list/layouts.ts
new file mode 100644
index 00000000000..cb98167fafe
--- /dev/null
+++ b/packages/js/experimental-products-app/src/product-list/layouts.ts
@@ -0,0 +1,39 @@
+/**
+ * External dependencies
+ */
+import type { SupportedLayouts, ViewTable } from '@wordpress/dataviews';
+
+export const DEFAULT_PRODUCT_TABLE_TITLE_FIELD = 'name';
+export const DEFAULT_PRODUCT_TABLE_MEDIA_FIELD = 'images';
+
+export const DEFAULT_PRODUCT_TABLE_FIELDS = [
+ 'product_status',
+ 'sku',
+ 'stock',
+ 'price',
+] as const;
+
+export const DEFAULT_PRODUCT_TABLE_LAYOUT: NonNullable<
+ SupportedLayouts[ 'table' ]
+> = {
+ fields: [ ...DEFAULT_PRODUCT_TABLE_FIELDS ],
+ layout: {
+ styles: {
+ price: {
+ align: 'end',
+ },
+ },
+ },
+};
+
+export const DEFAULT_PRODUCT_TABLE_VIEW: ViewTable = {
+ type: 'table',
+ filters: [],
+ perPage: 20,
+ mediaField: DEFAULT_PRODUCT_TABLE_MEDIA_FIELD,
+ titleField: DEFAULT_PRODUCT_TABLE_TITLE_FIELD,
+ fields: [ ...DEFAULT_PRODUCT_TABLE_FIELDS ],
+ showLevels: false,
+ showMedia: true,
+ layout: DEFAULT_PRODUCT_TABLE_LAYOUT.layout,
+};
diff --git a/packages/js/experimental-products-app/src/style.scss b/packages/js/experimental-products-app/src/style.scss
index 35b592b865e..bafa651beb2 100644
--- a/packages/js/experimental-products-app/src/style.scss
+++ b/packages/js/experimental-products-app/src/style.scss
@@ -3,4 +3,9 @@
.product_page_woocommerce-products-dashboard {
background-color: unset;
+ @import "./fields/components/list-item/style.scss";
+ @import "./fields/downloadable/style.scss";
+ @import "./fields/images/style.scss";
+ @import "./fields/stock/style.scss";
+ @import "./fields/visibility_summary/style.scss";
}