Enhance getInputProps to allow passing of non-overridden props (#35034)
* Allow additional props to be passed to the Form getInputProps method * Remove getTextControlProps * Pass additional shared props through getInputProps in shipping * Simplify checkbox props * Unwrap currency props * Use onBlur event to sanitize prices * Add changelog entry * Add option to get checkbox props to form context helpers * Update checkbox tracks handler naming and typing * Fix up usage of getInputProps * Add helper sanitize method * Use sanitize helper method for product input fields * Fix inventory input props after rebase * Fix shipping typo * Fix up form types after rebase * Align all checkboxes on product page * Rename new checkbox helper to getCheckboxControlProps * Add helper method to get select control props * Add data changelog entry * Check for product name length on blur * Add initial value for name to prevent uncontrolled value * Add initial value for sku
This commit is contained in:
parent
be6c26b671
commit
28f8e7f996
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Allow passing of additional props to form inputs
|
|
@ -1,23 +1,22 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ChangeEvent } from 'react';
|
||||
import { createContext, useContext } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
CheckboxProps,
|
||||
ConsumerInputProps,
|
||||
InputProps,
|
||||
SelectControlProps,
|
||||
} from './form';
|
||||
|
||||
export type FormErrors< Values > = {
|
||||
[ P in keyof Values ]?: FormErrors< Values[ P ] > | string;
|
||||
};
|
||||
|
||||
export type InputProps< Value > = {
|
||||
value: Value;
|
||||
checked: boolean;
|
||||
selected?: boolean;
|
||||
onChange: ( value: ChangeEvent< HTMLInputElement > | Value ) => void;
|
||||
onBlur: () => void;
|
||||
className: string | undefined;
|
||||
help: string | null | undefined;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type FormContext< Values extends Record< string, any > > = {
|
||||
values: Values;
|
||||
|
@ -31,9 +30,18 @@ export type FormContext< Values extends Record< string, any > > = {
|
|||
setValue: ( name: string, value: any ) => void;
|
||||
setValues: ( valuesToSet: Values ) => void;
|
||||
handleSubmit: () => Promise< Values >;
|
||||
getCheckboxControlProps< Value extends Values[ keyof Values ] >(
|
||||
name: string,
|
||||
inputProps?: ConsumerInputProps< Values >
|
||||
): CheckboxProps< Values, Value >;
|
||||
getSelectControlProps< Value extends Values[ keyof Values ] >(
|
||||
name: string,
|
||||
inputProps?: ConsumerInputProps< Values >
|
||||
): SelectControlProps< Values, Value >;
|
||||
getInputProps< Value extends Values[ keyof Values ] >(
|
||||
name: string
|
||||
): InputProps< Value >;
|
||||
name: string,
|
||||
inputProps?: ConsumerInputProps< Values >
|
||||
): InputProps< Values, Value >;
|
||||
isValidForm: boolean;
|
||||
resetForm: (
|
||||
initialValues: Values,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
cloneElement,
|
||||
useState,
|
||||
|
@ -17,11 +18,12 @@ import _setWith from 'lodash/setWith';
|
|||
import _get from 'lodash/get';
|
||||
import _clone from 'lodash/clone';
|
||||
import _isEqual from 'lodash/isEqual';
|
||||
import _omit from 'lodash/omit';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { FormContext, FormErrors, InputProps } from './form-context';
|
||||
import { FormContext, FormErrors } from './form-context';
|
||||
|
||||
type FormProps< Values > = {
|
||||
/**
|
||||
|
@ -87,6 +89,40 @@ export type FormRef< Values > = {
|
|||
resetForm: ( initialValues: Values ) => void;
|
||||
};
|
||||
|
||||
export type InputProps< Values, Value > = {
|
||||
value: Value;
|
||||
checked: boolean;
|
||||
selected?: boolean;
|
||||
onChange: (
|
||||
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||
) => void;
|
||||
onBlur: () => void;
|
||||
className: string | undefined;
|
||||
help: string | null | undefined;
|
||||
};
|
||||
|
||||
export type CheckboxProps< Values, Value > = Omit<
|
||||
InputProps< Values, Value >,
|
||||
'value' | 'selected'
|
||||
>;
|
||||
|
||||
export type SelectControlProps< Values, Value > = Omit<
|
||||
InputProps< Values, Value >,
|
||||
'value'
|
||||
> & {
|
||||
value: string | undefined;
|
||||
};
|
||||
|
||||
export type ConsumerInputProps< Values > = {
|
||||
className?: string;
|
||||
onChange?: (
|
||||
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||
) => void;
|
||||
onBlur?: () => void;
|
||||
[ key: string ]: unknown;
|
||||
sanitize?: ( value: Values[ keyof Values ] ) => Values[ keyof Values ];
|
||||
};
|
||||
|
||||
/**
|
||||
* A form component to handle form state and provide input helper props.
|
||||
*/
|
||||
|
@ -268,21 +304,70 @@ function FormComponent< Values extends Record< string, any > >(
|
|||
};
|
||||
|
||||
function getInputProps< Value = Values[ keyof Values ] >(
|
||||
name: string
|
||||
): InputProps< Value > {
|
||||
name: string,
|
||||
inputProps: ConsumerInputProps< Values > = {}
|
||||
): InputProps< Values, Value > {
|
||||
const inputValue = _get( values, name );
|
||||
const isTouched = touched[ name ];
|
||||
const inputError = _get( errors, name );
|
||||
const {
|
||||
className: classNameProp,
|
||||
onBlur: onBlurProp,
|
||||
onChange: onChangeProp,
|
||||
sanitize,
|
||||
...additionalProps
|
||||
} = inputProps;
|
||||
|
||||
return {
|
||||
value: inputValue,
|
||||
checked: Boolean( inputValue ),
|
||||
selected: inputValue,
|
||||
onChange: ( value: ChangeEvent< HTMLInputElement > | Value ) =>
|
||||
handleChange( name, value as Values[ keyof Values ] ),
|
||||
onBlur: () => handleBlur( name ),
|
||||
className: isTouched && inputError ? 'has-error' : undefined,
|
||||
onChange: (
|
||||
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||
) => {
|
||||
handleChange( name, value );
|
||||
if ( onChangeProp ) {
|
||||
onChangeProp( value );
|
||||
}
|
||||
},
|
||||
onBlur: () => {
|
||||
if ( sanitize ) {
|
||||
handleChange( name, sanitize( inputValue ) );
|
||||
}
|
||||
handleBlur( name );
|
||||
if ( onBlurProp ) {
|
||||
onBlurProp();
|
||||
}
|
||||
},
|
||||
className: classnames( classNameProp, {
|
||||
'has-error': isTouched && inputError,
|
||||
} ),
|
||||
help: isTouched ? ( inputError as string ) : null,
|
||||
...additionalProps,
|
||||
};
|
||||
}
|
||||
|
||||
function getCheckboxControlProps< Value = Values[ keyof Values ] >(
|
||||
name: string,
|
||||
inputProps: ConsumerInputProps< Values > = {}
|
||||
): CheckboxProps< Values, Value > {
|
||||
return _omit( getInputProps( name, inputProps ), [
|
||||
'selected',
|
||||
'value',
|
||||
] );
|
||||
}
|
||||
|
||||
function getSelectControlProps< Value = Values[ keyof Values ] >(
|
||||
name: string,
|
||||
inputProps: ConsumerInputProps< Values > = {}
|
||||
): SelectControlProps< Values, Value > {
|
||||
const selectControlProps = getInputProps( name, inputProps );
|
||||
return {
|
||||
...selectControlProps,
|
||||
value:
|
||||
selectControlProps.value === undefined
|
||||
? undefined
|
||||
: String( selectControlProps.value ),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -301,7 +386,9 @@ function FormComponent< Values extends Record< string, any > >(
|
|||
setValue,
|
||||
setValues,
|
||||
handleSubmit,
|
||||
getCheckboxControlProps,
|
||||
getInputProps,
|
||||
getSelectControlProps,
|
||||
isValidForm: ! Object.keys( errors ).length,
|
||||
resetForm,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Add missing shipping class property
|
|
@ -86,6 +86,7 @@ export type Product< Status = ProductStatus, Type = ProductType > = Omit<
|
|||
backordered: boolean;
|
||||
shipping_required: boolean;
|
||||
shipping_taxable: boolean;
|
||||
shipping_class: string;
|
||||
shipping_class_id: number;
|
||||
average_rating: string;
|
||||
rating_count: number;
|
||||
|
|
|
@ -28,7 +28,12 @@ const AddProductPage: React.FC = () => {
|
|||
return (
|
||||
<div className="woocommerce-add-product">
|
||||
<Form< Partial< Product > >
|
||||
initialValues={ { stock_quantity: 0, stock_status: 'instock' } }
|
||||
initialValues={ {
|
||||
name: '',
|
||||
sku: '',
|
||||
stock_quantity: 0,
|
||||
stock_status: 'instock',
|
||||
} }
|
||||
errors={ {} }
|
||||
validate={ validate }
|
||||
>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
.woocommerce-product-form-actions {
|
||||
margin-top: $gap-largest + $gap-smaller;
|
||||
}
|
||||
.woocommerce-product__checkbox,
|
||||
.components-checkbox-control,
|
||||
.components-toggle-control {
|
||||
& > * {
|
||||
margin-bottom: 0;
|
||||
|
@ -17,8 +17,19 @@
|
|||
margin-right: $gap-smaller;
|
||||
}
|
||||
}
|
||||
.components-checkbox-control__label {
|
||||
align-items: center;
|
||||
.components-checkbox-control {
|
||||
&__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__input-container {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.components-base-control__field {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.woocommerce-tooltip {
|
||||
margin-left: $gap-smaller;
|
||||
|
|
|
@ -20,8 +20,8 @@ import {
|
|||
* Internal dependencies
|
||||
*/
|
||||
import './pricing-section.scss';
|
||||
import { formatCurrencyDisplayValue, getCurrencySymbolProps } from './utils';
|
||||
import { ProductSectionLayout } from '../layout/product-section-layout';
|
||||
import { getInputControlProps } from './utils';
|
||||
import { ADMIN_URL } from '../../utils/admin-settings';
|
||||
import { CurrencyContext } from '../../lib/currency-context';
|
||||
import { useProductHelper } from '../use-product-helper';
|
||||
|
@ -44,6 +44,8 @@ export const PricingSection: React.FC = () => {
|
|||
const pricesIncludeTax =
|
||||
taxSettings.woocommerce_prices_include_tax === 'yes';
|
||||
const context = useContext( CurrencyContext );
|
||||
const { getCurrencyConfig, formatAmount } = context;
|
||||
const currencyConfig = getCurrencyConfig();
|
||||
|
||||
const taxIncludedInPriceText = __(
|
||||
'Per your {{link}}store settings{{/link}}, tax is {{strong}}included{{/strong}} in the price.',
|
||||
|
@ -87,14 +89,17 @@ export const PricingSection: React.FC = () => {
|
|||
},
|
||||
} );
|
||||
|
||||
const regularPriceProps = getInputControlProps( {
|
||||
...getInputProps( 'regular_price' ),
|
||||
context,
|
||||
} );
|
||||
const salePriceProps = getInputControlProps( {
|
||||
...getInputProps( 'sale_price' ),
|
||||
context,
|
||||
} );
|
||||
const currencyInputProps = {
|
||||
...getCurrencySymbolProps( currencyConfig ),
|
||||
sanitize: ( value: Product[ keyof Product ] ) => {
|
||||
return sanitizePrice( String( value ) );
|
||||
},
|
||||
};
|
||||
const regularPriceProps = getInputProps(
|
||||
'regular_price',
|
||||
currencyInputProps
|
||||
);
|
||||
const salePriceProps = getInputProps( 'sale_price', currencyInputProps );
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
|
@ -135,10 +140,11 @@ export const PricingSection: React.FC = () => {
|
|||
{ ...regularPriceProps }
|
||||
label={ __( 'List price', 'woocommerce' ) }
|
||||
placeholder={ __( '10.59', 'woocommerce' ) }
|
||||
onChange={ ( value: string ) => {
|
||||
const sanitizedValue = sanitizePrice( value );
|
||||
regularPriceProps?.onChange( sanitizedValue );
|
||||
} }
|
||||
value={ formatCurrencyDisplayValue(
|
||||
String( regularPriceProps?.value ),
|
||||
currencyConfig,
|
||||
formatAmount
|
||||
) }
|
||||
/>
|
||||
</BaseControl>
|
||||
{ ! isTaxSettingsResolving && (
|
||||
|
@ -156,10 +162,11 @@ export const PricingSection: React.FC = () => {
|
|||
{ ...salePriceProps }
|
||||
label={ salePriceTitle }
|
||||
placeholder={ __( '8.59', 'woocommerce' ) }
|
||||
onChange={ ( value: string ) => {
|
||||
const sanitizedValue = sanitizePrice( value );
|
||||
salePriceProps?.onChange( sanitizedValue );
|
||||
} }
|
||||
value={ formatCurrencyDisplayValue(
|
||||
String( salePriceProps?.value ),
|
||||
currencyConfig,
|
||||
formatAmount
|
||||
) }
|
||||
/>
|
||||
</BaseControl>
|
||||
</CardBody>
|
||||
|
|
|
@ -12,19 +12,4 @@
|
|||
margin-left: $gap-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
&__feature-checkbox {
|
||||
.components-base-control__field {
|
||||
display: flex;
|
||||
.components-checkbox-control {
|
||||
&__label {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__input-container {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,20 @@ import { BlockInstance, serialize, parse } from '@wordpress/blocks';
|
|||
import './product-details-section.scss';
|
||||
import { CategoryField } from '../fields/category-field';
|
||||
import { EditProductLinkModal } from '../shared/edit-product-link-modal';
|
||||
import { getCheckboxProps, getTextControlProps } from './utils';
|
||||
import { getCheckboxTracks } from './utils';
|
||||
import { ProductSectionLayout } from '../layout/product-section-layout';
|
||||
|
||||
const PRODUCT_DETAILS_SLUG = 'product-details';
|
||||
|
||||
export const ProductDetailsSection: React.FC = () => {
|
||||
const { getInputProps, values, setValue, touched, errors } =
|
||||
useFormContext< Product >();
|
||||
const {
|
||||
getCheckboxControlProps,
|
||||
getInputProps,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
setValue,
|
||||
} = useFormContext< Product >();
|
||||
const [ showProductLinkEditModal, setShowProductLinkEditModal ] =
|
||||
useState( false );
|
||||
const [ descriptionBlocks, setDescriptionBlocks ] = useState<
|
||||
|
@ -66,7 +72,7 @@ export const ProductDetailsSection: React.FC = () => {
|
|||
};
|
||||
|
||||
const setSkuIfEmpty = () => {
|
||||
if ( values.sku || ! values.name.length ) {
|
||||
if ( values.sku || ! values.name?.length ) {
|
||||
return;
|
||||
}
|
||||
setValue( 'sku', cleanForSlug( values.name ) );
|
||||
|
@ -90,13 +96,9 @@ export const ProductDetailsSection: React.FC = () => {
|
|||
'e.g. 12 oz Coffee Mug',
|
||||
'woocommerce'
|
||||
) }
|
||||
{ ...getTextControlProps(
|
||||
getInputProps( 'name' )
|
||||
) }
|
||||
onBlur={ () => {
|
||||
setSkuIfEmpty();
|
||||
getInputProps( 'name' ).onBlur();
|
||||
} }
|
||||
{ ...getInputProps( 'name', {
|
||||
onBlur: setSkuIfEmpty,
|
||||
} ) }
|
||||
/>
|
||||
{ values.id && ! hasNameError() && permalinkPrefix && (
|
||||
<span className="woocommerce-product-form__secondary-text product-details-section__product-link">
|
||||
|
@ -170,12 +172,10 @@ export const ProductDetailsSection: React.FC = () => {
|
|||
/>
|
||||
</>
|
||||
}
|
||||
{ ...getCheckboxProps( {
|
||||
...getInputProps( 'featured' ),
|
||||
name: 'featured',
|
||||
className:
|
||||
'product-details-section__feature-checkbox',
|
||||
} ) }
|
||||
{ ...getCheckboxControlProps(
|
||||
'featured',
|
||||
getCheckboxTracks( 'featured' )
|
||||
) }
|
||||
/>
|
||||
{ showProductLinkEditModal && (
|
||||
<EditProductLinkModal
|
||||
|
|
|
@ -13,7 +13,6 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { getTextControlProps } from '../utils';
|
||||
|
||||
export const ManageStockSection: React.FC = () => {
|
||||
const { getInputProps } = useFormContext< Product >();
|
||||
|
@ -25,9 +24,7 @@ export const ManageStockSection: React.FC = () => {
|
|||
<TextControl
|
||||
type="number"
|
||||
label={ __( 'Current quantity', 'woocommerce' ) }
|
||||
{ ...getTextControlProps( {
|
||||
...getInputProps( 'stock_quantity' ),
|
||||
} ) }
|
||||
{ ...getInputProps( 'stock_quantity' ) }
|
||||
min={ 0 }
|
||||
/>
|
||||
<TextControl
|
||||
|
@ -38,9 +35,7 @@ export const ManageStockSection: React.FC = () => {
|
|||
__( '%d (store default)', 'woocommerce' ),
|
||||
notifyLowStockAmount
|
||||
) }
|
||||
{ ...getTextControlProps( {
|
||||
...getInputProps( 'low_stock_amount' ),
|
||||
} ) }
|
||||
{ ...getInputProps( 'low_stock_amount' ) }
|
||||
min={ 0 }
|
||||
/>
|
||||
<span className="woocommerce-product-form__secondary-text">
|
||||
|
|
|
@ -16,14 +16,15 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getCheckboxProps, getTextControlProps } from '../utils';
|
||||
import { getCheckboxTracks } from '../utils';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { ProductSectionLayout } from '../../layout/product-section-layout';
|
||||
import { ManageStockSection } from './manage-stock-section';
|
||||
import { ManualStockSection } from './manual-stock-section';
|
||||
|
||||
export const ProductInventorySection: React.FC = () => {
|
||||
const { getInputProps, values } = useFormContext< Product >();
|
||||
const { getCheckboxControlProps, getInputProps, values } =
|
||||
useFormContext< Product >();
|
||||
const canManageStock = getAdminSetting( 'manageStock', 'yes' ) === 'yes';
|
||||
|
||||
return (
|
||||
|
@ -67,7 +68,7 @@ export const ProductInventorySection: React.FC = () => {
|
|||
'washed-oxford-button-down-shirt',
|
||||
'woocommerce'
|
||||
) }
|
||||
{ ...getTextControlProps( getInputProps( 'sku' ) ) }
|
||||
{ ...getInputProps( 'sku' ) }
|
||||
/>
|
||||
{ canManageStock && (
|
||||
<>
|
||||
|
@ -76,8 +77,9 @@ export const ProductInventorySection: React.FC = () => {
|
|||
'Track quantity for this product',
|
||||
'woocommerce'
|
||||
) }
|
||||
{ ...getCheckboxProps(
|
||||
getInputProps( 'manage_stock' )
|
||||
{ ...getCheckboxControlProps(
|
||||
'manage_stock',
|
||||
getCheckboxTracks( 'manage_stock' )
|
||||
) }
|
||||
/>
|
||||
{ values.manage_stock && <ManageStockSection /> }
|
||||
|
|
|
@ -32,7 +32,6 @@ import {
|
|||
} from '../fields/shipping-dimensions-image';
|
||||
import { useProductHelper } from '../use-product-helper';
|
||||
import { AddNewShippingClassModal } from '../shared/add-new-shipping-class-modal';
|
||||
import { getTextControlProps } from './utils';
|
||||
import './product-shipping-section.scss';
|
||||
import {
|
||||
ADD_NEW_SHIPPING_CLASS_OPTION_VALUE,
|
||||
|
@ -74,7 +73,7 @@ function getInterpolatedSizeLabel( mixedString: string ) {
|
|||
* the first category different to `Uncategorized`.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce/issues/34657
|
||||
* @param product The product
|
||||
* @param product The product
|
||||
* @return The default shipping class
|
||||
*/
|
||||
function extractDefaultShippingClassFromProduct(
|
||||
|
@ -94,7 +93,8 @@ function extractDefaultShippingClassFromProduct(
|
|||
export function ProductShippingSection( {
|
||||
product,
|
||||
}: ProductShippingSectionProps ) {
|
||||
const { getInputProps, setValue } = useFormContext< PartialProduct >();
|
||||
const { getInputProps, getSelectControlProps, setValue } =
|
||||
useFormContext< PartialProduct >();
|
||||
const { formatNumber, parseNumber } = useProductHelper();
|
||||
const [ highlightSide, setHighlightSide ] =
|
||||
useState< ShippingDimensionsImageProps[ 'highlight' ] >();
|
||||
|
@ -140,19 +140,29 @@ export function ProductShippingSection( {
|
|||
EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME
|
||||
);
|
||||
|
||||
const selectShippingClassProps = getTextControlProps(
|
||||
getInputProps( 'shipping_class' )
|
||||
const dimensionProps = {
|
||||
onBlur: () => {
|
||||
setHighlightSide( undefined );
|
||||
},
|
||||
sanitize: ( value: PartialProduct[ keyof PartialProduct ] ) =>
|
||||
parseNumber( String( value ) ),
|
||||
suffix: dimensionUnit,
|
||||
};
|
||||
|
||||
const inputWidthProps = getInputProps( 'dimensions.width', dimensionProps );
|
||||
const inputLengthProps = getInputProps(
|
||||
'dimensions.length',
|
||||
dimensionProps
|
||||
);
|
||||
const inputWidthProps = getTextControlProps(
|
||||
getInputProps( 'dimensions.width' )
|
||||
const inputHeightProps = getInputProps(
|
||||
'dimensions.height',
|
||||
dimensionProps
|
||||
);
|
||||
const inputLengthProps = getTextControlProps(
|
||||
getInputProps( 'dimensions.length' )
|
||||
);
|
||||
const inputHeightProps = getTextControlProps(
|
||||
getInputProps( 'dimensions.height' )
|
||||
);
|
||||
const inputWeightProps = getTextControlProps( getInputProps( 'weight' ) );
|
||||
const inputWeightProps = getInputProps( 'weight', {
|
||||
sanitize: ( value: PartialProduct[ keyof PartialProduct ] ) =>
|
||||
parseNumber( String( value ) ),
|
||||
} );
|
||||
const shippingClassProps = getInputProps( 'shipping_class' );
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
|
@ -167,14 +177,8 @@ export function ProductShippingSection( {
|
|||
{ hasResolvedShippingClasses ? (
|
||||
<>
|
||||
<SelectControl
|
||||
{ ...selectShippingClassProps }
|
||||
label={ __( 'Shipping class', 'woocommerce' ) }
|
||||
options={ [
|
||||
...DEFAULT_SHIPPING_CLASS_OPTIONS,
|
||||
...mapShippingClassToSelectOption(
|
||||
shippingClasses ?? []
|
||||
),
|
||||
] }
|
||||
{ ...getSelectControlProps( 'shipping_class' ) }
|
||||
onChange={ ( value: string ) => {
|
||||
if (
|
||||
value ===
|
||||
|
@ -183,8 +187,14 @@ export function ProductShippingSection( {
|
|||
setShowShippingClassModal( true );
|
||||
return;
|
||||
}
|
||||
selectShippingClassProps?.onChange( value );
|
||||
shippingClassProps.onChange( value );
|
||||
} }
|
||||
options={ [
|
||||
...DEFAULT_SHIPPING_CLASS_OPTIONS,
|
||||
...mapShippingClassToSelectOption(
|
||||
shippingClasses ?? []
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<span className="woocommerce-product-form__secondary-text">
|
||||
{ interpolateComponents( {
|
||||
|
@ -235,7 +245,7 @@ export function ProductShippingSection( {
|
|||
<InputControl
|
||||
{ ...inputWidthProps }
|
||||
value={ formatNumber(
|
||||
inputWidthProps.value
|
||||
String( inputWidthProps.value )
|
||||
) }
|
||||
label={ getInterpolatedSizeLabel(
|
||||
__(
|
||||
|
@ -243,19 +253,9 @@ export function ProductShippingSection( {
|
|||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
onChange={ ( value: string ) =>
|
||||
inputWidthProps?.onChange(
|
||||
parseNumber( value )
|
||||
)
|
||||
}
|
||||
onFocus={ () => {
|
||||
setHighlightSide( 'A' );
|
||||
} }
|
||||
onBlur={ () => {
|
||||
setHighlightSide( undefined );
|
||||
inputWidthProps?.onBlur();
|
||||
} }
|
||||
suffix={ dimensionUnit }
|
||||
/>
|
||||
</BaseControl>
|
||||
|
||||
|
@ -267,7 +267,7 @@ export function ProductShippingSection( {
|
|||
<InputControl
|
||||
{ ...inputLengthProps }
|
||||
value={ formatNumber(
|
||||
inputLengthProps.value
|
||||
String( inputLengthProps.value )
|
||||
) }
|
||||
label={ getInterpolatedSizeLabel(
|
||||
__(
|
||||
|
@ -275,19 +275,9 @@ export function ProductShippingSection( {
|
|||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
onChange={ ( value: string ) =>
|
||||
inputLengthProps?.onChange(
|
||||
parseNumber( value )
|
||||
)
|
||||
}
|
||||
onFocus={ () => {
|
||||
setHighlightSide( 'B' );
|
||||
} }
|
||||
onBlur={ () => {
|
||||
setHighlightSide( undefined );
|
||||
inputLengthProps?.onBlur();
|
||||
} }
|
||||
suffix={ dimensionUnit }
|
||||
/>
|
||||
</BaseControl>
|
||||
|
||||
|
@ -299,7 +289,7 @@ export function ProductShippingSection( {
|
|||
<InputControl
|
||||
{ ...inputHeightProps }
|
||||
value={ formatNumber(
|
||||
inputHeightProps.value
|
||||
String( inputHeightProps.value )
|
||||
) }
|
||||
label={ getInterpolatedSizeLabel(
|
||||
__(
|
||||
|
@ -307,19 +297,9 @@ export function ProductShippingSection( {
|
|||
'woocommerce'
|
||||
)
|
||||
) }
|
||||
onChange={ ( value: string ) =>
|
||||
inputHeightProps?.onChange(
|
||||
parseNumber( value )
|
||||
)
|
||||
}
|
||||
onFocus={ () => {
|
||||
setHighlightSide( 'C' );
|
||||
} }
|
||||
onBlur={ () => {
|
||||
setHighlightSide( undefined );
|
||||
inputHeightProps?.onBlur();
|
||||
} }
|
||||
suffix={ dimensionUnit }
|
||||
/>
|
||||
</BaseControl>
|
||||
|
||||
|
@ -331,17 +311,12 @@ export function ProductShippingSection( {
|
|||
<InputControl
|
||||
{ ...inputWeightProps }
|
||||
value={ formatNumber(
|
||||
inputWeightProps.value
|
||||
String( inputWeightProps.value )
|
||||
) }
|
||||
label={ __(
|
||||
'Weight',
|
||||
'woocommerce'
|
||||
) }
|
||||
onChange={ ( value: string ) =>
|
||||
inputWeightProps?.onChange(
|
||||
parseNumber( value )
|
||||
)
|
||||
}
|
||||
suffix={ weightUnit }
|
||||
/>
|
||||
</BaseControl>
|
||||
|
@ -368,10 +343,10 @@ export function ProductShippingSection( {
|
|||
product &&
|
||||
extractDefaultShippingClassFromProduct( product )
|
||||
}
|
||||
onAdd={ ( values ) =>
|
||||
onAdd={ ( shippingClassValues ) =>
|
||||
createProductShippingClass<
|
||||
Promise< ProductShippingClass >
|
||||
>( values ).then( ( value ) => {
|
||||
>( shippingClassValues ).then( ( value ) => {
|
||||
invalidateResolution( 'getProductShippingClasses' );
|
||||
setValue( 'shipping_class', value.slug );
|
||||
return value;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
|
@ -9,85 +10,67 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
*/
|
||||
import { NUMBERS_AND_ALLOWED_CHARS } from '../constants';
|
||||
|
||||
type gettersProps = {
|
||||
context?: {
|
||||
formatAmount: ( number: number | string ) => string;
|
||||
getCurrencyConfig: () => {
|
||||
code: string;
|
||||
symbol: string;
|
||||
symbolPosition: string;
|
||||
decimalSeparator: string;
|
||||
priceFormat: string;
|
||||
thousandSeparator: string;
|
||||
precision: number;
|
||||
};
|
||||
};
|
||||
value: string;
|
||||
name?: string;
|
||||
checked: boolean;
|
||||
selected?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onChange: ( value: any ) => void;
|
||||
onBlur: () => void;
|
||||
className: string | undefined;
|
||||
help: string | null | undefined;
|
||||
type CurrencyConfig = {
|
||||
code: string;
|
||||
symbol: string;
|
||||
symbolPosition: string;
|
||||
decimalSeparator: string;
|
||||
priceFormat: string;
|
||||
thousandSeparator: string;
|
||||
precision: number;
|
||||
};
|
||||
|
||||
export const getCheckboxProps = ( {
|
||||
checked = false,
|
||||
className,
|
||||
name,
|
||||
onBlur,
|
||||
onChange,
|
||||
}: gettersProps ) => {
|
||||
/**
|
||||
* Get additional props to be passed to all checkbox inputs.
|
||||
*
|
||||
* @param {string} name Name of the checkbox
|
||||
* @return {Object} Props.
|
||||
*/
|
||||
export const getCheckboxTracks = ( name: string ) => {
|
||||
return {
|
||||
checked,
|
||||
className: classnames( 'woocommerce-product__checkbox', className ),
|
||||
onChange: ( isChecked: boolean ) => {
|
||||
onChange: (
|
||||
isChecked:
|
||||
| ChangeEvent< HTMLInputElement >
|
||||
| Product[ keyof Product ]
|
||||
) => {
|
||||
recordEvent( `product_checkbox_${ name }`, {
|
||||
checked: isChecked,
|
||||
} );
|
||||
return onChange( isChecked );
|
||||
},
|
||||
onBlur,
|
||||
};
|
||||
};
|
||||
|
||||
export const getTextControlProps = ( {
|
||||
className,
|
||||
onBlur,
|
||||
onChange,
|
||||
value = '',
|
||||
help,
|
||||
}: gettersProps ) => {
|
||||
return {
|
||||
value,
|
||||
className: classnames( 'woocommerce-product__text', className ),
|
||||
onChange,
|
||||
onBlur,
|
||||
help,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInputControlProps = ( {
|
||||
className,
|
||||
context,
|
||||
onBlur,
|
||||
onChange,
|
||||
value = '',
|
||||
help,
|
||||
}: gettersProps ) => {
|
||||
if ( ! context ) {
|
||||
return;
|
||||
}
|
||||
const { formatAmount, getCurrencyConfig } = context;
|
||||
const { decimalSeparator, symbol, symbolPosition, thousandSeparator } =
|
||||
getCurrencyConfig();
|
||||
/**
|
||||
* Get input props for currency related values and symbol positions.
|
||||
*
|
||||
* @param {Object} currencyConfig - Currency context
|
||||
* @return {Object} Props.
|
||||
*/
|
||||
export const getCurrencySymbolProps = ( currencyConfig: CurrencyConfig ) => {
|
||||
const { symbol, symbolPosition } = currencyConfig;
|
||||
const currencyPosition = symbolPosition.includes( 'left' )
|
||||
? 'prefix'
|
||||
: 'suffix';
|
||||
|
||||
// Cleans the value to show.
|
||||
return {
|
||||
[ currencyPosition ]: symbol,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans and formats the currency value shown to the user.
|
||||
*
|
||||
* @param {string} value Form value.
|
||||
* @param {Object} currencyConfig Currency context.
|
||||
* @return {string} Display value.
|
||||
*/
|
||||
export const formatCurrencyDisplayValue = (
|
||||
value: string,
|
||||
currencyConfig: CurrencyConfig,
|
||||
format: ( number: number | string ) => string
|
||||
) => {
|
||||
const { decimalSeparator, thousandSeparator } = currencyConfig;
|
||||
|
||||
const regex = new RegExp(
|
||||
NUMBERS_AND_ALLOWED_CHARS.replace( '%s1', decimalSeparator ).replace(
|
||||
'%s2',
|
||||
|
@ -95,16 +78,6 @@ export const getInputControlProps = ( {
|
|||
),
|
||||
'g'
|
||||
);
|
||||
const currencyString =
|
||||
value === undefined
|
||||
? value
|
||||
: formatAmount( value ).replace( regex, '' );
|
||||
return {
|
||||
value: currencyString,
|
||||
[ currencyPosition ]: symbol,
|
||||
className: classnames( 'woocommerce-product__input', className ),
|
||||
onChange,
|
||||
onBlur,
|
||||
help,
|
||||
};
|
||||
|
||||
return value === undefined ? value : format( value ).replace( regex, '' );
|
||||
};
|
||||
|
|
|
@ -11,7 +11,6 @@ import { ProductShippingClass } from '@woocommerce/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getTextControlProps } from '../../sections/utils';
|
||||
import './add-new-shipping-class-modal.scss';
|
||||
|
||||
export type ShippingClassFormProps = {
|
||||
|
@ -20,16 +19,10 @@ export type ShippingClassFormProps = {
|
|||
};
|
||||
|
||||
function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
||||
const { getInputProps, isValidForm } =
|
||||
const { errors, getInputProps, isValidForm } =
|
||||
useFormContext< ProductShippingClass >();
|
||||
const [ isLoading, setIsLoading ] = useState( false );
|
||||
|
||||
const inputNameProps = getTextControlProps( getInputProps( 'name' ) );
|
||||
const inputSlugProps = getTextControlProps( getInputProps( 'slug' ) );
|
||||
const inputDescriptionProps = getTextControlProps(
|
||||
getInputProps( 'description' )
|
||||
);
|
||||
|
||||
function handleAdd() {
|
||||
setIsLoading( true );
|
||||
onAdd()
|
||||
|
@ -45,11 +38,11 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
return (
|
||||
<div className="woocommerce-add-new-shipping-class-modal__wrapper">
|
||||
<TextControl
|
||||
{ ...inputNameProps }
|
||||
{ ...getInputProps( 'name' ) }
|
||||
label={ __( 'Name', 'woocommerce' ) }
|
||||
/>
|
||||
<TextControl
|
||||
{ ...inputSlugProps }
|
||||
{ ...getInputProps( 'slug' ) }
|
||||
label={ interpolateComponents( {
|
||||
mixedString: __(
|
||||
'Slug {{span}}(optional){{/span}}',
|
||||
|
@ -63,7 +56,7 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
} ) }
|
||||
/>
|
||||
<TextControl
|
||||
{ ...inputDescriptionProps }
|
||||
{ ...getInputProps( 'description' ) }
|
||||
label={ interpolateComponents( {
|
||||
mixedString: __(
|
||||
'Description {{span}}(optional){{/span}}',
|
||||
|
@ -76,7 +69,7 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
},
|
||||
} ) }
|
||||
help={
|
||||
inputDescriptionProps?.help ??
|
||||
errors?.description ??
|
||||
__(
|
||||
'Describe how you and other store administrators can use this shipping class.',
|
||||
'woocommerce'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Unwrap product page input props and pass via getInputProps
|
Loading…
Reference in New Issue