Refactor product tabs and add product tab slot fills (#36551)
This commit is contained in:
parent
b78318525b
commit
bcdf2518e6
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add new WooProductTabItem component for slot filling tab items.
|
|
@ -87,6 +87,7 @@ export { CollapsibleContent } from './collapsible-content';
|
|||
export { createOrderedChildren, sortFillsByOrder } from './utils';
|
||||
export { WooProductFieldItem as __experimentalWooProductFieldItem } from './woo-product-field-item';
|
||||
export { WooProductSectionItem as __experimentalWooProductSectionItem } from './woo-product-section-item';
|
||||
export { WooProductTabItem as __experimentalWooProductTabItem } from './woo-product-tab-item';
|
||||
export {
|
||||
ProductSectionLayout as __experimentalProductSectionLayout,
|
||||
ProductFieldSection as __experimentalProductFieldSection,
|
||||
|
|
|
@ -20,7 +20,10 @@ function createOrderedChildren< T = Fill.Props, S = Record< string, unknown > >(
|
|||
injectProps?: S
|
||||
) {
|
||||
if ( typeof children === 'function' ) {
|
||||
return cloneElement( children( props ), { order, ...injectProps } );
|
||||
return cloneElement( children( { ...props, order, ...injectProps } ), {
|
||||
order,
|
||||
...injectProps,
|
||||
} );
|
||||
} else if ( isValidElement( children ) ) {
|
||||
return cloneElement( children, { ...props, order, ...injectProps } );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# WooProductTabItem Slot & Fill
|
||||
|
||||
A Slotfill component that will allow you to add a new tab to the product editor.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
<WooProductTabItem id={ key } location="tab/general" order={ 2 } pluginId="test-plugin" tabProps={ { title: 'New tab', name: 'new-tab' } } >
|
||||
<Card>
|
||||
<CardBody>{ /* Tab content */ }</CardBody>
|
||||
</Card>
|
||||
</WooProductTabItem>
|
||||
|
||||
<WooProductTabItem.Slot location="tab/general" />
|
||||
```
|
||||
|
||||
### WooProductTabItem (fill)
|
||||
|
||||
This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props:
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | String | A unique string to identify your fill. Used for configuiration management. |
|
||||
| `location` | String | The string used to identify the particular location that you want to render your section. |
|
||||
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
|
||||
| `tabProps` | Object | An object containing tab props: name, title, className, disabled (see TabPanel.Tab from @wordpress/components) |
|
||||
| `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. |
|
||||
|
||||
### WooProductTabItem.Slot (slot)
|
||||
|
||||
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------- | ------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `location` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. |
|
|
@ -0,0 +1 @@
|
|||
export * from './woo-product-tab-item';
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React, { ReactElement, ReactNode } from 'react';
|
||||
import { Slot, Fill, TabPanel } from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { createOrderedChildren } from '../utils';
|
||||
|
||||
type WooProductTabItemProps = {
|
||||
id: string;
|
||||
pluginId: string;
|
||||
template?: string;
|
||||
order?: number;
|
||||
tabProps:
|
||||
| TabPanel.Tab
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| ( ( fillProps: Record< string, any > | undefined ) => TabPanel.Tab );
|
||||
templates?: Array< { name: string; order?: number } >;
|
||||
};
|
||||
|
||||
type WooProductFieldSlotProps = {
|
||||
template: string;
|
||||
children: (
|
||||
tabs: TabPanel.Tab[],
|
||||
tabChildren: Record< string, ReactNode >
|
||||
) => ReactElement | null;
|
||||
};
|
||||
|
||||
export const WooProductTabItem: React.FC< WooProductTabItemProps > & {
|
||||
Slot: React.VFC<
|
||||
Omit< Slot.Props, 'children' > & WooProductFieldSlotProps
|
||||
>;
|
||||
} = ( { children, order, template, tabProps, templates } ) => {
|
||||
if ( ! template && ! templates ) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'WooProductTabItem fill is missing template or templates property.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
templates = templates || [ { name: template as string, order } ];
|
||||
return (
|
||||
<>
|
||||
{ templates.map( ( templateData ) => (
|
||||
<Fill
|
||||
name={ `woocommerce_product_tab_${ templateData.name }` }
|
||||
key={ templateData.name }
|
||||
>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren< Fill.Props >(
|
||||
children,
|
||||
templateData.order || 20,
|
||||
{},
|
||||
{
|
||||
tabProps,
|
||||
templateName: templateData.name,
|
||||
order: templateData.order || 20,
|
||||
...fillProps,
|
||||
}
|
||||
);
|
||||
} }
|
||||
</Fill>
|
||||
) ) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
WooProductTabItem.Slot = ( { fillProps, template, children } ) => (
|
||||
<Slot
|
||||
name={ `woocommerce_product_tab_${ template }` }
|
||||
fillProps={ fillProps }
|
||||
>
|
||||
{ ( fills ) => {
|
||||
const tabData = fills.reduce(
|
||||
( { childrenMap, tabs }, fill ) => {
|
||||
const props: WooProductTabItemProps = fill[ 0 ].props;
|
||||
if ( props && props.tabProps ) {
|
||||
childrenMap[ props.tabProps.name ] = fill[ 0 ];
|
||||
const tabProps =
|
||||
typeof props.tabProps === 'function'
|
||||
? props.tabProps( fillProps )
|
||||
: props.tabProps;
|
||||
tabs.push( {
|
||||
...tabProps,
|
||||
order: props.order ?? 20,
|
||||
} );
|
||||
}
|
||||
return {
|
||||
childrenMap,
|
||||
tabs,
|
||||
};
|
||||
},
|
||||
{ childrenMap: {}, tabs: [] } as {
|
||||
childrenMap: Record< string, ReactElement >;
|
||||
tabs: Array< TabPanel.Tab & { order: number } >;
|
||||
}
|
||||
);
|
||||
const orderedTabs = tabData.tabs.sort( ( a, b ) => {
|
||||
return a.order - b.order;
|
||||
} );
|
||||
|
||||
return children( orderedTabs, tabData.childrenMap );
|
||||
} }
|
||||
</Slot>
|
||||
);
|
|
@ -126,7 +126,7 @@ const EditProductPage: React.FC = () => {
|
|||
product.status === 'trash' &&
|
||||
! isPendingAction &&
|
||||
! wasDeletedUsingAction && (
|
||||
<ProductFormLayout>
|
||||
<ProductFormLayout id="error">
|
||||
<div className="woocommerce-edit-product__error">
|
||||
{ __(
|
||||
'You cannot edit this item because it is in the Trash. Please restore it and try again.',
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import './product-form-fills';
|
||||
import './product-form-tab-fills';
|
||||
import './product-form-variation-tab-fills';
|
||||
|
||||
export * from './shipping-section/shipping-section-fills';
|
||||
export * from './details-section/details-section-fills';
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import {
|
||||
__experimentalWooProductTabItem as WooProductTabItem,
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
useFormContext,
|
||||
} from '@woocommerce/components';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OptionsSection } from '../sections/options-section';
|
||||
import { ProductVariationsSection } from '../sections/product-variations-section';
|
||||
import {
|
||||
TAB_GENERAL_ID,
|
||||
TAB_SHIPPING_ID,
|
||||
TAB_INVENTORY_ID,
|
||||
TAB_PRICING_ID,
|
||||
} from './constants';
|
||||
|
||||
const Tabs = () => {
|
||||
const { values: product } = useFormContext< Product >();
|
||||
const tabPropData = useMemo(
|
||||
() => ( {
|
||||
general: {
|
||||
name: 'general',
|
||||
title: __( 'General', 'woocommerce' ),
|
||||
},
|
||||
pricing: {
|
||||
name: 'pricing',
|
||||
title: __( 'Pricing', 'woocommerce' ),
|
||||
disabled: !! product?.variations?.length,
|
||||
},
|
||||
inventory: {
|
||||
name: 'inventory',
|
||||
title: __( 'Inventory', 'woocommerce' ),
|
||||
disabled: !! product?.variations?.length,
|
||||
},
|
||||
shipping: {
|
||||
name: 'shipping',
|
||||
title: __( 'Shipping', 'woocommerce' ),
|
||||
disabled: !! product?.variations?.length,
|
||||
},
|
||||
options: {
|
||||
name: 'options',
|
||||
title: __( 'Options', 'woocommerce' ),
|
||||
},
|
||||
} ),
|
||||
[ product.variations ]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<WooProductTabItem
|
||||
id="tab/general"
|
||||
template="tab/general"
|
||||
pluginId="core"
|
||||
order={ 1 }
|
||||
tabProps={ tabPropData.general }
|
||||
>
|
||||
<WooProductSectionItem.Slot location={ TAB_GENERAL_ID } />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/pricing"
|
||||
template="tab/general"
|
||||
pluginId="core"
|
||||
order={ 3 }
|
||||
tabProps={ tabPropData.pricing }
|
||||
>
|
||||
<WooProductSectionItem.Slot location={ TAB_PRICING_ID } />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/inventory"
|
||||
template="tab/general"
|
||||
pluginId="core"
|
||||
order={ 5 }
|
||||
tabProps={ tabPropData.inventory }
|
||||
>
|
||||
<WooProductSectionItem.Slot location={ TAB_INVENTORY_ID } />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/shipping"
|
||||
template="tab/general"
|
||||
pluginId="core"
|
||||
order={ 7 }
|
||||
tabProps={ tabPropData.shipping }
|
||||
>
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_SHIPPING_ID }
|
||||
fillProps={ { product } }
|
||||
/>
|
||||
</WooProductTabItem>
|
||||
{ window.wcAdminFeatures[ 'product-variation-management' ] ? (
|
||||
<WooProductTabItem
|
||||
id="tab/options"
|
||||
template="tab/general"
|
||||
pluginId="core"
|
||||
order={ 9 }
|
||||
tabProps={ tabPropData.options }
|
||||
>
|
||||
<>
|
||||
<OptionsSection />
|
||||
<ProductVariationsSection />
|
||||
</>
|
||||
</WooProductTabItem>
|
||||
) : null }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
registerPlugin( 'wc-admin-product-editor-form-tab-fills', {
|
||||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-product-editor',
|
||||
render: () => {
|
||||
return <Tabs />;
|
||||
},
|
||||
} );
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import {
|
||||
__experimentalWooProductTabItem as WooProductTabItem,
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
} from '@woocommerce/components';
|
||||
import { PartialProduct } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductVariationDetailsSection } from '../sections/product-variation-details-section';
|
||||
import { TAB_INVENTORY_ID, TAB_SHIPPING_ID, TAB_PRICING_ID } from './constants';
|
||||
|
||||
const tabPropData = {
|
||||
general: {
|
||||
name: 'general',
|
||||
title: __( 'General', 'woocommerce' ),
|
||||
},
|
||||
pricing: {
|
||||
name: 'pricing',
|
||||
title: __( 'Pricing', 'woocommerce' ),
|
||||
},
|
||||
inventory: {
|
||||
name: 'inventory',
|
||||
title: __( 'Inventory', 'woocommerce' ),
|
||||
},
|
||||
shipping: {
|
||||
name: 'shipping',
|
||||
title: __( 'Shipping', 'woocommerce' ),
|
||||
},
|
||||
options: {
|
||||
name: 'options',
|
||||
title: __( 'Options', 'woocommerce' ),
|
||||
},
|
||||
};
|
||||
|
||||
const Tabs = () => {
|
||||
return (
|
||||
<>
|
||||
<WooProductTabItem
|
||||
id="tab/general/variation"
|
||||
template="tab/variation"
|
||||
pluginId="core"
|
||||
order={ 1 }
|
||||
tabProps={ tabPropData.general }
|
||||
>
|
||||
<ProductVariationDetailsSection />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/pricing"
|
||||
template="tab/variation"
|
||||
pluginId="core"
|
||||
order={ 3 }
|
||||
tabProps={ tabPropData.pricing }
|
||||
>
|
||||
<WooProductSectionItem.Slot location={ TAB_PRICING_ID } />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/inventory"
|
||||
template="tab/variation"
|
||||
pluginId="core"
|
||||
order={ 5 }
|
||||
tabProps={ tabPropData.inventory }
|
||||
>
|
||||
<WooProductSectionItem.Slot location={ TAB_INVENTORY_ID } />
|
||||
</WooProductTabItem>
|
||||
<WooProductTabItem
|
||||
id="tab/shipping"
|
||||
template="tab/variation"
|
||||
pluginId="core"
|
||||
order={ 7 }
|
||||
tabProps={ tabPropData.shipping }
|
||||
>
|
||||
{ ( { product }: { product: PartialProduct } ) => (
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_SHIPPING_ID }
|
||||
fillProps={ { product } }
|
||||
/>
|
||||
) }
|
||||
</WooProductTabItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Preloading product form data, as product pages are waiting on this to be resolved.
|
||||
* The above Form component won't get rendered until the getProductForm is resolved.
|
||||
*/
|
||||
registerPlugin( 'wc-admin-product-editor-form-variation-tab-fills', {
|
||||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-product-editor',
|
||||
render: () => {
|
||||
return <Tabs />;
|
||||
},
|
||||
} );
|
|
@ -2,20 +2,28 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Children, useEffect } from '@wordpress/element';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { TabPanel, Tooltip } from '@wordpress/components';
|
||||
import { navigateTo, getNewPath, getQuery } from '@woocommerce/navigation';
|
||||
import { __experimentalWooProductTabItem as WooProductTabItem } from '@woocommerce/components';
|
||||
import { PartialProduct } from '@woocommerce/data';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './product-form-layout.scss';
|
||||
import { ProductFormTab } from '../product-form-tab';
|
||||
import { useHeaderHeight } from '~/header/use-header-height';
|
||||
|
||||
export const ProductFormLayout: React.FC< {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
} > = ( { children } ) => {
|
||||
type ProductFormLayoutProps = {
|
||||
id: string;
|
||||
product?: PartialProduct;
|
||||
};
|
||||
|
||||
export const ProductFormLayout: React.FC< ProductFormLayoutProps > = ( {
|
||||
id,
|
||||
product,
|
||||
} ) => {
|
||||
const query = getQuery() as Record< string, string >;
|
||||
|
||||
useEffect( () => {
|
||||
|
@ -36,16 +44,15 @@ export const ProductFormLayout: React.FC< {
|
|||
const tabPanelTabs = document.querySelector(
|
||||
'.product-form-layout .components-tab-panel__tabs'
|
||||
) as HTMLElement;
|
||||
tabPanelTabs.style.top = adminBarHeight + headerHeight + 'px';
|
||||
if ( tabPanelTabs ) {
|
||||
tabPanelTabs.style.top = adminBarHeight + headerHeight + 'px';
|
||||
}
|
||||
}, [ adminBarHeight, headerHeight ] );
|
||||
|
||||
const tabs = Children.map( children, ( child: JSX.Element ) => {
|
||||
if ( child.type !== ProductFormTab ) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: child.props.name,
|
||||
title: child.props.disabled ? (
|
||||
const getTooltipTabs = ( tabs: TabPanel.Tab[] ) => {
|
||||
return tabs.map( ( tab ) => ( {
|
||||
name: tab.name,
|
||||
title: tab.disabled ? (
|
||||
<Tooltip
|
||||
text={ __(
|
||||
'Manage individual variation details in the Options tab.',
|
||||
|
@ -54,49 +61,61 @@ export const ProductFormLayout: React.FC< {
|
|||
>
|
||||
<span className="woocommerce-product-form-tab__item-inner">
|
||||
<span className="woocommerce-product-form-tab__item-inner-text">
|
||||
{ child.props.title }
|
||||
{ tab.title }
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span className="woocommerce-product-form-tab__item-inner">
|
||||
<span className="woocommerce-product-form-tab__item-inner-text">
|
||||
{ child.props.title }
|
||||
{ tab.title }
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
disabled: child.props.disabled,
|
||||
};
|
||||
} );
|
||||
disabled: tab.disabled,
|
||||
} ) );
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel
|
||||
className="product-form-layout"
|
||||
activeClass="is-active"
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Disabled properties will be included in newer versions of Gutenberg.
|
||||
tabs={ tabs }
|
||||
initialTabName={ query.tab ?? tabs[ 0 ].name }
|
||||
onSelect={ ( tabName: string ) => {
|
||||
window.document.documentElement.scrollTop = 0;
|
||||
navigateTo( {
|
||||
url: getNewPath( { tab: tabName } ),
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ ( tab ) => (
|
||||
<>
|
||||
{ Children.map( children, ( child: JSX.Element ) => {
|
||||
if (
|
||||
child.type !== ProductFormTab ||
|
||||
child.props.name !== tab.name
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return child;
|
||||
} ) }
|
||||
</>
|
||||
) }
|
||||
</TabPanel>
|
||||
<>
|
||||
<WooProductTabItem.Slot
|
||||
template={ 'tab/' + id }
|
||||
fillProps={ { product } }
|
||||
>
|
||||
{ ( tabs, childrenMap ) =>
|
||||
tabs.length > 0 ? (
|
||||
<TabPanel
|
||||
className="product-form-layout"
|
||||
activeClass="is-active"
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Disabled properties will be included in newer versions of Gutenberg.
|
||||
tabs={ getTooltipTabs( tabs ) }
|
||||
initialTabName={ query.tab ?? tabs[ 0 ].name }
|
||||
onSelect={ ( tabName: string ) => {
|
||||
window.document.documentElement.scrollTop = 0;
|
||||
navigateTo( {
|
||||
url: getNewPath( { tab: tabName } ),
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ ( tab ) => {
|
||||
const classes = classnames(
|
||||
'woocommerce-product-form-tab',
|
||||
'woocommerce-product-form-tab__' + tab.name
|
||||
);
|
||||
const child = childrenMap[ tab.name ];
|
||||
return (
|
||||
<div className={ classes } key={ tab.name }>
|
||||
{ typeof child === 'function'
|
||||
? child( product )
|
||||
: child }
|
||||
</div>
|
||||
);
|
||||
} }
|
||||
</TabPanel>
|
||||
) : null
|
||||
}
|
||||
</WooProductTabItem.Slot>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
Form,
|
||||
FormRef,
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
SlotContextProvider,
|
||||
} from '@woocommerce/components';
|
||||
import { Form, FormRef, SlotContextProvider } from '@woocommerce/components';
|
||||
import { PartialProduct, Product } from '@woocommerce/data';
|
||||
import { PluginArea } from '@wordpress/plugins';
|
||||
import { Ref } from 'react';
|
||||
|
@ -16,17 +11,8 @@ import { Ref } from 'react';
|
|||
*/
|
||||
import { ProductFormHeader } from './layout/product-form-header';
|
||||
import { ProductFormLayout } from './layout/product-form-layout';
|
||||
import { ProductVariationsSection } from './sections/product-variations-section';
|
||||
import { validate } from './product-validation';
|
||||
import { OptionsSection } from './sections/options-section';
|
||||
import { ProductFormFooter } from './layout/product-form-footer';
|
||||
import { ProductFormTab } from './product-form-tab';
|
||||
import {
|
||||
TAB_GENERAL_ID,
|
||||
TAB_SHIPPING_ID,
|
||||
TAB_INVENTORY_ID,
|
||||
TAB_PRICING_ID,
|
||||
} from './fills/constants';
|
||||
|
||||
export const ProductForm: React.FC< {
|
||||
product?: PartialProduct;
|
||||
|
@ -49,51 +35,7 @@ export const ProductForm: React.FC< {
|
|||
validate={ validate }
|
||||
>
|
||||
<ProductFormHeader />
|
||||
<ProductFormLayout>
|
||||
<ProductFormTab name="general" title="General">
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_GENERAL_ID }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
<ProductFormTab
|
||||
name="pricing"
|
||||
title="Pricing"
|
||||
disabled={ !! product?.variations?.length }
|
||||
>
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_PRICING_ID }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
<ProductFormTab
|
||||
name="inventory"
|
||||
title="Inventory"
|
||||
disabled={ !! product?.variations?.length }
|
||||
>
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_INVENTORY_ID }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
<ProductFormTab
|
||||
name="shipping"
|
||||
title="Shipping"
|
||||
disabled={ !! product?.variations?.length }
|
||||
>
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_SHIPPING_ID }
|
||||
fillProps={ { product } }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
{ window.wcAdminFeatures[
|
||||
'product-variation-management'
|
||||
] ? (
|
||||
<ProductFormTab name="options" title="Options">
|
||||
<OptionsSection />
|
||||
<ProductVariationsSection />
|
||||
</ProductFormTab>
|
||||
) : (
|
||||
<></>
|
||||
) }
|
||||
</ProductFormLayout>
|
||||
<ProductFormLayout id="general" product={ product } />
|
||||
<ProductFormFooter />
|
||||
{ /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ }
|
||||
<PluginArea scope="woocommerce-product-editor" />
|
||||
|
|
|
@ -3,14 +3,9 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import {
|
||||
Form,
|
||||
FormRef,
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
SlotContextProvider,
|
||||
} from '@woocommerce/components';
|
||||
import { PartialProduct, ProductVariation } from '@woocommerce/data';
|
||||
import { Form, FormRef, SlotContextProvider } from '@woocommerce/components';
|
||||
import { PluginArea } from '@wordpress/plugins';
|
||||
import { PartialProduct, ProductVariation } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -18,15 +13,8 @@ import { PluginArea } from '@wordpress/plugins';
|
|||
import PostsNavigation from './shared/posts-navigation';
|
||||
import { ProductFormLayout } from './layout/product-form-layout';
|
||||
import { ProductFormFooter } from './layout/product-form-footer';
|
||||
import { ProductFormTab } from './product-form-tab';
|
||||
import { ProductVariationDetailsSection } from './sections/product-variation-details-section';
|
||||
import { ProductVariationFormHeader } from './layout/product-variation-form-header';
|
||||
import useProductVariationNavigation from './hooks/use-product-variation-navigation';
|
||||
import {
|
||||
TAB_INVENTORY_ID,
|
||||
TAB_SHIPPING_ID,
|
||||
TAB_PRICING_ID,
|
||||
} from './fills/constants';
|
||||
|
||||
import './product-variation-form.scss';
|
||||
|
||||
|
@ -60,27 +48,11 @@ export const ProductVariationForm: React.FC< {
|
|||
ref={ formRef }
|
||||
>
|
||||
<ProductVariationFormHeader />
|
||||
<ProductFormLayout key={ productVariation.id }>
|
||||
<ProductFormTab name="general" title="General">
|
||||
<ProductVariationDetailsSection />
|
||||
</ProductFormTab>
|
||||
<ProductFormTab name="pricing" title="Pricing">
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_PRICING_ID }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
<ProductFormTab name="inventory" title="Inventory">
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_INVENTORY_ID }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
<ProductFormTab name="shipping" title="Shipping">
|
||||
<WooProductSectionItem.Slot
|
||||
location={ TAB_SHIPPING_ID }
|
||||
fillProps={ { product } }
|
||||
/>
|
||||
</ProductFormTab>
|
||||
</ProductFormLayout>
|
||||
<ProductFormLayout
|
||||
key={ productVariation.id }
|
||||
id="variation"
|
||||
product={ productVariation as PartialProduct }
|
||||
/>
|
||||
<ProductFormFooter />
|
||||
|
||||
<div className="product-variation-form__navigation">
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add slot fill support for tabs for the new product management MVP.
|
Loading…
Reference in New Issue