[External products] Product details (#41442)
* Add buy button section * Enable external product support into the product block editor * Hide buy button section when product type is not external * Remove BaseControl from TextControl since it's not required anymore, InputControl takes care of that now * Add type and suffix support to the product-text-field block * Add the placeholder to the external url input and remove required constraint to the buy button text * Set the url icon link type to external * Fix input border to be red when invalida now that base control is not present twice * Set the min height to 36px to match others non InputControls components height * Extends required constrain to also support a custom error message * Extends the product-text-field validation system * Add product-text-field documentation * Add changelog files * Fix php linter error * Fix compilation error * Fix linter errors
This commit is contained in:
parent
89514921f9
commit
ed0d38c44b
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add support for custom validation message to the generic text block
|
|
@ -8,66 +8,171 @@ A reusable text field for the product editor.
|
|||
|
||||
### label
|
||||
|
||||
- **Type:** `String`
|
||||
- **Required:** `Yes`
|
||||
- **Type:** `String`
|
||||
- **Required:** `Yes`
|
||||
|
||||
Label that appears on top of the field.
|
||||
|
||||
### property
|
||||
|
||||
- **Type:** `String`
|
||||
- **Required:** `Yes`
|
||||
- **Type:** `String`
|
||||
- **Required:** `Yes`
|
||||
|
||||
Property in which the value is stored.
|
||||
|
||||
|
||||
### help
|
||||
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
|
||||
Help text that appears below the field.
|
||||
|
||||
### required
|
||||
|
||||
- **Type:** `Boolean`
|
||||
- **Required:** `No`
|
||||
- **Type:** `Boolean`|`String`
|
||||
- **Required:** `No`
|
||||
|
||||
Indicates and enforces that the field is required.
|
||||
If the value is string it will be used as the custom error message.
|
||||
|
||||
### tooltip
|
||||
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
|
||||
If provided, shows a tooltip next to the label with additional information.
|
||||
|
||||
### placeholder
|
||||
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
|
||||
Placeholder text that appears in the field when it's empty.
|
||||
|
||||
### type
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Default:** `'text'`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the type of the input. The `value` can be [any valid input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types). The message is used as a custom error `message` for the [typeMismatch](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/typeMismatch) validity.
|
||||
|
||||
### pattern
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `String`
|
||||
- **Required:** `Yes`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the validation pattern of the input. The `value` can be [any valid regular expression](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern). The `message` is used as a custom error message for the [patternMismatch](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/patternMismatch) validity.
|
||||
|
||||
### minLength
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `Number`
|
||||
- **Required:** `Yes`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the minimum string length constraint of the input. The `value` can be [any positive integer](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/minLength). The `message` is used as a custom error message for the [tooShort](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/tooShort) validity.
|
||||
|
||||
### maxLength
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `Number`
|
||||
- **Required:** `Yes`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the maximum string length constraint of the input. The `value` can be [any positive integer](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxLength). The `message` is used as a custom error message for the [tooLong](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/tooLong) validity.
|
||||
|
||||
### min
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `Number`
|
||||
- **Required:** `Yes`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the [minimum](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min) value that is acceptable and valid for the input containing the attribute. The `value` must be less than or equal to the value of the `max` attribute. The `message` is used as a custom error message for the [rangeUnderflow](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/rangeUnderflow) validity.
|
||||
|
||||
### max
|
||||
|
||||
- **Type:** `Object`
|
||||
- `value`
|
||||
- **Type:** `Number`
|
||||
- **Required:** `Yes`
|
||||
- `message`
|
||||
- **Type:** `String`
|
||||
- **Required:** `No`
|
||||
- **Required:** `No`
|
||||
|
||||
Reffers to the [maximum](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max) value that is acceptable and valid for the input containing the attribute. The `value` must be greater than or equal to the value of the `min` attribute. The `message` is used as a custom error message for the [rangeOverflow](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/rangeOverflow) validity.
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a snippet that adds a field similar to the previous screenshot:
|
||||
|
||||
```php
|
||||
$section->add_block(
|
||||
[
|
||||
'id' => 'example-text-meta',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 13,
|
||||
'attributes' => [
|
||||
'label' => 'Text',
|
||||
'property' => 'meta_data.text',
|
||||
'placeholder' => 'Placeholder',
|
||||
'required' => true,
|
||||
'help' => 'Add additional information here',
|
||||
'tooltip' => 'My tooltip'
|
||||
],
|
||||
]
|
||||
array(
|
||||
'id' => 'example-text-meta',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 13,
|
||||
'attributes' => array(
|
||||
'label' => 'Text',
|
||||
'property' => 'meta_data.text',
|
||||
'placeholder' => 'Placeholder',
|
||||
'required' => true,
|
||||
'help' => 'Add additional information here',
|
||||
'tooltip' => 'My tooltip',
|
||||
),
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Here's a snippet that adds fields validations:
|
||||
|
||||
```php
|
||||
$section->add_block(
|
||||
array(
|
||||
'id' => 'product-external-url',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'external_url',
|
||||
'label' => __( 'Link to the external product', 'woocommerce' ),
|
||||
'placeholder' => __( 'Enter the external URL to the product', 'woocommerce' ),
|
||||
'suffix' => true,
|
||||
'type' => array(
|
||||
'value' => 'url',
|
||||
'message' => __( 'Link to the external product is an invalid URL.', 'woocommerce' ),
|
||||
),
|
||||
'minLength' => array(
|
||||
'value' => 8,
|
||||
'message' => __( 'The link must be longer than %d.', 'woocommerce' ),
|
||||
),
|
||||
'required' => __( 'Link to the external product is required.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
```
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
"title": "Product text field",
|
||||
"category": "woocommerce",
|
||||
"description": "A text field for use in the product editor.",
|
||||
"keywords": [
|
||||
"products",
|
||||
"text"
|
||||
],
|
||||
"keywords": [ "products", "text" ],
|
||||
"textdomain": "default",
|
||||
"attributes": {
|
||||
"label": {
|
||||
|
@ -27,21 +24,29 @@
|
|||
"tooltip": {
|
||||
"type": "string"
|
||||
},
|
||||
"suffix": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": {
|
||||
"type": "object"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"type": "object"
|
||||
},
|
||||
"validationRegex": {
|
||||
"type": "string"
|
||||
},
|
||||
"validationErrorMessage": {
|
||||
"type": "string"
|
||||
"pattern": {
|
||||
"type": "object"
|
||||
},
|
||||
"minLength": {
|
||||
"type": "number"
|
||||
"type": "object"
|
||||
},
|
||||
"maxLength": {
|
||||
"type": "number"
|
||||
"type": "object"
|
||||
},
|
||||
"min": {
|
||||
"type": "object"
|
||||
},
|
||||
"max": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
|
|
|
@ -1,92 +1,167 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { useWooBlockProps } from '@woocommerce/block-templates';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { createElement, useRef } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Icon, external } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useValidation } from '../../../contexts/validation-context';
|
||||
import useProductEntityProp from '../../../hooks/use-product-entity-prop';
|
||||
import { TextBlockAttributes } from './types';
|
||||
import { ProductEditorBlockEditProps } from '../../../types';
|
||||
import { TextControl } from '../../../components/text-control';
|
||||
import { useValidation } from '../../../contexts/validation-context';
|
||||
import { useProductEdits } from '../../../hooks/use-product-edits';
|
||||
import useProductEntityProp from '../../../hooks/use-product-entity-prop';
|
||||
import { ProductEditorBlockEditProps } from '../../../types';
|
||||
import { TextBlockAttributes } from './types';
|
||||
|
||||
export function Edit( {
|
||||
attributes,
|
||||
context: { postType },
|
||||
}: ProductEditorBlockEditProps< TextBlockAttributes > ) {
|
||||
const blockProps = useWooBlockProps( attributes );
|
||||
|
||||
const {
|
||||
property,
|
||||
label,
|
||||
placeholder,
|
||||
required,
|
||||
validationRegex,
|
||||
validationErrorMessage,
|
||||
pattern,
|
||||
minLength,
|
||||
maxLength,
|
||||
min,
|
||||
max,
|
||||
help,
|
||||
tooltip,
|
||||
disabled,
|
||||
type,
|
||||
suffix,
|
||||
} = attributes;
|
||||
|
||||
const [ value, setValue ] = useProductEntityProp< string >( property, {
|
||||
postType,
|
||||
fallbackValue: '',
|
||||
} );
|
||||
|
||||
const { hasEdit } = useProductEdits();
|
||||
|
||||
const inputRef = useRef< HTMLInputElement >( null );
|
||||
|
||||
const { error, validate } = useValidation< Product >(
|
||||
property,
|
||||
async function validator() {
|
||||
if ( typeof value !== 'string' ) {
|
||||
return __(
|
||||
'Unexpected property type assigned to field.',
|
||||
'woocommerce'
|
||||
if ( ! inputRef.current ) return;
|
||||
|
||||
const input = inputRef.current;
|
||||
|
||||
let customErrorMessage = '';
|
||||
|
||||
if ( input.validity.typeMismatch ) {
|
||||
customErrorMessage =
|
||||
type?.message ??
|
||||
__( 'Invalid value for the field.', 'woocommerce' );
|
||||
}
|
||||
if ( input.validity.valueMissing ) {
|
||||
customErrorMessage =
|
||||
typeof required === 'string'
|
||||
? required
|
||||
: __( 'This field is required.', 'woocommerce' );
|
||||
}
|
||||
if ( input.validity.patternMismatch ) {
|
||||
customErrorMessage =
|
||||
pattern?.message ??
|
||||
__( 'Invalid value for the field.', 'woocommerce' );
|
||||
}
|
||||
if ( input.validity.tooShort ) {
|
||||
// eslint-disable-next-line @wordpress/valid-sprintf
|
||||
customErrorMessage = sprintf(
|
||||
minLength?.message ??
|
||||
/* translators: %d: minimum length */
|
||||
__(
|
||||
'The minimum length of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
minLength?.value
|
||||
);
|
||||
}
|
||||
if ( required && ! value ) {
|
||||
return __( 'This field is required.', 'woocommerce' );
|
||||
}
|
||||
if ( validationRegex ) {
|
||||
const regExp = new RegExp( validationRegex );
|
||||
if ( ! regExp.test( value ) ) {
|
||||
return (
|
||||
validationErrorMessage ||
|
||||
__( 'Invalid value for the field.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( typeof minLength === 'number' && value.length < minLength ) {
|
||||
return sprintf(
|
||||
/* translators: %d: minimum length */
|
||||
__(
|
||||
'The minimum length of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
minLength
|
||||
if ( input.validity.tooLong ) {
|
||||
// eslint-disable-next-line @wordpress/valid-sprintf
|
||||
customErrorMessage = sprintf(
|
||||
maxLength?.message ??
|
||||
/* translators: %d: maximum length */
|
||||
__(
|
||||
'The maximum length of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
maxLength?.value
|
||||
);
|
||||
}
|
||||
if ( typeof maxLength === 'number' && value.length > maxLength ) {
|
||||
return sprintf(
|
||||
/* translators: %d: maximum length */
|
||||
__(
|
||||
'The maximum length of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
maxLength
|
||||
if ( input.validity.rangeUnderflow ) {
|
||||
// eslint-disable-next-line @wordpress/valid-sprintf
|
||||
customErrorMessage = sprintf(
|
||||
min?.message ??
|
||||
/* translators: %d: minimum length */
|
||||
__(
|
||||
'The minimum value of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
min?.value
|
||||
);
|
||||
}
|
||||
if ( input.validity.rangeOverflow ) {
|
||||
// eslint-disable-next-line @wordpress/valid-sprintf
|
||||
customErrorMessage = sprintf(
|
||||
max?.message ??
|
||||
/* translators: %d: maximum length */
|
||||
__(
|
||||
'The maximum value of the field is %d',
|
||||
'woocommerce'
|
||||
),
|
||||
max?.value
|
||||
);
|
||||
}
|
||||
|
||||
input.setCustomValidity( customErrorMessage );
|
||||
|
||||
if ( ! input.validity.valid ) {
|
||||
return input.validationMessage;
|
||||
}
|
||||
},
|
||||
[ value ]
|
||||
[ type, required, pattern, minLength, maxLength, min, max ]
|
||||
);
|
||||
|
||||
function getSuffix() {
|
||||
if ( ! suffix || ! value || ! inputRef.current ) return;
|
||||
|
||||
const isValidUrl =
|
||||
inputRef.current.type === 'url' &&
|
||||
! inputRef.current.validity.typeMismatch;
|
||||
|
||||
if ( suffix === true && isValidUrl ) {
|
||||
return (
|
||||
<Link
|
||||
type="external"
|
||||
href={ value }
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="wp-block-woocommerce-product-text-field__suffix-link"
|
||||
>
|
||||
<Icon icon={ external } size={ 20 } />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return typeof suffix === 'string' ? suffix : undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<TextControl
|
||||
ref={ inputRef }
|
||||
type={ type?.value ?? 'text' }
|
||||
value={ value }
|
||||
disabled={ disabled }
|
||||
label={ label }
|
||||
|
@ -99,8 +174,14 @@ export function Edit( {
|
|||
error={ error }
|
||||
help={ help }
|
||||
placeholder={ placeholder }
|
||||
required={ required }
|
||||
tooltip={ tooltip }
|
||||
suffix={ getSuffix() }
|
||||
required={ Boolean( required ) }
|
||||
pattern={ pattern?.value }
|
||||
minLength={ minLength?.value }
|
||||
maxLength={ maxLength?.value }
|
||||
min={ min?.value }
|
||||
max={ max?.value }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.wp-block-woocommerce-product-text-field {
|
||||
&__suffix-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $grid-unit-30;
|
||||
height: $grid-unit-30;
|
||||
fill: $gray-700;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { HTMLInputTypeAttribute } from 'react';
|
||||
import type { BlockAttributes } from '@wordpress/blocks';
|
||||
|
||||
export interface TextBlockAttributes extends BlockAttributes {
|
||||
|
@ -9,9 +10,12 @@ export interface TextBlockAttributes extends BlockAttributes {
|
|||
help?: string;
|
||||
tooltip?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
validationRegex?: string;
|
||||
validationErrorMessage?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
suffix?: boolean | string;
|
||||
required?: boolean | string;
|
||||
type?: { value?: HTMLInputTypeAttribute; message?: string };
|
||||
pattern?: { value: string; message?: string };
|
||||
minLength?: { value: number; message?: string };
|
||||
maxLength?: { value: number; message?: string };
|
||||
min?: { value: number; message?: string };
|
||||
max?: { value: number; message?: string };
|
||||
}
|
||||
|
|
|
@ -42,6 +42,25 @@
|
|||
max-width: unset;
|
||||
}
|
||||
|
||||
.components-base-control {
|
||||
&.has-error {
|
||||
.components-input-control__backdrop {
|
||||
border-color: $studio-red-50;
|
||||
}
|
||||
|
||||
.components-base-control__help {
|
||||
color: $studio-red-50;
|
||||
}
|
||||
}
|
||||
|
||||
.components-input-control__container .components-input-control__input {
|
||||
min-height: $grid-unit-40 + $grid-unit-05;
|
||||
}
|
||||
}
|
||||
|
||||
// This is wrong for @wordpress/components/InputControl since it is
|
||||
// wrapped within the BaseControl by default. So it does not need
|
||||
// to be wrapped again.
|
||||
.has-error {
|
||||
.components-base-control {
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -46,3 +46,8 @@ export { NumberControl as __experimentalNumberControl } from './number-control';
|
|||
export * from './product-page-skeleton';
|
||||
|
||||
export * from './modal-editor-welcome-guide';
|
||||
|
||||
export {
|
||||
TextControl as __experimentalTextControl,
|
||||
TextControlProps,
|
||||
} from './text-control';
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './text-control';
|
||||
export * from './types';
|
||||
|
|
|
@ -1,52 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
import { Ref } from 'react';
|
||||
import { createElement, forwardRef } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
BaseControl,
|
||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||
__experimentalInputControl as InputControl,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Label } from '../label/label';
|
||||
import { TextControlProps } from './types';
|
||||
|
||||
export type TextProps = {
|
||||
value?: string;
|
||||
onChange: ( selected: string ) => void;
|
||||
label: string;
|
||||
suffix?: string;
|
||||
help?: string;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
onBlur?: () => void;
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const TextControl: React.FC< TextProps > = ( {
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
help,
|
||||
error,
|
||||
onBlur,
|
||||
placeholder,
|
||||
required,
|
||||
tooltip,
|
||||
disabled,
|
||||
}: TextProps ) => {
|
||||
const textControlId = useInstanceId(
|
||||
BaseControl,
|
||||
'text-control'
|
||||
) as string;
|
||||
export const TextControl = forwardRef( function ForwardedTextControl(
|
||||
{
|
||||
label,
|
||||
help,
|
||||
error,
|
||||
tooltip,
|
||||
className,
|
||||
required,
|
||||
onChange,
|
||||
onBlur,
|
||||
...props
|
||||
}: TextControlProps,
|
||||
ref: Ref< HTMLInputElement >
|
||||
) {
|
||||
return (
|
||||
<BaseControl
|
||||
id={ textControlId }
|
||||
<InputControl
|
||||
{ ...props }
|
||||
ref={ ref }
|
||||
className={ classNames( className, {
|
||||
'has-error': error,
|
||||
} ) }
|
||||
label={
|
||||
<Label
|
||||
label={ label }
|
||||
|
@ -54,19 +43,10 @@ export const TextControl: React.FC< TextProps > = ( {
|
|||
tooltip={ tooltip }
|
||||
/>
|
||||
}
|
||||
className={ classNames( {
|
||||
'has-error': error,
|
||||
} ) }
|
||||
required={ required }
|
||||
help={ error || help }
|
||||
>
|
||||
<InputControl
|
||||
id={ textControlId }
|
||||
disabled={ disabled }
|
||||
placeholder={ placeholder }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
onBlur={ onBlur }
|
||||
></InputControl>
|
||||
</BaseControl>
|
||||
onChange={ onChange }
|
||||
onBlur={ onBlur }
|
||||
/>
|
||||
);
|
||||
};
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* This must inherit the InputControlProp from @wordpress/components
|
||||
* but it has not been exported yet
|
||||
*/
|
||||
export type TextControlProps = Omit<
|
||||
React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes< HTMLInputElement >,
|
||||
HTMLInputElement
|
||||
>,
|
||||
'onChange'
|
||||
> & {
|
||||
label: string;
|
||||
help?: string;
|
||||
error?: string;
|
||||
tooltip?: string;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
onChange?( value: string ): void;
|
||||
};
|
|
@ -205,7 +205,6 @@ export function VariationsTableRow( {
|
|||
{ ( variation.status === 'private' ||
|
||||
! variation.regular_price ) && (
|
||||
<Tooltip
|
||||
// @ts-expect-error className is missing in TS, should remove this when it is included.
|
||||
className="woocommerce-attribute-list-item__actions-tooltip"
|
||||
position="top center"
|
||||
text={ NOT_VISIBLE_TEXT }
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add support for external/affiliate products
|
|
@ -45,6 +45,10 @@ class Init {
|
|||
array_push( $this->supported_post_types, 'variable' );
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'product-external-affiliate' ) ) {
|
||||
array_push( $this->supported_post_types, 'external' );
|
||||
}
|
||||
|
||||
$this->redirection_controller = new RedirectionController( $this->supported_post_types );
|
||||
|
||||
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
|
|
|
@ -226,11 +226,84 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
|
|||
'order' => 10,
|
||||
)
|
||||
);
|
||||
|
||||
// External/Affiliate section.
|
||||
if ( Features::is_enabled( 'product-external-affiliate' ) ) {
|
||||
$buy_button_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-buy-button-section',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Buy button', 'woocommerce' ),
|
||||
'description' => __( 'Add a link and choose a label for the button linked to a product sold elsewhere.', 'woocommerce' ),
|
||||
),
|
||||
'hideConditions' => array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type !== "external"',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$buy_button_section->add_block(
|
||||
array(
|
||||
'id' => 'product-external-url',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'external_url',
|
||||
'label' => __( 'Link to the external product', 'woocommerce' ),
|
||||
'placeholder' => __( 'Enter the external URL to the product', 'woocommerce' ),
|
||||
'suffix' => true,
|
||||
'type' => array(
|
||||
'value' => 'url',
|
||||
'message' => __( 'Link to the external product is an invalid URL.', 'woocommerce' ),
|
||||
),
|
||||
'required' => __( 'Link to the external product is required.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns = $buy_button_section->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-columns',
|
||||
'blockName' => 'core/columns',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-column1',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 10,
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'button_text',
|
||||
'label' => __( 'Buy button text', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-column2',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Images section.
|
||||
$images_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-images-section',
|
||||
'order' => 30,
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Images', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
|
@ -258,7 +331,7 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
|
|||
$general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-downloads-section',
|
||||
'order' => 40,
|
||||
'order' => 50,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Downloads', 'woocommerce' ),
|
||||
'description' => __( "Add any files you'd like to make available for the customer to download after purchasing, such as instructions or warranty info.", 'woocommerce' ),
|
||||
|
|
Loading…
Reference in New Issue