diff --git a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md
index e955b142144..3b7a53c9fe8 100644
--- a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md
+++ b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md
@@ -59,7 +59,7 @@ For our Woo extension, we'll be appending our field right at the end with `wooco
Let's get started with creating a new class which will hold the code for the field. Add a new file with the name `class-product-fields.php` to the `/includes/admin/` folder. Within the class, we add our namespace, an abort if anyone tries to call the file directly and a \_\_construct method which calls the `hooks()` method:
```php
-hooks();
+ $this->hooks();
}
private function hooks() {}
@@ -93,19 +93,19 @@ With the class set up and being called, we can create a function to add the cust
```php
public function add_field() {
global $product_object;
- ?>
-
- update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'] ) );
- $product->save_meta_data();
+ $product->update_meta_data( '_new_stock_information', sanitize_text_field( $_POST['_new_stock_information'] ) );
+ $product->save_meta_data();
}
}
```
@@ -142,12 +142,12 @@ If we add data and save the product, then the new meta data is inserted into the
At this point you have a working extension that saves a custom field for a product as product meta.
Showing the field in the store
-If we want to display the new field in our store, then we can do this with the `get_meta()` method of the Woo product class: `$product->get_meta( '\_new_stock_information' )`
+If we want to display the new field in our store, then we can do this with the `get_meta()` method of the Woo product class: `$product->get_meta( '\_new_stock_information' )`
Let's get started by creating a new file /includes/class-product.php. You may have noticed that this is outside the `/admin/` folder as this code will run in the front. So when we set up the class, we also adjust the namespace accordingly:
```php
-hooks();
+ $this->hooks();
}
private function hooks() { }
@@ -190,9 +190,9 @@ In our function we output the stock information with the [appropriate escape fun
```php
public function add_stock_info() {
global $product;
- ?>
-
+ >
+ <DefaultTaskItem />
+ </div>
) }
-
+ </WooOnboardingTaskListItem>
),
} );
```
@@ -168,37 +168,37 @@ import { registerPlugin } from '@wordpress/plugins';
Next, we create a [functional component](https://reactjs.org/docs/components-and-props.html) that returns our task card. The intermixed JavaScript/HTML syntax we're using here is called JSX. If you're unfamiliar with it, you can [read more about it in the React docs](https://reactjs.org/docs/introducing-jsx.html).
```js
-const Task = ( { onComplete, task } ) => {
+const Task = ( { onComplete, task } ) => {
const { actionTask } = useDispatch( ONBOARDING_STORE_NAME );
const { isActioned } = task;
return (
-
-
+ <Card className="woocommerce-task-card">
+ <CardBody>
{ __(
"This task's completion status is dependent on being actioned. The action button below will action this task, while the complete button will optimistically complete the task in the task list and redirect back to the task list. Note that in this example, the task must be actioned for completion to persist.",
'plugin-domain'
) }{ ' ' }
-
-
+ <br />
+ <br />
{ __( 'Task actioned status: ', 'plugin-domain' ) }{ ' ' }
{ isActioned ? 'actioned' : 'not actioned' }
-
-
-
-
-
-
-
-
+ </button>
+ </div>
+ </CardBody>
+ </Card>
);
};
```
@@ -211,14 +211,14 @@ Next, we register the Task component as a plugin named "add-task-content" using
```js
registerPlugin( 'add-task-content', {
- render: () => (
+ render: () => (
{ ( {
onComplete,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
query,
task,
- } ) => }
+ } ) => }
),
scope: 'woocommerce-tasks',
@@ -232,20 +232,20 @@ Finally, we register another plugin named "my-task-list-item-plugin." This plugi
```js
registerPlugin( 'my-task-list-item-plugin', {
scope: 'woocommerce-tasks',
- render: () => (
-
- { ( { defaultTaskItem: DefaultTaskItem } ) => (
+ render: () => (
+ <WooOnboardingTaskListItem id="my-task">
+ { ( { defaultTaskItem: DefaultTaskItem } ) => (
// Add a custom wrapper around the default task item.
-
-
-
+ >
+ <DefaultTaskItem />
+ </div>
) }
-
+ </WooOnboardingTaskListItem>
),
} );
```
@@ -344,7 +344,7 @@ import { addFilter } from '@wordpress/hooks';
addFilter(
'woocommerce_admin_homescreen_quicklinks',
'my-extension',
- ( quickLinks ) => {
+ ( quickLinks ) => {
return [
...quickLinks,
{
@@ -374,7 +374,7 @@ Despite being a part of the new React-powered admin experience in WooCommerce, A
The recommended approach for using Admin Notes is to encapsulate your note within its own class that uses the [NoteTraits](https://github.com/woocommerce/woocommerce-admin/blob/831c9ff13a862f22cf53d3ae676daeabbefe90ad/src/Notes/NoteTraits.php) trait included with WooCommerce Admin. Below is a simple example of what this might look like:
```php
-set_title( 'Getting Started' );
+ $note->set_title( 'Getting Started' );
// Set our note's content.
- $note->set_content(
+ $note->set_content(
sprintf(
'Extension activated on %s.', $activated_time_formatted
)
@@ -436,41 +436,41 @@ class ExampleNote {
// You can use this property to re-localize notes on the fly, but
// that is just one use. You can store other data here too. This
// is backed by a longtext column in the database.
- $note->set_content_data( (object) array(
- 'getting_started' => true,
- 'activated' => $activated_time,
- 'activated_formatted' => $activated_time_formatted
+ $note->set_content_data( (object) array(
+ 'getting_started' => true,
+ 'activated' => $activated_time,
+ 'activated_formatted' => $activated_time_formatted
) );
// Set the type of the note. Note types are defined as enum-style
// constants in the Note class. Available note types are:
// error, warning, update, info, marketing.
- $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
+ $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
// Set the type of layout the note uses. Supported layout types are:
// 'banner', 'plain', 'thumbnail'
- $note->set_layout( 'plain' );
+ $note->set_layout( 'plain' );
// Set the image for the note. This property renders as the src
// attribute for an img tag, so use a string here.
- $note->set_image( '' );
+ $note->set_image( '' );
// Set the note name and source. You should store your extension's
// name (slug) in the source property of the note. You can use
// the name property of the note to support multiple sub-types of
// notes. This also gives you a handy way of namespacing your notes.
- $note->set_source( 'inbox-note-example');
- $note->set_name( self::NOTE_NAME );
+ $note->set_source( 'inbox-note-example');
+ $note->set_name( self::NOTE_NAME );
// Add action buttons to the note. A note can support 0, 1, or 2 actions.
// The first parameter is the action name, which can be used for event handling.
// The second parameter renders as the label for the button.
// The third parameter is an optional URL for actions that require navigation.
- $note->add_action(
+ $note->add_action(
'settings', 'Open Settings', '?page=wc-settings&tab=general'
);
- $note->add_action(
+ $note->add_action(
'learn_more', 'Learn More', 'https://example.com'
);
@@ -559,12 +559,12 @@ Next, we'll instantiate a new `Note` object.
Once we have an instance of the Note class, we can work with its API to set its properties, starting with its title.
-`$note->set_title( 'Getting Started' );`
+`$note->set_title( 'Getting Started' );`
Then we'll use some of the timestamp data we collected above to set the note's content.
```php
-$note->set_content(
+$note->set_content(
sprintf(
'Extension activated on %s.', $activated_time_formatted
)
@@ -574,41 +574,41 @@ $note->set_content(
In addition to regular content, notes also support structured content using the `content_data` property. You can use this property to re-localize notes on the fly, but that is just one use case. You can store other data here too. This is backed by a `longtext` column in the database.
```php
-$note->set_content_data( (object) array(
- 'getting_started' => true,
- 'activated' => $activated_time,
- 'activated_formatted' => $activated_time_formatted
+$note->set_content_data( (object) array(
+ 'getting_started' => true,
+ 'activated' => $activated_time,
+ 'activated_formatted' => $activated_time_formatted
) );
```
Next, we'll set the note's `type` property. Note types are defined as enum-style class constants in the `Note` class. Available note types are _error_, _warning_, _update_, _info_, and _marketing_. When selecting a note type, be aware that the _error_ and _update_ result in the note being shown as a Store Alert, not in the Inbox. It's best to avoid using these types of notes unless you absolutely need to.
-`$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );`
+`$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );`
Admin Notes also support a few different layouts. You can specify `banner`, `plain`, or `thumbnail` as the layout. If you're interested in seeing the different layouts in action, take a look at [this simple plugin](https://gist.github.com/octaedro/864315edaf9c6a2a6de71d297be1ed88) that you can install to experiment with them.
We'll choose `plain` as our layout, but it's also the default, so we could leave this property alone and the effect would be the same.
-`$note->set_layout( 'plain' );`
+`$note->set_layout( 'plain' );`
If you have an image that you want to add to your Admin Note, you can specify it using the `set_image` function. This property ultimately renders as the `src` attribute on an `img` tag, so use a string here.
-`$note->set_image( '' );`
+`$note->set_image( '' );`
Next, we'll set the values for our Admin Note's `name` and `source` properties. As a best practice, you should store your extension's name (i.e. its slug) in the `source` property of the note. You can use the `name` property to support multiple sub-types of notes. This gives you a handy way of namespacing your notes and managing them at both a high and low level.
```php
-$note->set_source( 'inbox-note-example');
-$note->set_name( self::NOTE_NAME );
+$note->set_source( 'inbox-note-example');
+$note->set_name( self::NOTE_NAME );
```
Admin Notes can support 0, 1, or 2 actions (buttons). You can use these actions to capture events that trigger asynchronous processes or help the merchant navigate to a particular view to complete a step, or even simply to provide an external link for further information. The `add_action()` function takes up to three arguments. The first is the action name, which can be used for event handling, the second renders as a label for the action's button, and the third is an optional URL for actions that require navigation.
```php
-$note->add_action(
+$note->add_action(
'settings', 'Open Settings', '?page=wc-settings&tab=general'
);
-$note->add_action(
+$note->add_action(
'learn_more', 'Learn More', 'https://example.com'
);
```
diff --git a/docs/high-performance-order-storage/guide-large-store.md b/docs/high-performance-order-storage/guide-large-store.md
index 75583f42615..dd2b40d159e 100644
--- a/docs/high-performance-order-storage/guide-large-store.md
+++ b/docs/high-performance-order-storage/guide-large-store.md
@@ -87,7 +87,7 @@ Also, we didn't see a noticeable negative performance impact when keeping synchr
### Switch to HPOS as authoritative
-It's time to switch to HPOS. Go to **WooCommerce > Settings > Advanced > Features** and set HPOS to be authoritative (select “**Use the WooCommerce orders tables**").
+It's time to switch to HPOS. Go to **WooCommerce > Settings > Advanced > Features** and set HPOS to be authoritative (select "**Use the WooCommerce orders tables**").
As mentioned above, don't turn off synchronization yet. If there are any issues, the system can be instantaneously reverted to the posts table, resulting in no downtime.
@@ -107,7 +107,7 @@ We disable sync on read first because it demands more resources. If your site is
### Switch off sync on write
-If everything is working as expected, you can disable sync on write as well. Given sync on read was already disabled, you can disable sync altogether from the settings. As usual, go to **WooCommerce > Settings > Advanced > Features**, and uncheck **“Enable compatibility mode"**.
+If everything is working as expected, you can disable sync on write as well. Given sync on read was already disabled, you can disable sync altogether from the settings. As usual, go to **WooCommerce > Settings > Advanced > Features**, and uncheck **"Enable compatibility mode"**.
On our high-volume site, we fully disabled sync after 1 week. We still run some manual synchronization (via `wp wc cot sync`) periodically so that we have the opportunity to fall back to posts immediately should anything happen.
@@ -118,11 +118,11 @@ Now with synchronization fully disabled, test out various critical flows, check
### Review: Phase 3 Checklist
1. [ ] Plan to be online and monitoring your live site for a period of time.
-2. [ ] Enable synchronization with posts set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select “**Use the WordPress posts tables**".
+2. [ ] Enable synchronization with posts set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select "**Use the WordPress posts tables**".
3. [ ] Start migration via CLI using the `wp wc cot sync` command.
4. [ ] Monitor for errors during migration; halt or resume as necessary.
5. [ ] Verify migrated data integrity using the verify command `wp wc cot verify_cot_data`.
-6. [ ] Enable synchronization with HPOS set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select “Use the **WooCommerce orders tables**".
+6. [ ] Enable synchronization with HPOS set as authoritative: in **WooCommerce > Settings > Advanced > Features** > select "Use the **WooCommerce orders tables**".
7. [ ] Test all critical flows, perform checkouts with multiple payment methods, and verify order data accuracy.
8. [ ] Monitor support tickets for any issues.
9. [ ] Disable synchronization on read using the provided snippet: `add_filter( 'woocommerce_hpos_enable_sync_on_read', '__return_false' );`
diff --git a/docs/performance/performance-best-practices.md b/docs/performance/performance-best-practices.md
index a88896370c4..b47f828754e 100644
--- a/docs/performance/performance-best-practices.md
+++ b/docs/performance/performance-best-practices.md
@@ -20,7 +20,7 @@ For WooCommerce extensions, performance optimization means ensuring that your co
## Benchmarking Performance
-Setting clear performance benchmarks is essential for development and continuous improvement of WooCommerce extensions. A recommended performance standard is achieving a Chrome Core Web Vitals "Performance" score of 90 or above on Woo Express, using tools like the [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/).
+Setting clear performance benchmarks is essential for development and continuous improvement of WooCommerce extensions. A recommended performance standard is achieving a Chrome Core Web Vitals "Performance" score of 90 or above on a simple Woo site, using tools like the [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/).
### Using Accessible Tools for Benchmarking
diff --git a/docs/product-collection-block/dom-events.md b/docs/product-collection-block/dom-events.md
index cc49b4bb8b9..940e49a929d 100644
--- a/docs/product-collection-block/dom-events.md
+++ b/docs/product-collection-block/dom-events.md
@@ -10,13 +10,13 @@ tags: how-to
This event is triggered when Product Collection block was rendered or re-rendered (e.g. due to page change).
-### `detail` parameters
+### `wc-blocks_product_list_rendered` - `detail` parameters
| Parameter | Type | Default value | Description |
| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. |
-### Example usage
+### `wc-blocks_product_list_rendered` - Example usage
```javascript
window.document.addEventListener(
@@ -27,3 +27,27 @@ window.document.addEventListener(
}
);
```
+
+## Event: `wc-blocks_viewed_product`
+
+This event is triggered when some blocks are clicked in order to view product (redirect to product page).
+
+### `wc-blocks_viewed_product` - `detail` parameters
+
+| Parameter | Type | Default value | Description |
+| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `collection` | string | `undefined` | Collection type. It's `undefined` for "create your own" collections as the type is not specified. For other Core collections it can be one of type: `woocommerce/product-collection/best-sellers`, `woocommerce/product-collection/featured`, `woocommerce/product-collection/new-arrivals`, `woocommerce/product-collection/on-sale`, `woocommerce/product-collection/top-rated`. For custom collections it will hold their name. |
+| `productId` | number | | Product ID |
+
+### `wc-blocks_viewed_product` Example usage
+
+```javascript
+window.document.addEventListener(
+ 'wc-blocks_viewed_product',
+ ( e ) => {
+ const { collection, productId } = e.detail;
+ console.log( collection ) // -> collection name, e.g. "woocommerce/product-collection/featured" or undefined for default one
+ console.log( productId ) // -> product ID, e.g. 34
+ }
+);
+```
diff --git a/docs/product-collection-block/register-product-collection.md b/docs/product-collection-block/register-product-collection.md
index 9f96f791262..677668ba71e 100644
--- a/docs/product-collection-block/register-product-collection.md
+++ b/docs/product-collection-block/register-product-collection.md
@@ -6,20 +6,20 @@ tags: how-to
# Register Product Collection
-The `__experimentalRegisterProductCollection` function is part of the `@woocommerce/blocks-registry` package. This function allows third party developers to register a new collection. This function accepts most of the arguments that are accepted by [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation).
+The `__experimentalRegisterProductCollection` function is part of the `@woocommerce/blocks-registry` package. This function allows third party developers to register a new collection. This function accepts most of the arguments that are accepted by [Block Variation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/#defining-a-block-variation).
> [!WARNING]
> It's experimental and may change in the future. Please use it with caution.
**There are two ways to use this function:**
-1. Using `@woocommerce/dependency-extraction-webpack-plugin` in a Webpack configuration: This will allow you to import the function from the package & use it in your code. For example:
+1. Using `@woocommerce/dependency-extraction-webpack-plugin` in a Webpack configuration: This will allow you to import the function from the package & use it in your code. For example:
```tsx
import { __experimentalRegisterProductCollection } from "@woocommerce/blocks-registry";
```
-2. Using the global `wc` object: This will allow you to use the function using the global JS object without importing it. For example:
+2. Using the global `wc` object: This will allow you to use the function using the global JS object without importing it. For example:
```tsx
wc.wcBlocksRegistry.__experimentalRegisterProductCollection({
@@ -113,7 +113,7 @@ __experimentalRegisterProductCollection({
});
```
-As you can see in the example above, we are registering a new collection with the name `woocommerce/product-collection/my-custom-collection` & title `My Custom Collection`. Here is screenshot of how it will look like:
+As you can see in the example above, we are registering a new collection with the name `woocommerce/product-collection/my-custom-collection` & title `My Custom Collection`. Here is screenshot of how it will look like:
![image](https://github.com/woocommerce/woocommerce/assets/16707866/7fddbc02-a6cd-494e-b2f4-13dd5ef9cf96)
### Example 2: Register a new collection with a preview
diff --git a/docs/theme-development/template-structure.md b/docs/theme-development/template-structure.md
index 4688eb53875..888cd31ca11 100644
--- a/docs/theme-development/template-structure.md
+++ b/docs/theme-development/template-structure.md
@@ -22,7 +22,7 @@ Below is video walkthrough showing how one may go about updating the template fi
## Template list
-The various template files on your WooCommerce site can be found via an FTP client or your hosts file manager, in `/wp-content/plugins/woocommerce/templates/`. Below are links to the current and earlier versions of the WooCommerce template files on Github, where you can view the code exactly as it appears in those files:
+The various template files on your WooCommerce site can be found via an FTP client or your hosts file manager, in `/wp-content/plugins/woocommerce/templates/`. Below are links to the current and earlier versions of the WooCommerce template files on Github, where you can view the code exactly as it appears in those files:
| Latest Version | Files |
| -------------- | ----- |
@@ -96,7 +96,7 @@ Below are the links to the files of all major previous WooCommerce versions:
## Changing Templates via Hooks
-When you open a template file, you will notice they all contain _hooks_ that allow you to add/move content without needing to edit template files themselves. Hooks are a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. This method allows implementing a code snippet that “hooks” into a particular a theme location. It avoids upgrade issues, as the template files can be left completely untouched and doesn't require a child theme to be configured.
+When you open a template file, you will notice they all contain _hooks_ that allow you to add/move content without needing to edit template files themselves. Hooks are a way for one piece of code to interact/modify another piece of code at specific, pre-defined spots. This method allows implementing a code snippet that "hooks" into a particular a theme location. It avoids upgrade issues, as the template files can be left completely untouched and doesn't require a child theme to be configured.
Let's take a look at [/wp-content/plugins/woocommerce/templates/emails/admin-new-order.php](https://github.com/woocommerce/woocommerce/blob/8.9.0/plugins/woocommerce/templates/emails/admin-new-order.php) and see what a hook looks like. Starting on line 30, we see the following code, which is responsible for producing the order details section of the New Order email.
@@ -137,7 +137,7 @@ The copied file will now override the WooCommerce default template file, so you
---
-**Note** A (desirable) side-effect of your templates being upgrade-safe is that WooCommerce core templates will update, but your custom overrides will not. You may occassionally see notices in your System Status report that says, e.g. “version 3.5.0 is out of date. The core version is 3.7.0″. Should that happen, follow the Fixing Outdated WooCommerce Templates guide to bring them in line.
+**Note** A (desirable) side-effect of your templates being upgrade-safe is that WooCommerce core templates will update, but your custom overrides will not. You may occassionally see notices in your System Status report that says, e.g. "version 3.5.0 is out of date. The core version is 3.7.0". Should that happen, follow the Fixing Outdated WooCommerce Templates guide to bring them in line.
---
diff --git a/docs/ux-guidelines-extensions/navigation.md b/docs/ux-guidelines-extensions/navigation.md
index 428d63bdca9..52b43a9b556 100644
--- a/docs/ux-guidelines-extensions/navigation.md
+++ b/docs/ux-guidelines-extensions/navigation.md
@@ -7,10 +7,7 @@ menu_title: Navigation
Examples:
-- If your extension is extending a component within WooCommerce, it should live within either the Extensions navigation drawer (in Woo Express stores), or directly within that category's section.
-
-Extensions drawer (Woo Express)
-![Navigation extensions drawer](https://developer.woocommerce.com/docs/wp-content/uploads/sites/3/2024/01/Image-1224x572-1.png)
+- If your extension is extending a component within WooCommerce, it should live directly within that category's section.
![Navigation category](https://developer.woocommerce.com/docs/wp-content/uploads/sites/3/2024/01/Image-1242x764-1.png)
diff --git a/packages/js/product-editor/changelog/add-product_data_forms_to_data_views b/packages/js/product-editor/changelog/add-product_data_forms_to_data_views
new file mode 100644
index 00000000000..2470f0110d2
--- /dev/null
+++ b/packages/js/product-editor/changelog/add-product_data_forms_to_data_views
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add DataForms panel to the experimental product data views list.
diff --git a/packages/js/product-editor/src/products-app/dataviews-actions/index.tsx b/packages/js/product-editor/src/products-app/dataviews-actions/index.tsx
new file mode 100644
index 00000000000..dd122071a5b
--- /dev/null
+++ b/packages/js/product-editor/src/products-app/dataviews-actions/index.tsx
@@ -0,0 +1,45 @@
+/**
+ * External dependencies
+ */
+import { useMemo } from '@wordpress/element';
+import { edit } from '@wordpress/icons';
+import { privateApis as routerPrivateApis } from '@wordpress/router';
+import { __ } from '@wordpress/i18n';
+import { Product } from '@woocommerce/data';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+
+const { useHistory, useLocation } = unlock( routerPrivateApis );
+
+export const useEditProductAction = ( { postType }: { postType: string } ) => {
+ const history = useHistory();
+ const location = useLocation();
+ return useMemo(
+ () => ( {
+ id: 'edit-product',
+ label: __( 'Edit', 'woocommerce' ),
+ isPrimary: true,
+ icon: edit,
+ supportsBulk: true,
+ isEligible( product: Product ) {
+ if ( product.status === 'trash' ) {
+ return false;
+ }
+ return true;
+ },
+ callback( items: Product[] ) {
+ const product = items[ 0 ];
+ history.push( {
+ ...location.params,
+ postId: product.id,
+ postType,
+ quickEdit: true,
+ } );
+ },
+ } ),
+ [ history, location.params ]
+ );
+};
diff --git a/packages/js/product-editor/src/products-app/product-edit/index.tsx b/packages/js/product-editor/src/products-app/product-edit/index.tsx
new file mode 100644
index 00000000000..80a09a06340
--- /dev/null
+++ b/packages/js/product-editor/src/products-app/product-edit/index.tsx
@@ -0,0 +1,160 @@
+/**
+ * External dependencies
+ */
+import { DataForm, isItemValid } from '@wordpress/dataviews';
+import type { Form } from '@wordpress/dataviews';
+import { createElement, useState, useMemo } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { useSelect } from '@wordpress/data';
+import classNames from 'classnames';
+import {
+ // @ts-expect-error missing types.
+ __experimentalHeading as Heading,
+ // @ts-expect-error missing types.
+ __experimentalText as Text,
+ // @ts-expect-error missing types.
+ __experimentalHStack as HStack,
+ // @ts-expect-error missing types.
+ __experimentalVStack as VStack,
+ FlexItem,
+ Button,
+} from '@wordpress/components';
+// @ts-expect-error missing types.
+// eslint-disable-next-line @woocommerce/dependency-group
+import { privateApis as editorPrivateApis } from '@wordpress/editor';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import { productFields } from '../product-list/fields';
+
+const { NavigableRegion } = unlock( editorPrivateApis );
+
+const form: Form = {
+ type: 'panel',
+ fields: [ 'name', 'status' ],
+};
+
+type ProductEditProps = {
+ subTitle?: string;
+ className?: string;
+ hideTitleFromUI?: boolean;
+ actions?: React.JSX.Element;
+ postType: string;
+ postId: string;
+};
+
+export default function ProductEdit( {
+ subTitle,
+ actions,
+ className,
+ hideTitleFromUI = true,
+ postType,
+ postId = '',
+}: ProductEditProps ) {
+ const classes = classNames( 'edit-product-page', className, {
+ 'is-empty': ! postId,
+ } );
+ const ids = useMemo( () => postId.split( ',' ), [ postId ] );
+ const { initialEdits } = useSelect(
+ ( select ) => {
+ return {
+ initialEdits:
+ ids.length === 1
+ ? select( 'wc/admin/products' ).getProduct( ids[ 0 ] )
+ : null,
+ };
+ },
+ [ postType, ids ]
+ );
+ const [ edits, setEdits ] = useState( {} );
+ const itemWithEdits = useMemo( () => {
+ return {
+ ...initialEdits,
+ ...edits,
+ };
+ }, [ initialEdits, edits ] );
+ const isUpdateDisabled = ! isItemValid(
+ itemWithEdits,
+ productFields,
+ form
+ );
+
+ const onSubmit = async ( event: Event ) => {
+ event.preventDefault();
+
+ if ( ! isItemValid( itemWithEdits, productFields, form ) ) {
+ return;
+ }
+ // Empty save.
+
+ setEdits( {} );
+ };
+
+ return (
+
+