Merged in feature/MAW-855-import-code-into-aws (pull request #2)
code import from pantheon * code import from pantheon
This commit is contained in:
@@ -11,6 +11,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { getSettingWithCoercion } from '@woocommerce/settings';
|
||||
import {
|
||||
AttributeObject,
|
||||
AttributeTerm,
|
||||
isAttributeQueryCollection,
|
||||
isAttributeTermCollection,
|
||||
isBoolean,
|
||||
@@ -47,7 +48,7 @@ const ActiveAttributeFilters = ( {
|
||||
displayStyle,
|
||||
isLoadingCallback,
|
||||
}: ActiveAttributeFiltersProps ) => {
|
||||
const { results, isLoading } = useCollection( {
|
||||
const { results, isLoading } = useCollection< AttributeTerm >( {
|
||||
namespace: '/wc/store/v1',
|
||||
resourceName: 'products/attributes/terms',
|
||||
resourceValues: [ attributeObject.id ],
|
||||
@@ -73,7 +74,7 @@ const ActiveAttributeFilters = ( {
|
||||
const attributeLabel = attributeObject.label;
|
||||
|
||||
const filteringForPhpTemplate = getSettingWithCoercion(
|
||||
'is_rendering_php_template',
|
||||
'isRenderingPhpTemplate',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useQueryStateByKey } from '@woocommerce/base-context/hooks';
|
||||
import { getSetting, getSettingWithCoercion } from '@woocommerce/settings';
|
||||
import { useMemo, useEffect, useState } from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import { Label } from '@woocommerce/blocks-components';
|
||||
import {
|
||||
isAttributeQueryCollection,
|
||||
isBoolean,
|
||||
@@ -59,7 +59,7 @@ const ActiveFiltersBlock = ( {
|
||||
const isMounted = useIsMounted();
|
||||
const componentHasMounted = isMounted();
|
||||
const filteringForPhpTemplate = getSettingWithCoercion(
|
||||
'is_rendering_php_template',
|
||||
'isRenderingPhpTemplate',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
@@ -323,7 +323,7 @@ const ActiveFiltersBlock = ( {
|
||||
);
|
||||
|
||||
const hasFilterableProducts = getSettingWithCoercion(
|
||||
'has_filterable_products',
|
||||
'hasFilterableProducts',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
@@ -160,7 +160,6 @@
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
line-height: 16px;
|
||||
padding: 0;
|
||||
margin: 0 0.5em 0 0;
|
||||
color: currentColor;
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { formatPrice } from '@woocommerce/price-format';
|
||||
import { RemovableChip } from '@woocommerce/base-components/chip';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import { Label, RemovableChip } from '@woocommerce/blocks-components';
|
||||
import { getQueryArgs, addQueryArgs, removeQueryArgs } from '@wordpress/url';
|
||||
import { changeUrl } from '@woocommerce/utils';
|
||||
import { Icon, closeSmall } from '@wordpress/icons';
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getSettingWithCoercion } from '@woocommerce/settings';
|
||||
import { getQueryArgs, removeQueryArgs } from '@wordpress/url';
|
||||
import {
|
||||
AttributeQuery,
|
||||
AttributeTerm,
|
||||
isAttributeQueryCollection,
|
||||
isBoolean,
|
||||
isString,
|
||||
@@ -72,19 +73,19 @@ const AttributeFilterBlock = ( {
|
||||
getNotice?: GetNotice;
|
||||
} ) => {
|
||||
const hasFilterableProducts = getSettingWithCoercion(
|
||||
'has_filterable_products',
|
||||
'hasFilterableProducts',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
const filteringForPhpTemplate = getSettingWithCoercion(
|
||||
'is_rendering_php_template',
|
||||
'isRenderingPhpTemplate',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
const pageUrl = getSettingWithCoercion(
|
||||
'page_url',
|
||||
'pageUrl',
|
||||
window.location.href,
|
||||
isString
|
||||
);
|
||||
@@ -124,11 +125,12 @@ const AttributeFilterBlock = ( {
|
||||
useQueryStateByKey( 'attributes', [] );
|
||||
|
||||
const { results: attributeTerms, isLoading: attributeTermsLoading } =
|
||||
useCollection( {
|
||||
useCollection< AttributeTerm >( {
|
||||
namespace: '/wc/store/v1',
|
||||
resourceName: 'products/attributes/terms',
|
||||
resourceValues: [ attributeObject?.id || 0 ],
|
||||
shouldSelect: blockAttributes.attributeId > 0,
|
||||
query: { orderby: 'menu_order' },
|
||||
} );
|
||||
|
||||
const { results: filteredCounts, isLoading: filteredCountsLoading } =
|
||||
@@ -544,9 +546,6 @@ const AttributeFilterBlock = ( {
|
||||
'single-selection': ! multiple,
|
||||
'is-loading': isLoading,
|
||||
} ) }
|
||||
style={ {
|
||||
borderStyle: 'none',
|
||||
} }
|
||||
suggestions={ displayedOptions
|
||||
.filter(
|
||||
( option ) =>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import CheckboxList from '@woocommerce/base-components/checkbox-list';
|
||||
import { CheckboxList } from '@woocommerce/blocks-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
@@ -69,7 +69,6 @@ const Edit = ( {
|
||||
}: EditProps ) => {
|
||||
const {
|
||||
attributeId,
|
||||
className,
|
||||
displayStyle,
|
||||
heading,
|
||||
headingLevel,
|
||||
@@ -353,6 +352,7 @@ const Edit = ( {
|
||||
href={ getAdminLink(
|
||||
'edit.php?post_type=product&page=product_attributes'
|
||||
) }
|
||||
target="_top"
|
||||
>
|
||||
{ __( 'Add new attribute', 'woo-gutenberg-products-block' ) +
|
||||
' ' }
|
||||
@@ -362,6 +362,7 @@ const Edit = ( {
|
||||
className="wc-block-attribute-filter__read_more_button"
|
||||
isTertiary
|
||||
href="https://docs.woocommerce.com/document/managing-product-taxonomies/"
|
||||
target="_blank"
|
||||
>
|
||||
{ __( 'Learn more', 'woo-gutenberg-products-block' ) }
|
||||
</Button>
|
||||
@@ -419,12 +420,7 @@ const Edit = ( {
|
||||
{ isEditing ? (
|
||||
renderEditMode()
|
||||
) : (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-attribute-filter'
|
||||
) }
|
||||
>
|
||||
<div className={ classnames( 'wc-block-attribute-filter' ) }>
|
||||
{ heading && (
|
||||
<BlockTitle
|
||||
className="wc-block-attribute-filter__title"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
import { Icon, category } from '@wordpress/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -27,13 +26,6 @@ registerBlockType( metadata, {
|
||||
},
|
||||
supports: {
|
||||
...metadata.supports,
|
||||
...( isFeaturePluginBuild() && {
|
||||
__experimentalBorder: {
|
||||
radius: false,
|
||||
color: true,
|
||||
width: false,
|
||||
},
|
||||
} ),
|
||||
},
|
||||
attributes: {
|
||||
...metadata.attributes,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { SVG, Rect } from '@wordpress/primitives';
|
||||
|
||||
export const queryPaginationIcon = (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<Rect
|
||||
x="4"
|
||||
y="10.5"
|
||||
width="6"
|
||||
height="3"
|
||||
rx="1.5"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<Rect
|
||||
x="12"
|
||||
y="10.5"
|
||||
width="3"
|
||||
height="3"
|
||||
rx="1.5"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<Rect
|
||||
x="17"
|
||||
y="10.5"
|
||||
width="3"
|
||||
height="3"
|
||||
rx="1.5"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</SVG>
|
||||
);
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
import { Icon, queryPagination } from '@wordpress/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import { queryPaginationIcon } from './icon';
|
||||
import './style.scss';
|
||||
|
||||
const featurePluginSupport = {
|
||||
@@ -32,7 +33,7 @@ registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ queryPagination }
|
||||
icon={ queryPaginationIcon }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
const expressIcon = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="#1E1E1E"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M18.25 12a6.25 6.25 0 1 1-12.5 0 6.25 6.25 0 0 1 12.5 0Z"
|
||||
/>
|
||||
<path fill="#1E1E1E" d="M10 3h4v3h-4z" />
|
||||
<rect width="1.5" height="5" x="11.25" y="8" fill="#1E1E1E" rx=".75" />
|
||||
<path
|
||||
fill="#1E1E1E"
|
||||
d="m15.7 4.816 1.66 1.078-1.114 1.718-1.661-1.078z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default expressIcon;
|
||||
@@ -91,7 +91,7 @@ const CheckoutExpressPayment = () => {
|
||||
headingLevel="2"
|
||||
>
|
||||
{ __(
|
||||
'Express checkout',
|
||||
'Express Checkout',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Title>
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
$border-width: 1px;
|
||||
$border-radius: 5px;
|
||||
|
||||
.wc-block-components-express-payment {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
|
||||
// nested class to avoid conflict with .editor-styles-wrapper ul
|
||||
.wc-block-components-express-payment__event-buttons {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr));
|
||||
grid-gap: 10px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
> li {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
@@ -27,17 +21,22 @@ $border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
.wc-block-components-express-payment__event-buttons {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-express-payment--checkout {
|
||||
/* stylelint-disable-next-line function-calc-no-unspaced-operator */
|
||||
margin-top: calc($border-radius * 3);
|
||||
margin-top: calc($universal-border-radius * 3);
|
||||
|
||||
.wc-block-components-express-payment__event-buttons {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr));
|
||||
grid-gap: 10px;
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-express-payment__title-container {
|
||||
display: flex;
|
||||
@@ -45,17 +44,17 @@ $border-radius: 5px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -$border-radius;
|
||||
top: -$universal-border-radius;
|
||||
vertical-align: middle;
|
||||
|
||||
// Pseudo-elements used to show the border before and after the title.
|
||||
&::before {
|
||||
border-left: $border-width solid currentColor;
|
||||
border-top: $border-width solid currentColor;
|
||||
border-radius: $border-radius 0 0 0;
|
||||
border-radius: $universal-border-radius 0 0 0;
|
||||
content: "";
|
||||
display: block;
|
||||
height: $border-radius - $border-width;
|
||||
height: $universal-border-radius - $border-width;
|
||||
margin-right: $gap-small;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
@@ -65,10 +64,10 @@ $border-radius: 5px;
|
||||
&::after {
|
||||
border-right: $border-width solid currentColor;
|
||||
border-top: $border-width solid currentColor;
|
||||
border-radius: 0 $border-radius 0 0;
|
||||
border-radius: 0 $universal-border-radius 0 0;
|
||||
content: "";
|
||||
display: block;
|
||||
height: $border-radius - $border-width;
|
||||
height: $universal-border-radius - $border-width;
|
||||
margin-left: $gap-small;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
@@ -83,10 +82,10 @@ $border-radius: 5px;
|
||||
|
||||
.wc-block-components-express-payment__content {
|
||||
@include with-translucent-border(0 $border-width $border-width);
|
||||
padding: #{$gap-large - $border-radius} $gap-large $gap-large;
|
||||
padding: #{$gap-large - $universal-border-radius} $gap-large $gap-large;
|
||||
|
||||
&::after {
|
||||
border-radius: 0 0 $border-radius $border-radius;
|
||||
border-radius: 0 0 $universal-border-radius $universal-border-radius;
|
||||
}
|
||||
|
||||
> p {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEditorContext } from '@woocommerce/base-context';
|
||||
import { CheckboxControl } from '@woocommerce/blocks-checkout';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY, PAYMENT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
@@ -23,7 +22,14 @@ import PaymentMethodErrorBoundary from './payment-method-error-boundary';
|
||||
*
|
||||
* @return {*} The rendered component.
|
||||
*/
|
||||
const PaymentMethodCard = ( { children, showSaveOption } ) => {
|
||||
interface PaymentMethodCardProps {
|
||||
showSaveOption: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const PaymentMethodCard = ( {
|
||||
children,
|
||||
showSaveOption,
|
||||
}: PaymentMethodCardProps ) => {
|
||||
const { isEditor } = useEditorContext();
|
||||
const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
|
||||
const paymentMethodStore = select( PAYMENT_STORE_KEY );
|
||||
@@ -44,7 +50,7 @@ const PaymentMethodCard = ( { children, showSaveOption } ) => {
|
||||
className="wc-block-components-payment-methods__save-card-info"
|
||||
label={ __(
|
||||
'Save payment information to my account for future purchases.',
|
||||
'woocommerce'
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ shouldSavePaymentMethod }
|
||||
onChange={ () =>
|
||||
@@ -58,9 +64,4 @@ const PaymentMethodCard = ( { children, showSaveOption } ) => {
|
||||
);
|
||||
};
|
||||
|
||||
PaymentMethodCard.propTypes = {
|
||||
showSaveOption: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PaymentMethodCard;
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
import { noticeContexts } from '@woocommerce/base-context';
|
||||
|
||||
class PaymentMethodErrorBoundary extends Component {
|
||||
state = { errorMessage: '', hasError: false };
|
||||
|
||||
static getDerivedStateFromError( error ) {
|
||||
return {
|
||||
errorMessage: error.message,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError, errorMessage } = this.state;
|
||||
const { isEditor } = this.props;
|
||||
|
||||
if ( hasError ) {
|
||||
let errorText = __(
|
||||
'We are experiencing difficulties with this payment method. Please contact us for assistance.',
|
||||
'woocommerce'
|
||||
);
|
||||
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
|
||||
if ( errorMessage ) {
|
||||
errorText = errorMessage;
|
||||
} else {
|
||||
errorText = __(
|
||||
"There was an error with this payment method. Please verify it's configured correctly.",
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
}
|
||||
const notices = [
|
||||
{
|
||||
id: '0',
|
||||
content: errorText,
|
||||
isDismissible: false,
|
||||
status: 'error',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<StoreNoticesContainer
|
||||
additionalNotices={ notices }
|
||||
context={ noticeContexts.PAYMENTS }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
PaymentMethodErrorBoundary.propTypes = {
|
||||
isEditor: PropTypes.bool,
|
||||
};
|
||||
|
||||
PaymentMethodErrorBoundary.defaultProps = {
|
||||
isEditor: false,
|
||||
};
|
||||
|
||||
export default PaymentMethodErrorBoundary;
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
import { noticeContexts } from '@woocommerce/base-context';
|
||||
import { NoticeType } from '@woocommerce/types';
|
||||
interface PaymentMethodErrorBoundaryProps {
|
||||
isEditor: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const PaymentMethodErrorBoundary = ( {
|
||||
isEditor,
|
||||
children,
|
||||
}: PaymentMethodErrorBoundaryProps ) => {
|
||||
const [ errorMessage ] = useState( '' );
|
||||
const [ hasError ] = useState( false );
|
||||
if ( hasError ) {
|
||||
let errorText = __(
|
||||
'We are experiencing difficulties with this payment method. Please contact us for assistance.',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
|
||||
if ( errorMessage ) {
|
||||
errorText = errorMessage;
|
||||
} else {
|
||||
errorText = __(
|
||||
"There was an error with this payment method. Please verify it's configured correctly.",
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
}
|
||||
}
|
||||
const notices: NoticeType[] = [
|
||||
{
|
||||
id: '0',
|
||||
content: errorText,
|
||||
isDismissible: false,
|
||||
status: 'error',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<StoreNoticesContainer
|
||||
additionalNotices={ notices }
|
||||
context={ noticeContexts.PAYMENTS }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <>{ children }</>;
|
||||
};
|
||||
export default PaymentMethodErrorBoundary;
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { cloneElement, useCallback } from '@wordpress/element';
|
||||
import { useEditorContext } from '@woocommerce/base-context';
|
||||
import classNames from 'classnames';
|
||||
import RadioControlAccordion from '@woocommerce/base-components/radio-control-accordion';
|
||||
import { RadioControlAccordion } from '@woocommerce/blocks-components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { getPaymentMethods } from '@woocommerce/blocks-registry';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import { Label } from '@woocommerce/blocks-components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
import { useMemo, cloneElement } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { noticeContexts } from '@woocommerce/base-context';
|
||||
import RadioControl from '@woocommerce/base-components/radio-control';
|
||||
import {
|
||||
RadioControl,
|
||||
RadioControlOptionType,
|
||||
} from '@woocommerce/blocks-components';
|
||||
import {
|
||||
usePaymentMethodInterface,
|
||||
useStoreEvents,
|
||||
@@ -13,7 +16,6 @@ import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { getPaymentMethods } from '@woocommerce/blocks-registry';
|
||||
import { isNull } from '@woocommerce/types';
|
||||
import { RadioControlOption } from '@woocommerce/base-components/radio-control/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -87,7 +89,7 @@ const SavedPaymentMethodOptions = () => {
|
||||
const { removeNotice } = useDispatch( 'core/notices' );
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
|
||||
const options = useMemo< RadioControlOption[] >( () => {
|
||||
const options = useMemo< RadioControlOptionType[] >( () => {
|
||||
const types = Object.keys( savedPaymentMethods );
|
||||
|
||||
// Get individual payment methods from saved payment methods and put them into a unique array.
|
||||
@@ -145,7 +147,7 @@ const SavedPaymentMethodOptions = () => {
|
||||
} );
|
||||
return mappedOptions.filter(
|
||||
( option ) => typeof option !== 'undefined'
|
||||
) as RadioControlOption[];
|
||||
) as RadioControlOptionType[];
|
||||
}, [
|
||||
savedPaymentMethods,
|
||||
paymentMethods,
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
line-height: 1.375; // =22px when font-size is 16px.
|
||||
background-color: #fff;
|
||||
padding: em($gap-small) 0 em($gap-small) $gap;
|
||||
border-radius: 4px;
|
||||
border-radius: $universal-border-radius;
|
||||
border: 1px solid $input-border-gray;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
@@ -201,6 +201,16 @@
|
||||
@include with-translucent-border(1px 1px 0 1px);
|
||||
}
|
||||
|
||||
.wc-block-components-radio-control-accordion-option:first-child::after {
|
||||
border-top-left-radius: $universal-border-radius;
|
||||
border-top-right-radius: $universal-border-radius;
|
||||
}
|
||||
|
||||
.wc-block-components-radio-control-accordion-option:last-child::after {
|
||||
border-bottom-left-radius: $universal-border-radius;
|
||||
border-bottom-right-radius: $universal-border-radius;
|
||||
}
|
||||
|
||||
.wc-block-components-radio-control__option:last-child::after,
|
||||
.wc-block-components-radio-control-accordion-option:last-child::after {
|
||||
border-width: 1px;
|
||||
|
||||
@@ -27,19 +27,24 @@ jest.mock( '../saved-payment-method-options', () => ( { onChange } ) => {
|
||||
);
|
||||
} );
|
||||
|
||||
jest.mock(
|
||||
'@woocommerce/base-components/radio-control-accordion',
|
||||
() =>
|
||||
( { onChange } ) =>
|
||||
(
|
||||
<>
|
||||
<span>Payment method options</span>
|
||||
<button onClick={ () => onChange( 'credit-card' ) }>
|
||||
Select new payment
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
);
|
||||
jest.mock( '@woocommerce/blocks-components', () => {
|
||||
const originalModule = jest.requireActual(
|
||||
'@woocommerce/blocks-components'
|
||||
);
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
RadioControlAccordion: ( { onChange } ) => (
|
||||
<>
|
||||
<span>Payment method options</span>
|
||||
<button onClick={ () => onChange( 'credit-card' ) }>
|
||||
Select new payment
|
||||
</button>
|
||||
</>
|
||||
),
|
||||
};
|
||||
} );
|
||||
|
||||
const originalSelect = jest.requireActual( '@wordpress/data' ).select;
|
||||
const selectMock = jest
|
||||
|
||||
@@ -12,12 +12,10 @@ import { CartCheckoutSidebarCompatibilityNotice } from '@woocommerce/editor-comp
|
||||
import { NoPaymentMethodsNotice } from '@woocommerce/editor-components/no-payment-methods-notice';
|
||||
import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { DefaultNotice } from '@woocommerce/editor-components/default-notice';
|
||||
import { TemplateNotice } from '@woocommerce/editor-components/template-notice';
|
||||
import { IncompatiblePaymentGatewaysNotice } from '@woocommerce/editor-components/incompatible-payment-gateways-notice';
|
||||
import { IncompatibleExtensionsNotice } from '@woocommerce/editor-components/incompatible-extension-notice';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CartCheckoutFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
declare module '@wordpress/editor' {
|
||||
let store: StoreDescriptor;
|
||||
@@ -39,49 +37,73 @@ const withSidebarNotices = createHigherOrderComponent(
|
||||
isSelected: isBlockSelected,
|
||||
} = props;
|
||||
|
||||
const isBlockTheme = getSetting( 'isBlockTheme' );
|
||||
|
||||
const [
|
||||
isIncompatiblePaymentGatewaysNoticeDismissed,
|
||||
setIsIncompatiblePaymentGatewaysNoticeDismissed,
|
||||
isIncompatibleExtensionsNoticeDismissed,
|
||||
setIsIncompatibleExtensionsNoticeDismissed,
|
||||
] = useState( true );
|
||||
|
||||
const toggleIncompatiblePaymentGatewaysNoticeDismissedStatus = (
|
||||
const toggleIncompatibleExtensionsNoticeDismissedStatus = (
|
||||
isDismissed: boolean
|
||||
) => {
|
||||
setIsIncompatiblePaymentGatewaysNoticeDismissed( isDismissed );
|
||||
setIsIncompatibleExtensionsNoticeDismissed( isDismissed );
|
||||
};
|
||||
|
||||
const { isCart, isCheckout, isPaymentMethodsBlock, hasPaymentMethods } =
|
||||
useSelect( ( select ) => {
|
||||
const { getBlockParentsByBlockName, getBlockName } =
|
||||
select( blockEditorStore );
|
||||
const parent = getBlockParentsByBlockName( clientId, [
|
||||
'woocommerce/cart',
|
||||
'woocommerce/checkout',
|
||||
] ).map( getBlockName );
|
||||
const currentBlockName = getBlockName( clientId );
|
||||
return {
|
||||
isCart:
|
||||
parent.includes( 'woocommerce/cart' ) ||
|
||||
currentBlockName === 'woocommerce/cart',
|
||||
isCheckout:
|
||||
parent.includes( 'woocommerce/checkout' ) ||
|
||||
currentBlockName === 'woocommerce/checkout',
|
||||
isPaymentMethodsBlock:
|
||||
currentBlockName ===
|
||||
'woocommerce/checkout-payment-block',
|
||||
hasPaymentMethods:
|
||||
select(
|
||||
PAYMENT_STORE_KEY
|
||||
).paymentMethodsInitialized() &&
|
||||
Object.keys(
|
||||
select(
|
||||
PAYMENT_STORE_KEY
|
||||
).getAvailablePaymentMethods()
|
||||
).length > 0,
|
||||
};
|
||||
} );
|
||||
const {
|
||||
isCart,
|
||||
isCheckout,
|
||||
isPaymentMethodsBlock,
|
||||
hasPaymentMethods,
|
||||
parentId,
|
||||
} = useSelect( ( select ) => {
|
||||
const { getBlockParentsByBlockName, getBlockName } =
|
||||
select( blockEditorStore );
|
||||
|
||||
const parents = getBlockParentsByBlockName( clientId, [
|
||||
'woocommerce/cart',
|
||||
'woocommerce/checkout',
|
||||
] ).reduce(
|
||||
(
|
||||
accumulator: Record< string, string >,
|
||||
parentClientId: string
|
||||
) => {
|
||||
const parentName = getBlockName( parentClientId );
|
||||
accumulator[ parentName ] = parentClientId;
|
||||
return accumulator;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const currentBlockName = getBlockName( clientId );
|
||||
const parentBlockIsCart =
|
||||
Object.keys( parents ).includes( 'woocommerce/cart' );
|
||||
const parentBlockIsCheckout = Object.keys( parents ).includes(
|
||||
'woocommerce/checkout'
|
||||
);
|
||||
const currentBlockIsCart =
|
||||
currentBlockName === 'woocommerce/cart' || parentBlockIsCart;
|
||||
const currentBlockIsCheckout =
|
||||
currentBlockName === 'woocommerce/checkout' ||
|
||||
parentBlockIsCheckout;
|
||||
const targetParentBlock = currentBlockIsCart
|
||||
? 'woocommerce/cart'
|
||||
: 'woocommerce/checkout';
|
||||
|
||||
return {
|
||||
isCart: currentBlockIsCart,
|
||||
isCheckout: currentBlockIsCheckout,
|
||||
parentId:
|
||||
currentBlockName === targetParentBlock
|
||||
? clientId
|
||||
: parents[ targetParentBlock ],
|
||||
isPaymentMethodsBlock:
|
||||
currentBlockName === 'woocommerce/checkout-payment-block',
|
||||
hasPaymentMethods:
|
||||
select( PAYMENT_STORE_KEY ).paymentMethodsInitialized() &&
|
||||
Object.keys(
|
||||
select( PAYMENT_STORE_KEY ).getAvailablePaymentMethods()
|
||||
).length > 0,
|
||||
};
|
||||
} );
|
||||
|
||||
// Show sidebar notices only when a WooCommerce block is selected.
|
||||
if (
|
||||
@@ -95,28 +117,19 @@ const withSidebarNotices = createHigherOrderComponent(
|
||||
return (
|
||||
<>
|
||||
<InspectorControls>
|
||||
<IncompatiblePaymentGatewaysNotice
|
||||
<IncompatibleExtensionsNotice
|
||||
toggleDismissedStatus={
|
||||
toggleIncompatiblePaymentGatewaysNoticeDismissedStatus
|
||||
toggleIncompatibleExtensionsNoticeDismissedStatus
|
||||
}
|
||||
block={
|
||||
isCheckout
|
||||
? 'woocommerce/checkout'
|
||||
: 'woocommerce/cart'
|
||||
isCart ? 'woocommerce/cart' : 'woocommerce/checkout'
|
||||
}
|
||||
clientId={ parentId }
|
||||
/>
|
||||
|
||||
{ isBlockTheme ? (
|
||||
<TemplateNotice
|
||||
block={ isCheckout ? 'checkout' : 'cart' }
|
||||
/>
|
||||
) : (
|
||||
<DefaultNotice
|
||||
block={ isCheckout ? 'checkout' : 'cart' }
|
||||
/>
|
||||
) }
|
||||
<DefaultNotice block={ isCheckout ? 'checkout' : 'cart' } />
|
||||
|
||||
{ isIncompatiblePaymentGatewaysNoticeDismissed ? (
|
||||
{ isIncompatibleExtensionsNoticeDismissed ? (
|
||||
<CartCheckoutSidebarCompatibilityNotice
|
||||
block={ isCheckout ? 'checkout' : 'cart' }
|
||||
/>
|
||||
|
||||
@@ -46,6 +46,15 @@ export const useForcedLayout = ( {
|
||||
const { replaceInnerBlocks } = dispatch( 'core/block-editor' );
|
||||
|
||||
return registry.subscribe( () => {
|
||||
const currentBlock = registry
|
||||
.select( 'core/block-editor' )
|
||||
.getBlock( clientId );
|
||||
|
||||
// If the block is removed we shouldn't reinsert its inner blocks.
|
||||
if ( ! currentBlock ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const innerBlocks = registry
|
||||
.select( 'core/block-editor' )
|
||||
.getBlocks( clientId );
|
||||
|
||||
@@ -13,6 +13,9 @@ import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundar
|
||||
import { EditorProvider, CartProvider } from '@woocommerce/base-context';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { getQueryArg } from '@wordpress/url';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -37,7 +40,7 @@ const ALLOWED_BLOCKS = [
|
||||
'woocommerce/empty-cart-block',
|
||||
];
|
||||
|
||||
export const Edit = ( { className, attributes, setAttributes } ) => {
|
||||
export const Edit = ( { clientId, className, attributes, setAttributes } ) => {
|
||||
const { hasDarkControls, currentView, isPreview = false } = attributes;
|
||||
const defaultTemplate = [
|
||||
[ 'woocommerce/filled-cart-block', {}, [] ],
|
||||
@@ -49,6 +52,22 @@ export const Edit = ( { className, attributes, setAttributes } ) => {
|
||||
} ),
|
||||
} );
|
||||
|
||||
// This focuses on the block when a certain query param is found. This is used on the link from the task list.
|
||||
const focus = useRef( getQueryArg( window.location.href, 'focus' ) );
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
focus.current === 'cart' &&
|
||||
! select( 'core/block-editor' ).hasSelectedBlock()
|
||||
) {
|
||||
dispatch( 'core/block-editor' ).selectBlock( clientId );
|
||||
dispatch( 'core/interface' ).enableComplementaryArea(
|
||||
'core/edit-site',
|
||||
'edit-site/block-inspector'
|
||||
);
|
||||
}
|
||||
}, [ clientId ] );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InspectorControls>
|
||||
|
||||
@@ -45,6 +45,24 @@ const settings = {
|
||||
attributes: blockAttributes,
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
transforms: {
|
||||
to: [
|
||||
{
|
||||
type: 'block',
|
||||
blocks: [ 'woocommerce/classic-shortcode' ],
|
||||
transform: ( attributes ) => {
|
||||
return createBlock(
|
||||
'woocommerce/classic-shortcode',
|
||||
{
|
||||
shortcode: 'cart',
|
||||
align: attributes.align,
|
||||
},
|
||||
[]
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Migrates v1 to v2 checkout.
|
||||
deprecated: [
|
||||
{
|
||||
|
||||
@@ -42,8 +42,8 @@ export const Edit = ( { attributes, setAttributes }: Props ): JSX.Element => {
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { columns: value } )
|
||||
}
|
||||
min={ getSetting( 'min_columns', 1 ) }
|
||||
max={ getSetting( 'max_columns', 6 ) }
|
||||
min={ getSetting( 'minColumns', 1 ) }
|
||||
max={ getSetting( 'maxColumns', 6 ) }
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "woocommerce/cart-express-payment-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Express Checkout",
|
||||
"description": "Provide an express payment option for your customers.",
|
||||
"description": "Allow customers to breeze through with quick payment options.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, payment } from '@wordpress/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import expressIcon from '../../../cart-checkout-shared/icon';
|
||||
|
||||
registerBlockType( 'woocommerce/cart-express-payment-block', {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ payment }
|
||||
style={ { fill: 'none' } } // this is needed for this particular svg
|
||||
icon={ expressIcon }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
export default {
|
||||
isShippingCalculatorEnabled: {
|
||||
type: 'boolean',
|
||||
default: getSetting( 'isShippingCalculatorEnabled', true ),
|
||||
},
|
||||
lock: {
|
||||
type: 'object',
|
||||
default: {
|
||||
move: false,
|
||||
remove: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -5,14 +5,9 @@ import { TotalsShipping } from '@woocommerce/base-components/cart-checkout';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { TotalsWrapper } from '@woocommerce/blocks-checkout';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
const Block = ( {
|
||||
className,
|
||||
isShippingCalculatorEnabled,
|
||||
}: {
|
||||
className: string;
|
||||
isShippingCalculatorEnabled: boolean;
|
||||
} ): JSX.Element | null => {
|
||||
const Block = ( { className }: { className: string } ): JSX.Element | null => {
|
||||
const { cartTotals, cartNeedsShipping } = useStoreCart();
|
||||
|
||||
if ( ! cartNeedsShipping ) {
|
||||
@@ -24,7 +19,10 @@ const Block = ( {
|
||||
return (
|
||||
<TotalsWrapper className={ className }>
|
||||
<TotalsShipping
|
||||
showCalculator={ isShippingCalculatorEnabled }
|
||||
showCalculator={ getSetting< boolean >(
|
||||
'isShippingCalculatorEnabled',
|
||||
true
|
||||
) }
|
||||
showRateSelector={ true }
|
||||
values={ cartTotals }
|
||||
currency={ totalsCurrency }
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
@@ -14,19 +14,16 @@ import Block from './block';
|
||||
|
||||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
}: {
|
||||
attributes: {
|
||||
isShippingCalculatorEnabled: boolean;
|
||||
className: string;
|
||||
lock: {
|
||||
move: boolean;
|
||||
remove: boolean;
|
||||
};
|
||||
};
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element => {
|
||||
const { isShippingCalculatorEnabled, className } = attributes;
|
||||
const { className } = attributes;
|
||||
const shippingEnabled = getSetting( 'shippingEnabled', true );
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
@@ -36,35 +33,29 @@ export const Edit = ( {
|
||||
{ !! shippingEnabled && (
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Shipping rates',
|
||||
'Shipping Calculations',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Shipping calculator',
|
||||
<p className="wc-block-checkout__controls-text">
|
||||
{ __(
|
||||
'Options that control shipping can be managed in your store settings.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={ __(
|
||||
'Allow customers to estimate shipping by entering their address.',
|
||||
</p>
|
||||
<ExternalLink
|
||||
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping§ion=options` }
|
||||
>
|
||||
{ __(
|
||||
'Manage shipping options',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ isShippingCalculatorEnabled }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
isShippingCalculatorEnabled:
|
||||
! isShippingCalculatorEnabled,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</ExternalLink>{ ' ' }
|
||||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
className={ className }
|
||||
isShippingCalculatorEnabled={ isShippingCalculatorEnabled }
|
||||
/>
|
||||
<Block className={ className } />
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import attributes from './attributes';
|
||||
|
||||
export default withFilteredAttributes( attributes )( Block );
|
||||
export default Block;
|
||||
|
||||
@@ -9,7 +9,6 @@ import { registerBlockType } from '@wordpress/blocks';
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import attributes from './attributes';
|
||||
|
||||
registerBlockType( 'woocommerce/cart-order-summary-shipping-block', {
|
||||
icon: {
|
||||
@@ -20,7 +19,6 @@ registerBlockType( 'woocommerce/cart-order-summary-shipping-block', {
|
||||
/>
|
||||
),
|
||||
},
|
||||
attributes,
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
|
||||
@@ -65,7 +65,7 @@ const defaultTemplate = [
|
||||
[
|
||||
'woocommerce/product-new',
|
||||
{
|
||||
columns: 3,
|
||||
columns: 4,
|
||||
rows: 1,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -19,12 +19,15 @@ const FrontendBlock = ( {
|
||||
} ): JSX.Element | null => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
useEffect( () => {
|
||||
if ( cartItems.length !== 0 || cartIsLoading ) {
|
||||
return;
|
||||
}
|
||||
dispatchEvent( 'wc-blocks_render_blocks_frontend', {
|
||||
element: document.body.querySelector(
|
||||
'.wp-block-woocommerce-cart'
|
||||
),
|
||||
} );
|
||||
}, [] );
|
||||
}, [ cartIsLoading, cartItems ] );
|
||||
if ( ! cartIsLoading && cartItems.length === 0 ) {
|
||||
return <div className={ className }>{ children }</div>;
|
||||
}
|
||||
|
||||
@@ -32,18 +32,18 @@ import OrderSummarySubtotalBlock from '../inner-blocks/cart-order-summary-subtot
|
||||
import OrderSummaryShippingBlock from '../inner-blocks/cart-order-summary-shipping/frontend';
|
||||
import OrderSummaryTaxesBlock from '../inner-blocks/cart-order-summary-taxes/frontend';
|
||||
|
||||
jest.mock( '@wordpress/compose', () => ( {
|
||||
...jest.requireActual( '@wordpress/compose' ),
|
||||
useResizeObserver: jest.fn().mockReturnValue( [ null, { width: 0 } ] ),
|
||||
} ) );
|
||||
|
||||
const CartBlock = ( {
|
||||
attributes = {
|
||||
showRateAfterTaxName: false,
|
||||
isShippingCalculatorEnabled: false,
|
||||
checkoutPageId: 0,
|
||||
},
|
||||
} ) => {
|
||||
const {
|
||||
showRateAfterTaxName,
|
||||
isShippingCalculatorEnabled,
|
||||
checkoutPageId,
|
||||
} = attributes;
|
||||
const { showRateAfterTaxName, checkoutPageId } = attributes;
|
||||
return (
|
||||
<Cart attributes={ attributes }>
|
||||
<FilledCart>
|
||||
@@ -54,11 +54,7 @@ const CartBlock = ( {
|
||||
<OrderSummaryBlock>
|
||||
<OrderSummaryHeadingBlock />
|
||||
<OrderSummarySubtotalBlock />
|
||||
<OrderSummaryShippingBlock
|
||||
isShippingCalculatorEnabled={
|
||||
isShippingCalculatorEnabled
|
||||
}
|
||||
/>
|
||||
<OrderSummaryShippingBlock />
|
||||
<OrderSummaryTaxesBlock
|
||||
showRateAfterTaxName={ showRateAfterTaxName }
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ALLOWED_COUNTRIES } from '@woocommerce/block-settings';
|
||||
import type {
|
||||
CartShippingAddress,
|
||||
CartBillingAddress,
|
||||
} from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const AddressCard = ( {
|
||||
address,
|
||||
onEdit,
|
||||
target,
|
||||
showPhoneField,
|
||||
}: {
|
||||
address: CartShippingAddress | CartBillingAddress;
|
||||
onEdit: () => void;
|
||||
target: string;
|
||||
showPhoneField: boolean;
|
||||
} ): JSX.Element | null => {
|
||||
return (
|
||||
<div className="wc-block-components-address-card">
|
||||
<address>
|
||||
<span className="wc-block-components-address-card__address-section">
|
||||
{ address.first_name + ' ' + address.last_name }
|
||||
</span>
|
||||
<div className="wc-block-components-address-card__address-section">
|
||||
{ [
|
||||
address.address_1,
|
||||
address.address_2,
|
||||
address.city,
|
||||
address.state,
|
||||
address.postcode,
|
||||
ALLOWED_COUNTRIES[ address.country ]
|
||||
? ALLOWED_COUNTRIES[ address.country ]
|
||||
: address.country,
|
||||
]
|
||||
.filter( ( field ) => !! field )
|
||||
.map( ( field, index ) => (
|
||||
<span key={ `address-` + index }>{ field }</span>
|
||||
) ) }
|
||||
</div>
|
||||
{ address.phone && showPhoneField ? (
|
||||
<div
|
||||
key={ `address-phone` }
|
||||
className="wc-block-components-address-card__address-section"
|
||||
>
|
||||
{ address.phone }
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
) }
|
||||
</address>
|
||||
{ onEdit && (
|
||||
<a
|
||||
role="button"
|
||||
href={ '#' + target }
|
||||
className="wc-block-components-address-card__edit"
|
||||
aria-label={ __(
|
||||
'Edit address',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
onClick={ ( e ) => {
|
||||
onEdit();
|
||||
e.preventDefault();
|
||||
} }
|
||||
>
|
||||
{ __( 'Edit', 'woo-gutenberg-products-block' ) }
|
||||
</a>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressCard;
|
||||
@@ -0,0 +1,41 @@
|
||||
.wc-block-components-address-card {
|
||||
border: 1px solid $universal-border;
|
||||
@include font-size(regular);
|
||||
padding: em($gap);
|
||||
margin: 0;
|
||||
border-radius: $universal-border-radius;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
address {
|
||||
margin: 0;
|
||||
font-style: normal;
|
||||
|
||||
.wc-block-components-address-card__address-section {
|
||||
display: block;
|
||||
margin: 0 0 2px 0;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 0 4px 0 0;
|
||||
&::after {
|
||||
content: ", ";
|
||||
}
|
||||
&:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.wc-block-components-address-card__edit {
|
||||
margin: 0 0 0 auto;
|
||||
text-decoration: none;
|
||||
@include font-size(small);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Wrapper for address fields which handles the edit/preview transition. Form fields are always rendered so that
|
||||
* validation can occur.
|
||||
*/
|
||||
export const AddressWrapper = ( {
|
||||
isEditing = false,
|
||||
addressCard,
|
||||
addressForm,
|
||||
}: {
|
||||
isEditing: boolean;
|
||||
addressCard: () => JSX.Element;
|
||||
addressForm: () => JSX.Element;
|
||||
} ): JSX.Element | null => {
|
||||
const wrapperClasses = classnames(
|
||||
'wc-block-components-address-address-wrapper',
|
||||
{
|
||||
'is-editing': isEditing,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ wrapperClasses }>
|
||||
<div className="wc-block-components-address-card-wrapper">
|
||||
{ addressCard() }
|
||||
</div>
|
||||
<div className="wc-block-components-address-form-wrapper">
|
||||
{ addressForm() }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressWrapper;
|
||||
@@ -0,0 +1,32 @@
|
||||
.wc-block-components-address-address-wrapper {
|
||||
position: relative;
|
||||
|
||||
.wc-block-components-address-card-wrapper,
|
||||
.wc-block-components-address-form-wrapper {
|
||||
transition: all 300ms ease-in-out;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.is-editing {
|
||||
.wc-block-components-address-form-wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
.wc-block-components-address-card-wrapper {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-editing) {
|
||||
.wc-block-components-address-form-wrapper {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
.wc-block-components-address-card-wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
margin: 0 auto 1em;
|
||||
display: block;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
}
|
||||
.wc-block-checkout-error__title {
|
||||
display: block;
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
} from '@wordpress/components';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
import type { TemplateArray } from '@wordpress/blocks';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { getQueryArg } from '@wordpress/url';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -46,9 +49,11 @@ const ALLOWED_BLOCKS: string[] = [
|
||||
];
|
||||
|
||||
export const Edit = ( {
|
||||
clientId,
|
||||
attributes,
|
||||
setAttributes,
|
||||
}: {
|
||||
clientId: string;
|
||||
attributes: Attributes;
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => undefined;
|
||||
} ): JSX.Element => {
|
||||
@@ -66,6 +71,22 @@ export const Edit = ( {
|
||||
isPreview = false,
|
||||
} = attributes;
|
||||
|
||||
// This focuses on the block when a certain query param is found. This is used on the link from the task list.
|
||||
const focus = useRef( getQueryArg( window.location.href, 'focus' ) );
|
||||
|
||||
useEffect( () => {
|
||||
if (
|
||||
focus.current === 'checkout' &&
|
||||
! select( 'core/block-editor' ).hasSelectedBlock()
|
||||
) {
|
||||
dispatch( 'core/block-editor' ).selectBlock( clientId );
|
||||
dispatch( 'core/interface' ).enableComplementaryArea(
|
||||
'core/edit-site',
|
||||
'edit-site/block-inspector'
|
||||
);
|
||||
}
|
||||
}, [ clientId ] );
|
||||
|
||||
const defaultTemplate = [
|
||||
[ 'woocommerce/checkout-fields-block', {}, [] ],
|
||||
[ 'woocommerce/checkout-totals-block', {}, [] ],
|
||||
|
||||
@@ -31,6 +31,24 @@ const settings = {
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
transforms: {
|
||||
to: [
|
||||
{
|
||||
type: 'block',
|
||||
blocks: [ 'woocommerce/classic-shortcode' ],
|
||||
transform: ( attributes ) => {
|
||||
return createBlock(
|
||||
'woocommerce/classic-shortcode',
|
||||
{
|
||||
shortcode: 'checkout',
|
||||
align: attributes.align,
|
||||
},
|
||||
[]
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Migrates v1 to v2 checkout.
|
||||
deprecated: [
|
||||
{
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useMemo, useEffect, Fragment, useState } from '@wordpress/element';
|
||||
import { useMemo, Fragment } from '@wordpress/element';
|
||||
import { useEffectOnce } from 'usehooks-ts';
|
||||
import {
|
||||
useCheckoutAddress,
|
||||
useStoreEvents,
|
||||
useEditorContext,
|
||||
noticeContexts,
|
||||
} from '@woocommerce/base-context';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
import type {
|
||||
BillingAddress,
|
||||
ShippingAddress,
|
||||
AddressField,
|
||||
AddressFields,
|
||||
} from '@woocommerce/settings';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import PhoneNumber from '../../phone-number';
|
||||
import CustomerAddress from './customer-address';
|
||||
|
||||
const Block = ( {
|
||||
showCompanyField = false,
|
||||
@@ -28,48 +29,38 @@ const Block = ( {
|
||||
showPhoneField = false,
|
||||
requireCompanyField = false,
|
||||
requirePhoneField = false,
|
||||
forceEditing = false,
|
||||
}: {
|
||||
showCompanyField: boolean;
|
||||
showApartmentField: boolean;
|
||||
showPhoneField: boolean;
|
||||
requireCompanyField: boolean;
|
||||
requirePhoneField: boolean;
|
||||
forceEditing?: boolean;
|
||||
} ): JSX.Element => {
|
||||
const {
|
||||
defaultAddressFields,
|
||||
billingAddress,
|
||||
setBillingAddress,
|
||||
setShippingAddress,
|
||||
setBillingPhone,
|
||||
setShippingPhone,
|
||||
useBillingAsShipping,
|
||||
} = useCheckoutAddress();
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const { billingAddress, setShippingAddress, useBillingAsShipping } =
|
||||
useCheckoutAddress();
|
||||
const { isEditor } = useEditorContext();
|
||||
// Clears data if fields are hidden.
|
||||
useEffect( () => {
|
||||
if ( ! showPhoneField ) {
|
||||
setBillingPhone( '' );
|
||||
}
|
||||
}, [ showPhoneField, setBillingPhone ] );
|
||||
|
||||
const [ addressesSynced, setAddressesSynced ] = useState( false );
|
||||
|
||||
// Syncs shipping address with billing address if "Force shipping to the customer billing address" is enabled.
|
||||
useEffect( () => {
|
||||
if ( addressesSynced ) {
|
||||
return;
|
||||
}
|
||||
useEffectOnce( () => {
|
||||
if ( useBillingAsShipping ) {
|
||||
setShippingAddress( billingAddress );
|
||||
const { email, ...addressValues } = billingAddress;
|
||||
const syncValues: Partial< ShippingAddress > = {
|
||||
...addressValues,
|
||||
};
|
||||
|
||||
if ( ! showPhoneField ) {
|
||||
delete syncValues.phone;
|
||||
}
|
||||
|
||||
if ( showCompanyField ) {
|
||||
delete syncValues.company;
|
||||
}
|
||||
|
||||
setShippingAddress( syncValues );
|
||||
}
|
||||
setAddressesSynced( true );
|
||||
}, [
|
||||
addressesSynced,
|
||||
setShippingAddress,
|
||||
billingAddress,
|
||||
useBillingAsShipping,
|
||||
] );
|
||||
} );
|
||||
|
||||
const addressFieldsConfig = useMemo( () => {
|
||||
return {
|
||||
@@ -87,54 +78,31 @@ const Block = ( {
|
||||
showApartmentField,
|
||||
] ) as Record< keyof AddressFields, Partial< AddressField > >;
|
||||
|
||||
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
const WrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
const noticeContext = useBillingAsShipping
|
||||
? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ]
|
||||
: [ noticeContexts.BILLING_ADDRESS ];
|
||||
|
||||
const { cartDataLoaded } = useSelect( ( select ) => {
|
||||
const store = select( CART_STORE_KEY );
|
||||
return {
|
||||
cartDataLoaded: store.hasFinishedResolution( 'getCartData' ),
|
||||
};
|
||||
} );
|
||||
return (
|
||||
<AddressFormWrapperComponent>
|
||||
<>
|
||||
<StoreNoticesContainer context={ noticeContext } />
|
||||
<AddressForm
|
||||
id="billing"
|
||||
type="billing"
|
||||
onChange={ ( values: Partial< BillingAddress > ) => {
|
||||
setBillingAddress( values );
|
||||
if ( useBillingAsShipping ) {
|
||||
setShippingAddress( values );
|
||||
dispatchCheckoutEvent( 'set-shipping-address' );
|
||||
}
|
||||
dispatchCheckoutEvent( 'set-billing-address' );
|
||||
} }
|
||||
values={ billingAddress }
|
||||
fields={
|
||||
Object.keys(
|
||||
defaultAddressFields
|
||||
) as ( keyof AddressFields )[]
|
||||
}
|
||||
fieldConfig={ addressFieldsConfig }
|
||||
/>
|
||||
{ showPhoneField && (
|
||||
<PhoneNumber
|
||||
id={ 'billing-phone' }
|
||||
errorId={ 'billing_phone' }
|
||||
isRequired={ requirePhoneField }
|
||||
value={ billingAddress.phone }
|
||||
onChange={ ( value ) => {
|
||||
setBillingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'billing',
|
||||
} );
|
||||
if ( useBillingAsShipping ) {
|
||||
setShippingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'shipping',
|
||||
} );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</AddressFormWrapperComponent>
|
||||
<WrapperComponent>
|
||||
{ cartDataLoaded ? (
|
||||
<CustomerAddress
|
||||
addressFieldsConfig={ addressFieldsConfig }
|
||||
showPhoneField={ showPhoneField }
|
||||
requirePhoneField={ requirePhoneField }
|
||||
forceEditing={ forceEditing }
|
||||
/>
|
||||
) : null }
|
||||
</WrapperComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useCallback, useEffect } from '@wordpress/element';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context';
|
||||
import type {
|
||||
BillingAddress,
|
||||
AddressField,
|
||||
AddressFields,
|
||||
} from '@woocommerce/settings';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import AddressWrapper from '../../address-wrapper';
|
||||
import PhoneNumber from '../../phone-number';
|
||||
import AddressCard from '../../address-card';
|
||||
|
||||
const CustomerAddress = ( {
|
||||
addressFieldsConfig,
|
||||
showPhoneField,
|
||||
requirePhoneField,
|
||||
forceEditing = false,
|
||||
}: {
|
||||
addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >;
|
||||
showPhoneField: boolean;
|
||||
requirePhoneField: boolean;
|
||||
forceEditing?: boolean;
|
||||
} ) => {
|
||||
const {
|
||||
defaultAddressFields,
|
||||
billingAddress,
|
||||
setShippingAddress,
|
||||
setBillingAddress,
|
||||
setBillingPhone,
|
||||
setShippingPhone,
|
||||
useBillingAsShipping,
|
||||
} = useCheckoutAddress();
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const hasAddress = !! (
|
||||
billingAddress.address_1 &&
|
||||
( billingAddress.first_name || billingAddress.last_name )
|
||||
);
|
||||
const [ editing, setEditing ] = useState( ! hasAddress || forceEditing );
|
||||
|
||||
// Forces editing state if store has errors.
|
||||
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
|
||||
const store = select( VALIDATION_STORE_KEY );
|
||||
return {
|
||||
hasValidationErrors: store.hasValidationErrors(),
|
||||
invalidProps: Object.keys( billingAddress )
|
||||
.filter( ( key ) => {
|
||||
return (
|
||||
store.getValidationError( 'billing_' + key ) !==
|
||||
undefined
|
||||
);
|
||||
} )
|
||||
.filter( Boolean ),
|
||||
};
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
if ( invalidProps.length > 0 && editing === false ) {
|
||||
setEditing( true );
|
||||
}
|
||||
}, [ editing, hasValidationErrors, invalidProps.length ] );
|
||||
|
||||
const addressFieldKeys = Object.keys(
|
||||
defaultAddressFields
|
||||
) as ( keyof AddressFields )[];
|
||||
|
||||
const onChangeAddress = useCallback(
|
||||
( values: Partial< BillingAddress > ) => {
|
||||
setBillingAddress( values );
|
||||
if ( useBillingAsShipping ) {
|
||||
setShippingAddress( values );
|
||||
dispatchCheckoutEvent( 'set-shipping-address' );
|
||||
}
|
||||
dispatchCheckoutEvent( 'set-billing-address' );
|
||||
},
|
||||
[
|
||||
dispatchCheckoutEvent,
|
||||
setBillingAddress,
|
||||
setShippingAddress,
|
||||
useBillingAsShipping,
|
||||
]
|
||||
);
|
||||
|
||||
const renderAddressCardComponent = useCallback(
|
||||
() => (
|
||||
<AddressCard
|
||||
address={ billingAddress }
|
||||
target="billing"
|
||||
onEdit={ () => {
|
||||
setEditing( true );
|
||||
} }
|
||||
showPhoneField={ showPhoneField }
|
||||
/>
|
||||
),
|
||||
[ billingAddress, showPhoneField ]
|
||||
);
|
||||
|
||||
const renderAddressFormComponent = useCallback(
|
||||
() => (
|
||||
<>
|
||||
<AddressForm
|
||||
id="billing"
|
||||
type="billing"
|
||||
onChange={ onChangeAddress }
|
||||
values={ billingAddress }
|
||||
fields={ addressFieldKeys }
|
||||
fieldConfig={ addressFieldsConfig }
|
||||
/>
|
||||
{ showPhoneField && (
|
||||
<PhoneNumber
|
||||
id="billing-phone"
|
||||
errorId={ 'billing_phone' }
|
||||
isRequired={ requirePhoneField }
|
||||
value={ billingAddress.phone }
|
||||
onChange={ ( value ) => {
|
||||
setBillingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'billing',
|
||||
} );
|
||||
if ( useBillingAsShipping ) {
|
||||
setShippingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'billing',
|
||||
} );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
),
|
||||
[
|
||||
addressFieldKeys,
|
||||
addressFieldsConfig,
|
||||
billingAddress,
|
||||
dispatchCheckoutEvent,
|
||||
onChangeAddress,
|
||||
requirePhoneField,
|
||||
setBillingPhone,
|
||||
setShippingPhone,
|
||||
showPhoneField,
|
||||
useBillingAsShipping,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AddressWrapper
|
||||
isEditing={ editing }
|
||||
addressCard={ renderAddressCardComponent }
|
||||
addressForm={ renderAddressFormComponent }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerAddress;
|
||||
@@ -2,8 +2,9 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useRef, useEffect } from '@wordpress/element';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
@@ -42,12 +43,26 @@ const FrontendBlock = ( {
|
||||
showCompanyField,
|
||||
showPhoneField,
|
||||
} = useCheckoutBlockContext();
|
||||
const { showBillingFields, forcedBillingAddress, useBillingAsShipping } =
|
||||
useCheckoutAddress();
|
||||
const {
|
||||
showBillingFields,
|
||||
forcedBillingAddress,
|
||||
useBillingAsShipping,
|
||||
useShippingAsBilling,
|
||||
} = useCheckoutAddress();
|
||||
|
||||
// If initial state was true, force editing to true so address fields are visible if the useShippingAsBilling option is unchecked.
|
||||
const toggledUseShippingAsBilling = useRef( useShippingAsBilling );
|
||||
|
||||
useEffect( () => {
|
||||
if ( useShippingAsBilling ) {
|
||||
toggledUseShippingAsBilling.current = true;
|
||||
}
|
||||
}, [ useShippingAsBilling ] );
|
||||
|
||||
if ( ! showBillingFields && ! useBillingAsShipping ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
title = getBillingAddresssBlockTitle( title, forcedBillingAddress );
|
||||
description = getBillingAddresssBlockDescription(
|
||||
description,
|
||||
@@ -71,6 +86,7 @@ const FrontendBlock = ( {
|
||||
showCompanyField={ showCompanyField }
|
||||
showPhoneField={ showPhoneField }
|
||||
requirePhoneField={ requirePhoneField }
|
||||
forceEditing={ toggledUseShippingAsBilling.current }
|
||||
/>
|
||||
{ children }
|
||||
</FormStep>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const Edit = ( {
|
||||
>
|
||||
<p className="wc-block-checkout__controls-text">
|
||||
{ __(
|
||||
'Account creation and guest checkout settings can be managed in the WooCommerce settings.',
|
||||
'Account creation and guest checkout settings can be managed in your store settings.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</p>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "woocommerce/checkout-express-payment-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Express Checkout",
|
||||
"description": "Provide an express payment option for your customers.",
|
||||
"description": "Allow customers to breeze through with quick payment options.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, payment } from '@wordpress/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import expressIcon from '../../../cart-checkout-shared/icon';
|
||||
import { Edit, Save } from './edit';
|
||||
|
||||
registerBlockType( 'woocommerce/checkout-express-payment-block', {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ payment }
|
||||
style={ { fill: 'none' } } // this is needed for this particular svg
|
||||
icon={ expressIcon }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -15,23 +15,15 @@
|
||||
.wc-block-checkout__shipping-fields,
|
||||
.wc-block-checkout__billing-fields {
|
||||
.wc-block-components-address-form {
|
||||
margin-left: #{-$gap-small * 0.5};
|
||||
margin-right: #{-$gap-small * 0.5};
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.wc-block-components-text-input,
|
||||
.wc-block-components-country-input,
|
||||
.wc-block-components-state-input {
|
||||
float: left;
|
||||
margin-left: #{$gap-small * 0.5};
|
||||
margin-right: #{$gap-small * 0.5};
|
||||
position: relative;
|
||||
width: calc(50% - #{$gap-small});
|
||||
flex: 0 0 calc(50% - #{$gap-smaller});
|
||||
box-sizing: border-box;
|
||||
|
||||
&:nth-of-type(2),
|
||||
&:first-of-type {
|
||||
@@ -42,11 +34,7 @@
|
||||
.wc-block-components-address-form__company,
|
||||
.wc-block-components-address-form__address_1,
|
||||
.wc-block-components-address-form__address_2 {
|
||||
width: calc(100% - #{$gap-small});
|
||||
}
|
||||
|
||||
.wc-block-components-checkbox {
|
||||
clear: both;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useShippingData } from '@woocommerce/base-context/hooks';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
|
||||
.wc-block-checkout__add-note .wc-block-components-textarea {
|
||||
margin-top: $gap;
|
||||
|
||||
&:focus {
|
||||
background-color: #fff;
|
||||
color: $input-text-active;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 1px $input-border-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-form .wc-block-checkout__order-notes.wc-block-components-checkout-step {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import classnames from 'classnames';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
|
||||
@@ -10,11 +10,13 @@ import {
|
||||
} from '@wordpress/element';
|
||||
import { useShippingData, useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
||||
import {
|
||||
FormattedMonetaryAmount,
|
||||
RadioControlOptionType,
|
||||
} from '@woocommerce/blocks-components';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { Icon, mapMarker } from '@wordpress/icons';
|
||||
import type { RadioControlOption } from '@woocommerce/base-components/radio-control/types';
|
||||
import { CartShippingPackageShippingRate } from '@woocommerce/types';
|
||||
import {
|
||||
isPackageRateCollectable,
|
||||
@@ -67,7 +69,7 @@ const getPickupDetails = (
|
||||
const renderPickupLocation = (
|
||||
option: CartShippingPackageShippingRate,
|
||||
packageCount: number
|
||||
): RadioControlOption => {
|
||||
): RadioControlOptionType => {
|
||||
const priceWithTaxes = getSetting( 'displayCartPricesIncludingTax', false )
|
||||
? parseInt( option.price, 10 ) + parseInt( option.taxes, 10 )
|
||||
: option.price;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { LOCAL_PICKUP_ENABLED } from '@woocommerce/block-settings';
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: $gray-100;
|
||||
border-radius: 4px;
|
||||
border-radius: $universal-border-radius;
|
||||
padding: 1px em($gap-small);
|
||||
margin-top: em($gap-smaller);
|
||||
@include font-size(regular);
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useMemo, useEffect, Fragment, useState } from '@wordpress/element';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import { useMemo, Fragment } from '@wordpress/element';
|
||||
import { useEffectOnce } from 'usehooks-ts';
|
||||
import {
|
||||
useCheckoutAddress,
|
||||
useStoreEvents,
|
||||
useEditorContext,
|
||||
noticeContexts,
|
||||
} from '@woocommerce/base-context';
|
||||
@@ -17,15 +16,16 @@ import {
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
import type {
|
||||
BillingAddress,
|
||||
ShippingAddress,
|
||||
AddressField,
|
||||
AddressFields,
|
||||
} from '@woocommerce/settings';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CART_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import PhoneNumber from '../../phone-number';
|
||||
import CustomerAddress from './customer-address';
|
||||
|
||||
const Block = ( {
|
||||
showCompanyField = false,
|
||||
@@ -41,52 +41,38 @@ const Block = ( {
|
||||
requirePhoneField: boolean;
|
||||
} ): JSX.Element => {
|
||||
const {
|
||||
defaultAddressFields,
|
||||
setShippingAddress,
|
||||
setBillingAddress,
|
||||
shippingAddress,
|
||||
billingAddress,
|
||||
setShippingPhone,
|
||||
useShippingAsBilling,
|
||||
setUseShippingAsBilling,
|
||||
} = useCheckoutAddress();
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const { isEditor } = useEditorContext();
|
||||
|
||||
const { email } = billingAddress;
|
||||
// This is used to track whether the "Use shipping as billing" checkbox was checked on first load and if we synced
|
||||
// the shipping address to the billing address if it was. This is not used on further toggles of the checkbox.
|
||||
const [ addressesSynced, setAddressesSynced ] = useState( false );
|
||||
// Syncs the billing address with the shipping address.
|
||||
const syncBillingWithShipping = () => {
|
||||
const syncValues: Partial< BillingAddress > = {
|
||||
...shippingAddress,
|
||||
};
|
||||
|
||||
// Clears data if fields are hidden.
|
||||
useEffect( () => {
|
||||
if ( ! showPhoneField ) {
|
||||
setShippingPhone( '' );
|
||||
delete syncValues.phone;
|
||||
}
|
||||
}, [ showPhoneField, setShippingPhone ] );
|
||||
|
||||
// Run this on first render to ensure addresses sync if needed, there is no need to re-run this when toggling the
|
||||
// checkbox.
|
||||
useEffect(
|
||||
() => {
|
||||
if ( addressesSynced ) {
|
||||
return;
|
||||
}
|
||||
if ( useShippingAsBilling ) {
|
||||
setBillingAddress( { ...shippingAddress, email } );
|
||||
}
|
||||
setAddressesSynced( true );
|
||||
},
|
||||
// Skip the `email` dependency since we don't want to re-run if that changes, but we do want to sync it on first render.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
addressesSynced,
|
||||
setBillingAddress,
|
||||
shippingAddress,
|
||||
useShippingAsBilling,
|
||||
]
|
||||
);
|
||||
if ( showCompanyField ) {
|
||||
delete syncValues.company;
|
||||
}
|
||||
|
||||
setBillingAddress( syncValues );
|
||||
};
|
||||
|
||||
// Run this on first render to ensure addresses sync if needed (this is not re-ran when toggling the checkbox).
|
||||
useEffectOnce( () => {
|
||||
if ( useShippingAsBilling ) {
|
||||
syncBillingWithShipping();
|
||||
}
|
||||
} );
|
||||
|
||||
// Create address fields config from block attributes.
|
||||
const addressFieldsConfig = useMemo( () => {
|
||||
return {
|
||||
company: {
|
||||
@@ -103,63 +89,50 @@ const Block = ( {
|
||||
showApartmentField,
|
||||
] ) as Record< keyof AddressFields, Partial< AddressField > >;
|
||||
|
||||
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
const WrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
const noticeContext = useShippingAsBilling
|
||||
? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ]
|
||||
: [ noticeContexts.SHIPPING_ADDRESS ];
|
||||
const hasAddress = !! (
|
||||
shippingAddress.address_1 &&
|
||||
( shippingAddress.first_name || shippingAddress.last_name )
|
||||
);
|
||||
|
||||
const { cartDataLoaded } = useSelect( ( select ) => {
|
||||
const store = select( CART_STORE_KEY );
|
||||
return {
|
||||
cartDataLoaded: store.hasFinishedResolution( 'getCartData' ),
|
||||
};
|
||||
} );
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddressFormWrapperComponent>
|
||||
<StoreNoticesContainer context={ noticeContext } />
|
||||
<AddressForm
|
||||
id="shipping"
|
||||
type="shipping"
|
||||
onChange={ ( values: Partial< ShippingAddress > ) => {
|
||||
setShippingAddress( values );
|
||||
if ( useShippingAsBilling ) {
|
||||
setBillingAddress( { ...values, email } );
|
||||
dispatchCheckoutEvent( 'set-billing-address' );
|
||||
}
|
||||
dispatchCheckoutEvent( 'set-shipping-address' );
|
||||
} }
|
||||
values={ shippingAddress }
|
||||
fields={
|
||||
Object.keys(
|
||||
defaultAddressFields
|
||||
) as ( keyof AddressFields )[]
|
||||
}
|
||||
fieldConfig={ addressFieldsConfig }
|
||||
/>
|
||||
{ showPhoneField && (
|
||||
<PhoneNumber
|
||||
id="shipping-phone"
|
||||
errorId={ 'shipping_phone' }
|
||||
isRequired={ requirePhoneField }
|
||||
value={ shippingAddress.phone }
|
||||
onChange={ ( value ) => {
|
||||
setShippingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'shipping',
|
||||
} );
|
||||
} }
|
||||
<StoreNoticesContainer context={ noticeContext } />
|
||||
<WrapperComponent>
|
||||
{ cartDataLoaded ? (
|
||||
<CustomerAddress
|
||||
addressFieldsConfig={ addressFieldsConfig }
|
||||
showPhoneField={ showPhoneField }
|
||||
requirePhoneField={ requirePhoneField }
|
||||
/>
|
||||
) }
|
||||
</AddressFormWrapperComponent>
|
||||
<CheckboxControl
|
||||
className="wc-block-checkout__use-address-for-billing"
|
||||
label={ __(
|
||||
'Use same address for billing',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ useShippingAsBilling }
|
||||
onChange={ ( checked: boolean ) => {
|
||||
setUseShippingAsBilling( checked );
|
||||
if ( checked ) {
|
||||
setBillingAddress( shippingAddress as BillingAddress );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) : null }
|
||||
</WrapperComponent>
|
||||
{ hasAddress && (
|
||||
<CheckboxControl
|
||||
className="wc-block-checkout__use-address-for-billing"
|
||||
label={ __(
|
||||
'Use same address for billing',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ useShippingAsBilling }
|
||||
onChange={ ( checked: boolean ) => {
|
||||
setUseShippingAsBilling( checked );
|
||||
if ( checked ) {
|
||||
syncBillingWithShipping();
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useCallback, useEffect } from '@wordpress/element';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import {
|
||||
useCheckoutAddress,
|
||||
useStoreEvents,
|
||||
useEditorContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import type {
|
||||
ShippingAddress,
|
||||
AddressField,
|
||||
AddressFields,
|
||||
} from '@woocommerce/settings';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import AddressWrapper from '../../address-wrapper';
|
||||
import PhoneNumber from '../../phone-number';
|
||||
import AddressCard from '../../address-card';
|
||||
|
||||
const CustomerAddress = ( {
|
||||
addressFieldsConfig,
|
||||
showPhoneField,
|
||||
requirePhoneField,
|
||||
}: {
|
||||
addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >;
|
||||
showPhoneField: boolean;
|
||||
requirePhoneField: boolean;
|
||||
} ) => {
|
||||
const {
|
||||
defaultAddressFields,
|
||||
shippingAddress,
|
||||
setShippingAddress,
|
||||
setBillingAddress,
|
||||
setShippingPhone,
|
||||
setBillingPhone,
|
||||
useShippingAsBilling,
|
||||
} = useCheckoutAddress();
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const { isEditor } = useEditorContext();
|
||||
const hasAddress = !! (
|
||||
shippingAddress.address_1 &&
|
||||
( shippingAddress.first_name || shippingAddress.last_name )
|
||||
);
|
||||
const [ editing, setEditing ] = useState( ! hasAddress || isEditor );
|
||||
|
||||
// Forces editing state if store has errors.
|
||||
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
|
||||
const store = select( VALIDATION_STORE_KEY );
|
||||
return {
|
||||
hasValidationErrors: store.hasValidationErrors(),
|
||||
invalidProps: Object.keys( shippingAddress )
|
||||
.filter( ( key ) => {
|
||||
return (
|
||||
store.getValidationError( 'shipping_' + key ) !==
|
||||
undefined
|
||||
);
|
||||
} )
|
||||
.filter( Boolean ),
|
||||
};
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
if ( invalidProps.length > 0 && editing === false ) {
|
||||
setEditing( true );
|
||||
}
|
||||
}, [ editing, hasValidationErrors, invalidProps.length ] );
|
||||
|
||||
const addressFieldKeys = Object.keys(
|
||||
defaultAddressFields
|
||||
) as ( keyof AddressFields )[];
|
||||
|
||||
const onChangeAddress = useCallback(
|
||||
( values: Partial< ShippingAddress > ) => {
|
||||
setShippingAddress( values );
|
||||
if ( useShippingAsBilling ) {
|
||||
// Sync billing with shipping. Ensure unwanted properties are omitted.
|
||||
const { ...syncBilling } = values;
|
||||
|
||||
if ( ! showPhoneField ) {
|
||||
delete syncBilling.phone;
|
||||
}
|
||||
|
||||
setBillingAddress( syncBilling );
|
||||
dispatchCheckoutEvent( 'set-billing-address' );
|
||||
}
|
||||
dispatchCheckoutEvent( 'set-shipping-address' );
|
||||
},
|
||||
[
|
||||
dispatchCheckoutEvent,
|
||||
setBillingAddress,
|
||||
setShippingAddress,
|
||||
useShippingAsBilling,
|
||||
showPhoneField,
|
||||
]
|
||||
);
|
||||
|
||||
const renderAddressCardComponent = useCallback(
|
||||
() => (
|
||||
<AddressCard
|
||||
address={ shippingAddress }
|
||||
target="shipping"
|
||||
onEdit={ () => {
|
||||
setEditing( true );
|
||||
} }
|
||||
showPhoneField={ showPhoneField }
|
||||
/>
|
||||
),
|
||||
[ shippingAddress, showPhoneField ]
|
||||
);
|
||||
|
||||
const renderAddressFormComponent = useCallback(
|
||||
() => (
|
||||
<>
|
||||
<AddressForm
|
||||
id="shipping"
|
||||
type="shipping"
|
||||
onChange={ onChangeAddress }
|
||||
values={ shippingAddress }
|
||||
fields={ addressFieldKeys }
|
||||
fieldConfig={ addressFieldsConfig }
|
||||
/>
|
||||
{ showPhoneField && (
|
||||
<PhoneNumber
|
||||
id="shipping-phone"
|
||||
errorId={ 'shipping_phone' }
|
||||
isRequired={ requirePhoneField }
|
||||
value={ shippingAddress.phone }
|
||||
onChange={ ( value ) => {
|
||||
setShippingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'shipping',
|
||||
} );
|
||||
if ( useShippingAsBilling ) {
|
||||
setBillingPhone( value );
|
||||
dispatchCheckoutEvent( 'set-phone-number', {
|
||||
step: 'billing',
|
||||
} );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
),
|
||||
[
|
||||
addressFieldKeys,
|
||||
addressFieldsConfig,
|
||||
dispatchCheckoutEvent,
|
||||
onChangeAddress,
|
||||
requirePhoneField,
|
||||
setBillingPhone,
|
||||
setShippingPhone,
|
||||
shippingAddress,
|
||||
showPhoneField,
|
||||
useShippingAsBilling,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AddressWrapper
|
||||
isEditing={ editing }
|
||||
addressCard={ renderAddressCardComponent }
|
||||
addressForm={ renderAddressFormComponent }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerAddress;
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
@@ -44,8 +44,4 @@ export default {
|
||||
remove: true,
|
||||
},
|
||||
},
|
||||
shippingCostRequiresAddress: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"shippingCostRequiresAddress": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/checkout-fields-block" ],
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useEffect } from '@wordpress/element';
|
||||
import { CART_STORE_KEY, VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { isPackageRateCollectable } from '@woocommerce/base-utils';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -164,17 +165,19 @@ const Block = ( {
|
||||
showIcon,
|
||||
localPickupText,
|
||||
shippingText,
|
||||
shippingCostRequiresAddress = false,
|
||||
}: {
|
||||
checked: string;
|
||||
onChange: ( value: string ) => void;
|
||||
showPrice: boolean;
|
||||
showIcon: boolean;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
localPickupText: string;
|
||||
shippingText: string;
|
||||
} ): JSX.Element | null => {
|
||||
const { shippingRates } = useShippingData();
|
||||
const shippingCostRequiresAddress = getSetting< boolean >(
|
||||
'shippingCostRequiresAddress',
|
||||
false
|
||||
);
|
||||
|
||||
return (
|
||||
<RadioGroup
|
||||
|
||||
@@ -23,8 +23,6 @@ import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { Attributes } from '@woocommerce/blocks/checkout/types';
|
||||
import { updateAttributeInSiblingBlock } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -154,9 +152,7 @@ const ShippingSelector = ( {
|
||||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
clientId,
|
||||
}: {
|
||||
clientId: string;
|
||||
attributes: {
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -167,16 +163,9 @@ export const Edit = ( {
|
||||
showPrice: boolean;
|
||||
showIcon: boolean;
|
||||
className: string;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
};
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element | null => {
|
||||
const toggleAttribute = ( key: keyof Attributes ): void => {
|
||||
const newAttributes = {} as Partial< Attributes >;
|
||||
newAttributes[ key ] = ! ( attributes[ key ] as boolean );
|
||||
setAttributes( newAttributes );
|
||||
};
|
||||
|
||||
const { setPrefersCollection } = useDispatch( CHECKOUT_STORE_KEY );
|
||||
const { prefersCollection } = useSelect( ( select ) => {
|
||||
const checkoutStore = select( CHECKOUT_STORE_KEY );
|
||||
@@ -221,30 +210,6 @@ export const Edit = ( {
|
||||
) }
|
||||
>
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Calculations',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Hide shipping costs until an address is entered',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ attributes.shippingCostRequiresAddress }
|
||||
onChange={ ( selected ) => {
|
||||
updateAttributeInSiblingBlock(
|
||||
clientId,
|
||||
'shippingCostRequiresAddress',
|
||||
selected,
|
||||
'woocommerce/checkout-shipping-methods-block'
|
||||
);
|
||||
|
||||
toggleAttribute( 'shippingCostRequiresAddress' );
|
||||
} }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __( 'Appearance', 'woo-gutenberg-products-block' ) }
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useShippingData } from '@woocommerce/base-context/hooks';
|
||||
@@ -25,12 +25,10 @@ const FrontendBlock = ( {
|
||||
showIcon,
|
||||
shippingText,
|
||||
localPickupText,
|
||||
shippingCostRequiresAddress,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
showStepNumber: boolean;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
children: JSX.Element;
|
||||
className?: string;
|
||||
showPrice: boolean;
|
||||
@@ -92,7 +90,6 @@ const FrontendBlock = ( {
|
||||
showIcon={ showIcon }
|
||||
localPickupText={ localPickupText }
|
||||
shippingText={ shippingText }
|
||||
shippingCostRequiresAddress={ shippingCostRequiresAddress }
|
||||
/>
|
||||
{ children }
|
||||
</FormStep>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
||||
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
|
||||
import type { CartShippingPackageShippingRate } from '@woocommerce/type-defs/cart';
|
||||
|
||||
export const RatePrice = ( {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
border: none;
|
||||
box-shadow: none !important;
|
||||
outline: 1px solid currentColor;
|
||||
border-radius: 0 !important;
|
||||
border-radius: $universal-border-radius;
|
||||
&.components-button:hover:not(:disabled),
|
||||
&.components-button:focus:not(:disabled),
|
||||
&:focus,
|
||||
|
||||
@@ -24,8 +24,4 @@ export default {
|
||||
remove: true,
|
||||
},
|
||||
},
|
||||
shippingCostRequiresAddress: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"shippingCostRequiresAddress": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/checkout-fields-block" ],
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
isAddressComplete,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
||||
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
|
||||
import { useEditorContext, noticeContexts } from '@woocommerce/base-context';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, ExternalLink, ToggleControl } from '@wordpress/components';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
import { Attributes } from '@woocommerce/blocks/checkout/types';
|
||||
import { updateAttributeInSiblingBlock } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -34,15 +32,12 @@ type shippingAdminLink = {
|
||||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
clientId,
|
||||
}: {
|
||||
clientId: string;
|
||||
attributes: {
|
||||
title: string;
|
||||
description: string;
|
||||
showStepNumber: boolean;
|
||||
className: string;
|
||||
shippingCostRequiresAddress: boolean;
|
||||
};
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element | null => {
|
||||
@@ -59,12 +54,6 @@ export const Edit = ( {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleAttribute = ( key: keyof Attributes ): void => {
|
||||
const newAttributes = {} as Partial< Attributes >;
|
||||
newAttributes[ key ] = ! ( attributes[ key ] as boolean );
|
||||
setAttributes( newAttributes );
|
||||
};
|
||||
|
||||
return (
|
||||
<FormStepBlock
|
||||
attributes={ attributes }
|
||||
@@ -77,26 +66,24 @@ export const Edit = ( {
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Calculations',
|
||||
'Shipping Calculations',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Hide shipping costs until an address is entered',
|
||||
<p className="wc-block-checkout__controls-text">
|
||||
{ __(
|
||||
'Options that control shipping can be managed in your store settings.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ attributes.shippingCostRequiresAddress }
|
||||
onChange={ ( selected ) => {
|
||||
updateAttributeInSiblingBlock(
|
||||
clientId,
|
||||
'shippingCostRequiresAddress',
|
||||
selected,
|
||||
'woocommerce/checkout-shipping-method-block'
|
||||
);
|
||||
toggleAttribute( 'shippingCostRequiresAddress' );
|
||||
} }
|
||||
/>
|
||||
</p>
|
||||
<ExternalLink
|
||||
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping§ion=options` }
|
||||
>
|
||||
{ __(
|
||||
'Manage shipping options',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</ExternalLink>{ ' ' }
|
||||
</PanelBody>
|
||||
{ globalShippingMethods.length > 0 && (
|
||||
<PanelBody
|
||||
@@ -133,11 +120,14 @@ export const Edit = ( {
|
||||
) }
|
||||
{ activeShippingZones.length && (
|
||||
<PanelBody
|
||||
title={ __( 'Zones', 'woo-gutenberg-products-block' ) }
|
||||
title={ __(
|
||||
'Shipping Zones',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<p className="wc-block-checkout__controls-text">
|
||||
{ __(
|
||||
'You currently have the following shipping zones active.',
|
||||
'Shipping Zones can be made managed in your store settings.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</p>
|
||||
@@ -151,24 +141,11 @@ export const Edit = ( {
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
<ExternalLink
|
||||
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping` }
|
||||
>
|
||||
{ __(
|
||||
'Manage shipping zones',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</ExternalLink>
|
||||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
noShippingPlaceholder={ <NoShippingPlaceholder /> }
|
||||
shippingCostRequiresAddress={
|
||||
attributes.shippingCostRequiresAddress
|
||||
}
|
||||
/>
|
||||
<Block noShippingPlaceholder={ <NoShippingPlaceholder /> } />
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.SHIPPING_METHODS } />
|
||||
</FormStepBlock>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { FormStep } from '@woocommerce/blocks-components';
|
||||
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
@@ -7,7 +7,31 @@ import { render, queryByText } from '@testing-library/react';
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit } from '../edit';
|
||||
const blockSettingsMock = jest.requireMock( '@woocommerce/block-settings' );
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...jest.requireActual( '@wordpress/data' ),
|
||||
useSelect: jest.fn().mockImplementation( ( fn ) => {
|
||||
const select = () => {
|
||||
return {
|
||||
getSelectionStart: () => ( {
|
||||
clientId: null,
|
||||
} ),
|
||||
getSelectionEnd: () => ( {
|
||||
clientId: null,
|
||||
} ),
|
||||
getFormatTypes: () => [],
|
||||
};
|
||||
};
|
||||
|
||||
if ( typeof fn === 'function' ) {
|
||||
return fn( select );
|
||||
}
|
||||
|
||||
return {
|
||||
isCaretWithinFormattedText: () => false,
|
||||
};
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
jest.mock( '@wordpress/block-editor', () => ( {
|
||||
...jest.requireActual( '@wordpress/block-editor' ),
|
||||
@@ -21,6 +45,8 @@ jest.mock( '@woocommerce/block-settings', () => ( {
|
||||
TERMS_URL: '/terms-and-conditions',
|
||||
} ) );
|
||||
|
||||
const blockSettingsMock = jest.requireMock( '@woocommerce/block-settings' );
|
||||
|
||||
describe( 'Edit', () => {
|
||||
it( 'Renders a checkbox if the checkbox attribute is true', async () => {
|
||||
const { container } = render(
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "woocommerce/classic-shortcode",
|
||||
"version": "1.0.0",
|
||||
"title": "Classic Shortcode",
|
||||
"description": "Renders classic WooCommerce shortcodes.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce" ],
|
||||
"supports": {
|
||||
"align": true,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": true
|
||||
},
|
||||
"attributes": {
|
||||
"shortcode": {
|
||||
"type": "string",
|
||||
"default": "cart",
|
||||
"enum": [ "cart", "checkout" ]
|
||||
},
|
||||
"align": {
|
||||
"type": "string",
|
||||
"default": "wide"
|
||||
}
|
||||
},
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createBlock, type BlockInstance } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { OnClickCallbackParameter, InheritedAttributes } from './types';
|
||||
|
||||
const isConversionPossible = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonLabel = () =>
|
||||
__( 'Transform into blocks', 'woo-gutenberg-products-block' );
|
||||
|
||||
const getBlockifiedTemplate = ( inheritedAttributes: InheritedAttributes ) =>
|
||||
[
|
||||
createBlock( 'woocommerce/cart', {
|
||||
...inheritedAttributes,
|
||||
className: 'wc-block-cart',
|
||||
} ),
|
||||
].filter( Boolean ) as BlockInstance[];
|
||||
|
||||
const onClickCallback = ( {
|
||||
clientId,
|
||||
attributes,
|
||||
getBlocks,
|
||||
replaceBlock,
|
||||
selectBlock,
|
||||
}: OnClickCallbackParameter ) => {
|
||||
replaceBlock( clientId, getBlockifiedTemplate( attributes ) );
|
||||
|
||||
const blocks = getBlocks();
|
||||
|
||||
const groupBlock = blocks.find(
|
||||
( block ) =>
|
||||
block.name === 'core/group' &&
|
||||
block.innerBlocks.some(
|
||||
( innerBlock ) =>
|
||||
innerBlock.name === 'woocommerce/store-notices'
|
||||
)
|
||||
);
|
||||
|
||||
if ( groupBlock ) {
|
||||
selectBlock( groupBlock.clientId );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Title shown within the block itself.
|
||||
*/
|
||||
const getTitle = () => {
|
||||
return __( 'Classic Cart', 'woo-gutenberg-products-block' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Description shown within the block itself.
|
||||
*/
|
||||
const getDescription = () => {
|
||||
return __(
|
||||
'This block will render the classic cart shortcode. You can optionally transform it into blocks for more control over the cart experience.',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
};
|
||||
|
||||
const blockifyConfig = {
|
||||
getButtonLabel,
|
||||
onClickCallback,
|
||||
getBlockifiedTemplate,
|
||||
};
|
||||
|
||||
export { blockifyConfig, isConversionPossible, getDescription, getTitle };
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createBlock, type BlockInstance } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { OnClickCallbackParameter, InheritedAttributes } from './types';
|
||||
|
||||
const isConversionPossible = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonLabel = () =>
|
||||
__( 'Transform into blocks', 'woo-gutenberg-products-block' );
|
||||
|
||||
const getBlockifiedTemplate = ( inheritedAttributes: InheritedAttributes ) =>
|
||||
[
|
||||
createBlock( 'woocommerce/checkout', {
|
||||
...inheritedAttributes,
|
||||
className: 'wc-block-checkout',
|
||||
} ),
|
||||
].filter( Boolean ) as BlockInstance[];
|
||||
|
||||
const onClickCallback = ( {
|
||||
clientId,
|
||||
attributes,
|
||||
getBlocks,
|
||||
replaceBlock,
|
||||
selectBlock,
|
||||
}: OnClickCallbackParameter ) => {
|
||||
replaceBlock( clientId, getBlockifiedTemplate( attributes ) );
|
||||
|
||||
const blocks = getBlocks();
|
||||
|
||||
const groupBlock = blocks.find(
|
||||
( block ) =>
|
||||
block.name === 'core/group' &&
|
||||
block.innerBlocks.some(
|
||||
( innerBlock ) =>
|
||||
innerBlock.name === 'woocommerce/store-notices'
|
||||
)
|
||||
);
|
||||
|
||||
if ( groupBlock ) {
|
||||
selectBlock( groupBlock.clientId );
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
return __( 'Classic Checkout', 'woo-gutenberg-products-block' );
|
||||
};
|
||||
|
||||
const getDescription = () => {
|
||||
return __(
|
||||
'This block will render the classic checkout shortcode. You can optionally transform it into blocks for more control over the checkout experience.',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
};
|
||||
|
||||
const blockifyConfig = {
|
||||
getButtonLabel,
|
||||
onClickCallback,
|
||||
getBlockifiedTemplate,
|
||||
};
|
||||
|
||||
export { blockifyConfig, isConversionPossible, getDescription, getTitle };
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TemplateDetails } from './types';
|
||||
|
||||
export const TYPES = {
|
||||
cart: 'cart',
|
||||
checkout: 'checkout',
|
||||
};
|
||||
export const PLACEHOLDERS = {
|
||||
cart: 'cart',
|
||||
checkout: 'checkout',
|
||||
};
|
||||
|
||||
export const TEMPLATES: TemplateDetails = {
|
||||
cart: {
|
||||
type: TYPES.cart,
|
||||
// Title shows up in the list view in the site editor.
|
||||
title: __( 'Cart Shortcode', 'woo-gutenberg-products-block' ),
|
||||
// Description in the site editor.
|
||||
description: __(
|
||||
'Renders the classic cart shortcode.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
placeholder: PLACEHOLDERS.cart,
|
||||
},
|
||||
checkout: {
|
||||
type: TYPES.checkout,
|
||||
title: __( 'Checkout Cart', 'woo-gutenberg-products-block' ),
|
||||
description: __(
|
||||
'Renders the classic checkout shortcode.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
placeholder: PLACEHOLDERS.checkout,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
:where(.wp-block-woocommerce-classic-shortcode) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-warning {
|
||||
border-left: 5px solid #2181d2;
|
||||
padding-left: em(40px);
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder .components-placeholder__fieldset {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-wireframe,
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-copy {
|
||||
grid-row-start: 1;
|
||||
grid-column-start: 1;
|
||||
transition: 0.3s all ease;
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-copy {
|
||||
border: 1px solid $gray-900;
|
||||
background-color: #fff;
|
||||
padding: $gap-large $gap-larger;
|
||||
border-radius: $universal-border-radius;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 900px;
|
||||
width: 400px;
|
||||
margin: auto;
|
||||
opacity: 0;
|
||||
z-index: 10;
|
||||
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-copy__icon-container {
|
||||
margin: 0 0 $gap;
|
||||
|
||||
span {
|
||||
@include font-size(larger);
|
||||
display: block;
|
||||
}
|
||||
.woo-icon {
|
||||
color: #{$studio-woocommerce-purple};
|
||||
@include font-size(large);
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0 0 $gap;
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-migration-button-container {
|
||||
justify-content: center;
|
||||
margin: $gap 0;
|
||||
}
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-wireframe {
|
||||
pointer-events: none;
|
||||
|
||||
// Image based placeholders should fill horizontal width.
|
||||
> img,
|
||||
> svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: $universal-border-light;
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-classic-shortcode {
|
||||
.components-placeholder {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.is-selected .wp-block-woocommerce-classic-shortcode,
|
||||
.is-hovered .wp-block-woocommerce-classic-shortcode,
|
||||
.wp-block-woocommerce-classic-shortcode.is-selected,
|
||||
.wp-block-woocommerce-classic-shortcode.is-hovered {
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-wireframe {
|
||||
filter: blur(3px);
|
||||
opacity: 0.5;
|
||||
|
||||
* {
|
||||
color: $universal-border-light !important;
|
||||
border-color: $universal-border-light !important;
|
||||
}
|
||||
}
|
||||
.wp-block-woocommerce-classic-shortcode__placeholder-copy {
|
||||
opacity: 1;
|
||||
}
|
||||
.components-placeholder {
|
||||
box-shadow: inherit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
BlockInstance,
|
||||
createBlock,
|
||||
registerBlockType,
|
||||
} from '@wordpress/blocks';
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
import {
|
||||
useBlockProps,
|
||||
BlockPreview,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import {
|
||||
Button,
|
||||
Placeholder,
|
||||
Popover,
|
||||
ExternalLink,
|
||||
TabbableContainer,
|
||||
} from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { shortcode, Icon } from '@wordpress/icons';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useState, createInterpolateElement } from '@wordpress/element';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import { woo } from '@woocommerce/icons';
|
||||
import { findBlock } from '@woocommerce/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './editor.scss';
|
||||
import './style.scss';
|
||||
import { CartPlaceholder, CheckoutPlaceholder } from './placeholder';
|
||||
import { TEMPLATES, TYPES } from './constants';
|
||||
import { getTemplateDetailsBySlug } from './utils';
|
||||
import * as blockifiedCheckout from './checkout';
|
||||
import * as blockifiedCart from './cart';
|
||||
import metadata from './block.json';
|
||||
import type { BlockifiedTemplateConfig } from './types';
|
||||
|
||||
type Attributes = {
|
||||
shortcode: string;
|
||||
align: string;
|
||||
};
|
||||
|
||||
const blockifiedFallbackConfig = {
|
||||
isConversionPossible: () => false,
|
||||
getBlockifiedTemplate: () => [],
|
||||
getDescription: () => '',
|
||||
onClickCallback: () => void 0,
|
||||
};
|
||||
|
||||
const conversionConfig: { [ key: string ]: BlockifiedTemplateConfig } = {
|
||||
[ TYPES.cart ]: blockifiedCart,
|
||||
[ TYPES.checkout ]: blockifiedCheckout,
|
||||
fallback: blockifiedFallbackConfig,
|
||||
};
|
||||
|
||||
const ConvertTemplate = ( { blockifyConfig, clientId, attributes } ) => {
|
||||
const { getButtonLabel, onClickCallback, getBlockifiedTemplate } =
|
||||
blockifyConfig;
|
||||
|
||||
const [ isPopoverOpen, setIsPopoverOpen ] = useState( false );
|
||||
|
||||
const { replaceBlock, selectBlock } = useDispatch( blockEditorStore );
|
||||
const { createInfoNotice } = useDispatch( noticesStore );
|
||||
|
||||
const { getBlocks } = useSelect( ( sel ) => {
|
||||
return {
|
||||
getBlocks: sel( blockEditorStore ).getBlocks,
|
||||
};
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<TabbableContainer className="wp-block-woocommerce-classic-shortcode__placeholder-migration-button-container">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ () => {
|
||||
onClickCallback( {
|
||||
clientId,
|
||||
getBlocks,
|
||||
attributes,
|
||||
replaceBlock,
|
||||
selectBlock,
|
||||
} );
|
||||
createInfoNotice(
|
||||
__(
|
||||
'Classic shortcode transformed to blocks.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: __(
|
||||
'Undo',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
onClick: () => {
|
||||
const targetBlocks = [
|
||||
'woocommerce/cart',
|
||||
'woocommerce/checkout',
|
||||
];
|
||||
const cartCheckoutBlock = findBlock( {
|
||||
blocks: getBlocks(),
|
||||
findCondition: (
|
||||
foundBlock: BlockInstance
|
||||
) =>
|
||||
targetBlocks.includes(
|
||||
foundBlock.name
|
||||
),
|
||||
} );
|
||||
if ( ! cartCheckoutBlock ) {
|
||||
return;
|
||||
}
|
||||
replaceBlock(
|
||||
cartCheckoutBlock.clientId,
|
||||
createBlock(
|
||||
'woocommerce/classic-shortcode',
|
||||
{
|
||||
shortcode:
|
||||
attributes.shortcode,
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'snackbar',
|
||||
}
|
||||
);
|
||||
} }
|
||||
onMouseEnter={ () => setIsPopoverOpen( true ) }
|
||||
onMouseLeave={ () => setIsPopoverOpen( false ) }
|
||||
text={ getButtonLabel ? getButtonLabel() : '' }
|
||||
tabIndex={ 0 }
|
||||
>
|
||||
{ isPopoverOpen && (
|
||||
<Popover resize={ false } placement="right-end">
|
||||
<div
|
||||
style={ {
|
||||
minWidth: '250px',
|
||||
width: '250px',
|
||||
maxWidth: '250px',
|
||||
minHeight: '300px',
|
||||
height: '300px',
|
||||
maxHeight: '300px',
|
||||
cursor: 'pointer',
|
||||
} }
|
||||
>
|
||||
<BlockPreview
|
||||
blocks={ getBlockifiedTemplate( {
|
||||
...attributes,
|
||||
isPreview: true,
|
||||
} ) }
|
||||
viewportWidth={ 1200 }
|
||||
additionalStyles={ [
|
||||
{
|
||||
css: 'body { padding: 20px !important; height: fit-content !important; overflow:hidden}',
|
||||
},
|
||||
] }
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
) }
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
href="https://woocommerce.com/document/cart-checkout-blocks-support-status/"
|
||||
target="_blank"
|
||||
tabIndex={ 0 }
|
||||
>
|
||||
{ __( 'Learn more', 'woo-gutenberg-products-block' ) }
|
||||
</Button>
|
||||
</TabbableContainer>
|
||||
);
|
||||
};
|
||||
const Edit = ( { clientId, attributes }: BlockEditProps< Attributes > ) => {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
const templateDetails = getTemplateDetailsBySlug(
|
||||
attributes.shortcode,
|
||||
TEMPLATES
|
||||
);
|
||||
const templateTitle = attributes.shortcode;
|
||||
const templatePlaceholder = templateDetails?.placeholder ?? 'cart';
|
||||
const templateType = templateDetails?.type ?? 'fallback';
|
||||
|
||||
const { isConversionPossible, getDescription, getTitle, blockifyConfig } =
|
||||
conversionConfig[ templateType ];
|
||||
|
||||
const canConvert = isConversionPossible();
|
||||
const placeholderTitle = getTitle
|
||||
? getTitle()
|
||||
: __( 'Classic Shortcode Placeholder', 'woo-gutenberg-products-block' );
|
||||
const placeholderDescription = getDescription( templateTitle, canConvert );
|
||||
|
||||
const learnMoreContent = createInterpolateElement(
|
||||
__(
|
||||
'You can learn more about the benefits of switching to blocks, compatibility with extensions, and how to switch back to shortcodes <a>in our documentation</a>.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
{
|
||||
a: (
|
||||
// Suppress the warning as this <a> will be interpolated into the string with content.
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
<ExternalLink href="https://woocommerce.com/document/cart-checkout-blocks-support-status/" />
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Placeholder className="wp-block-woocommerce-classic-shortcode__placeholder">
|
||||
<div className="wp-block-woocommerce-classic-shortcode__placeholder-wireframe">
|
||||
{ templatePlaceholder === 'cart' ? (
|
||||
<CartPlaceholder />
|
||||
) : (
|
||||
<CheckoutPlaceholder />
|
||||
) }
|
||||
</div>
|
||||
<div className="wp-block-woocommerce-classic-shortcode__placeholder-copy">
|
||||
<div className="wp-block-woocommerce-classic-shortcode__placeholder-copy__icon-container">
|
||||
<span className="woo-icon">
|
||||
<Icon icon={ woo } />{ ' ' }
|
||||
{ __(
|
||||
'WooCommerce',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</span>
|
||||
<span>{ placeholderTitle }</span>
|
||||
</div>
|
||||
<p
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: placeholderDescription,
|
||||
} }
|
||||
/>
|
||||
<p>{ learnMoreContent }</p>
|
||||
{ canConvert && blockifyConfig && (
|
||||
<ConvertTemplate
|
||||
clientId={ clientId }
|
||||
blockifyConfig={ blockifyConfig }
|
||||
attributes={ attributes }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</Placeholder>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const settings = {
|
||||
icon: (
|
||||
<Icon
|
||||
icon={ shortcode }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
edit: ( {
|
||||
attributes,
|
||||
clientId,
|
||||
setAttributes,
|
||||
}: BlockEditProps< Attributes > ) => {
|
||||
return (
|
||||
<Edit
|
||||
attributes={ attributes }
|
||||
setAttributes={ setAttributes }
|
||||
clientId={ clientId }
|
||||
/>
|
||||
);
|
||||
},
|
||||
save: () => null,
|
||||
variations: [
|
||||
{
|
||||
name: 'checkout',
|
||||
title: __( 'Classic Checkout', 'woo-gutenberg-products-block' ),
|
||||
attributes: {
|
||||
shortcode: 'checkout',
|
||||
},
|
||||
isActive: ( blockAttributes, variationAttributes ) =>
|
||||
blockAttributes.shortcode === variationAttributes.shortcode,
|
||||
scope: [ 'inserter' ],
|
||||
},
|
||||
{
|
||||
name: 'cart',
|
||||
title: __( 'Classic Cart', 'woo-gutenberg-products-block' ),
|
||||
attributes: {
|
||||
shortcode: 'cart',
|
||||
},
|
||||
isActive: ( blockAttributes, variationAttributes ) =>
|
||||
blockAttributes.shortcode === variationAttributes.shortcode,
|
||||
scope: [ 'inserter' ],
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
registerBlockType( metadata, settings );
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Rect, SVG, G } from '@wordpress/primitives';
|
||||
|
||||
export const CartPlaceholder = () => (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 892 516">
|
||||
<G fill="currentColor" transform="translate(-1)">
|
||||
<Rect width="100" height="20" x="1" rx="2" />
|
||||
<Rect width="100" height="20" x="421" rx="2" />
|
||||
<Rect width="100" height="20" x="793" rx="2" />
|
||||
<Rect width="150" height="24" x="125" y="48" rx="2" />
|
||||
<Rect width="50" height="24" x="125" y="86" rx="2" />
|
||||
<Rect width="50" height="24" x="457" y="48" rx="2" />
|
||||
<Rect width="100" height="100" x="15" y="48" rx="2" />
|
||||
<Rect width="150" height="150" x="1" y="318" rx="2" />
|
||||
<Rect width="150" height="150" x="373" y="318" rx="2" />
|
||||
<Rect width="150" height="150" x="187" y="318" rx="2" />
|
||||
<Rect width="150" height="24" x="125" y="178" rx="2" />
|
||||
<Rect width="50" height="24" x="125" y="216" rx="2" />
|
||||
<Rect width="50" height="24" x="457" y="178" rx="2" />
|
||||
<Rect width="100" height="100" x="15" y="178" rx="2" />
|
||||
<Rect width="304" height="359" x="588" y="34" rx="2" />
|
||||
<Rect width="520" height="1" x="1" y="34" rx=".5" />
|
||||
<Rect width="520" height="1" y="162" rx=".5" />
|
||||
<Rect width="520" height="1" x="1" y="292" rx=".5" />
|
||||
<Rect width="304" height="64" x="589" y="407" rx="2" />
|
||||
<Rect width="100" height="38" x="26" y="478" rx="2" />
|
||||
<Rect width="100" height="38" x="212" y="478" rx="2" />
|
||||
<Rect width="100" height="38" x="398" y="478" rx="2" />
|
||||
</G>
|
||||
</SVG>
|
||||
);
|
||||
|
||||
export const CheckoutPlaceholder = () => (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 892 726">
|
||||
<G fill="currentColor" transform="translate(-1)">
|
||||
<Rect width="203" height="38" x="1" rx="2" />
|
||||
<Rect width="434" height="38" x="1" y="62" rx="2" />
|
||||
<Rect width="434" height="38" y="124" rx="2" />
|
||||
<Rect width="434" height="38" x="1" y="186" rx="2" />
|
||||
<Rect width="434" height="38" x="2" y="248" rx="2" />
|
||||
<Rect width="434" height="38" x="3" y="310" rx="2" />
|
||||
<Rect width="434" height="38" x="3" y="372" rx="2" />
|
||||
<Rect width="892" height="204" x="2" y="434" rx="2" />
|
||||
<Rect width="203" height="38" x="231" rx="2" />
|
||||
<Rect width="203" height="38" x="514" rx="2" />
|
||||
<Rect width="427" height="100" x="466" y="62" rx="2" />
|
||||
<Rect width="304" height="64" x="588" y="662" rx="2" />
|
||||
<Rect width="38" height="38" x="466" rx="2" />
|
||||
<Rect width="203" height="38" x="48" y="662" rx="2" />
|
||||
<Rect width="38" height="38" y="662" rx="2" />
|
||||
</G>
|
||||
</SVG>
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
:where(div[data-block-name="woocommerce/classic-shortcode"]) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { type BlockInstance } from '@wordpress/blocks';
|
||||
|
||||
type TemplateDetail = {
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string | undefined;
|
||||
placeholder: string;
|
||||
};
|
||||
|
||||
export type TemplateDetails = Record< string, TemplateDetail >;
|
||||
|
||||
export type InheritedAttributes = {
|
||||
align?: string;
|
||||
};
|
||||
|
||||
export type OnClickCallbackParameter = {
|
||||
clientId: string;
|
||||
attributes: Record< string, unknown >;
|
||||
getBlocks: () => BlockInstance[];
|
||||
replaceBlock: ( clientId: string, blocks: BlockInstance[] ) => void;
|
||||
selectBlock: ( clientId: string ) => void;
|
||||
};
|
||||
|
||||
type ConversionConfig = {
|
||||
onClickCallback: ( params: OnClickCallbackParameter ) => void;
|
||||
getButtonLabel: () => string;
|
||||
getBlockifiedTemplate: (
|
||||
inheritedAttributes: InheritedAttributes
|
||||
) => BlockInstance[];
|
||||
};
|
||||
|
||||
export type BlockifiedTemplateConfig = {
|
||||
// Description of the template, shown in the block placeholder.
|
||||
getDescription: ( templateTitle: string, canConvert: boolean ) => string;
|
||||
// Returns the skeleton HTML for the template, or can be left blank to use the default fallback image.
|
||||
getSkeleton?: ( () => JSX.Element ) | undefined;
|
||||
// Returns the title for the placeholder, or can be left blank to use the default fallback text.
|
||||
getTitle?: ( () => string ) | undefined;
|
||||
// Is conversion possible for the template?
|
||||
isConversionPossible: () => boolean;
|
||||
// If conversion is possible, returns the config for the template to be blockified.
|
||||
blockifyConfig?: ConversionConfig | undefined;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TemplateDetails } from './types';
|
||||
|
||||
// Finds the most appropriate template details object for specific template keys such as single-product-hoodie.
|
||||
export function getTemplateDetailsBySlug(
|
||||
parsedTemplate: string,
|
||||
templates: TemplateDetails
|
||||
) {
|
||||
const templateKeys = Object.keys( templates );
|
||||
let templateDetails = null;
|
||||
|
||||
for ( let i = 0; templateKeys.length > i; i++ ) {
|
||||
const keyToMatch = parsedTemplate.substr( 0, templateKeys[ i ].length );
|
||||
const maybeTemplate = templates[ keyToMatch ];
|
||||
if ( maybeTemplate ) {
|
||||
templateDetails = maybeTemplate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return templateDetails;
|
||||
}
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
} from '@wordpress/blocks';
|
||||
import { isWpVersion } from '@woocommerce/settings';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
|
||||
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
|
||||
PRODUCT_QUERY_VARIATION_NAME as productsVariationName,
|
||||
} from '@woocommerce/blocks/product-query/constants';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
|
||||
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
|
||||
} from '../product-query/constants';
|
||||
import { VARIATION_NAME as productsVariationName } from '../product-query/variations/product-query';
|
||||
import { createArchiveTitleBlock, createRowBlock } from './utils';
|
||||
import { OnClickCallbackParameter, type InheritedAttributes } from './types';
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ export const TYPES = {
|
||||
productTaxonomy: 'product-taxonomy',
|
||||
productSearchResults: 'product-search-results',
|
||||
orderConfirmation: 'order-confirmation',
|
||||
cart: 'cart',
|
||||
checkout: 'checkout',
|
||||
checkoutHeader: 'checkout-header',
|
||||
};
|
||||
export const PLACEHOLDERS = {
|
||||
@@ -84,16 +82,6 @@ export const TEMPLATES: TemplateDetails = {
|
||||
),
|
||||
placeholder: PLACEHOLDERS.archiveProduct,
|
||||
},
|
||||
cart: {
|
||||
type: TYPES.cart,
|
||||
title: __( 'WooCommerce Cart Block', 'woo-gutenberg-products-block' ),
|
||||
placeholder: 'cart',
|
||||
},
|
||||
checkout: {
|
||||
type: TYPES.checkout,
|
||||
title: __( 'Checkout Block', 'woo-gutenberg-products-block' ),
|
||||
placeholder: 'checkout',
|
||||
},
|
||||
'checkout-header': {
|
||||
type: TYPES.checkoutHeader,
|
||||
title: __( 'Checkout Header', 'woo-gutenberg-products-block' ),
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
border: 1px solid $gray-900;
|
||||
background-color: #fff;
|
||||
padding: $gap-large $gap-larger;
|
||||
border-radius: 3px;
|
||||
border-radius: $universal-border-radius;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 900px;
|
||||
|
||||
@@ -175,7 +175,10 @@ const ConvertTemplate = ( { blockifyConfig, clientId, attributes } ) => {
|
||||
} }
|
||||
>
|
||||
<BlockPreview
|
||||
blocks={ getBlockifiedTemplate( attributes ) }
|
||||
blocks={ getBlockifiedTemplate( {
|
||||
...attributes,
|
||||
isPreview: true,
|
||||
} ) }
|
||||
viewportWidth={ 1200 }
|
||||
additionalStyles={ [
|
||||
{
|
||||
|
||||
@@ -1,11 +1,89 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { createBlock, type BlockInstance } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { OnClickCallbackParameter, InheritedAttributes } from './types';
|
||||
|
||||
const isConversionPossible = () => {
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonLabel = () =>
|
||||
__( 'Transform into blocks', 'woo-gutenberg-products-block' );
|
||||
|
||||
const getBlockifiedTemplate = ( inheritedAttributes: InheritedAttributes ) =>
|
||||
[
|
||||
createBlock( 'woocommerce/order-confirmation-status', {
|
||||
...inheritedAttributes,
|
||||
fontSize: 'large',
|
||||
} ),
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-summary',
|
||||
inheritedAttributes
|
||||
),
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-totals-wrapper',
|
||||
inheritedAttributes
|
||||
),
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-downloads-wrapper',
|
||||
inheritedAttributes
|
||||
),
|
||||
createBlock(
|
||||
'core/columns',
|
||||
{
|
||||
...inheritedAttributes,
|
||||
className: 'woocommerce-order-confirmation-address-wrapper',
|
||||
},
|
||||
[
|
||||
createBlock( 'core/column', inheritedAttributes, [
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-shipping-wrapper',
|
||||
inheritedAttributes
|
||||
),
|
||||
] ),
|
||||
createBlock( 'core/column', inheritedAttributes, [
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-billing-wrapper',
|
||||
inheritedAttributes
|
||||
),
|
||||
] ),
|
||||
]
|
||||
),
|
||||
createBlock(
|
||||
'woocommerce/order-confirmation-additional-information',
|
||||
inheritedAttributes
|
||||
),
|
||||
].filter( Boolean ) as BlockInstance[];
|
||||
|
||||
const onClickCallback = ( {
|
||||
clientId,
|
||||
attributes,
|
||||
getBlocks,
|
||||
replaceBlock,
|
||||
selectBlock,
|
||||
}: OnClickCallbackParameter ) => {
|
||||
replaceBlock( clientId, getBlockifiedTemplate( attributes ) );
|
||||
|
||||
const blocks = getBlocks();
|
||||
|
||||
const groupBlock = blocks.find(
|
||||
( block ) =>
|
||||
block.name === 'core/group' &&
|
||||
block.innerBlocks.some(
|
||||
( innerBlock ) =>
|
||||
innerBlock.name === 'woocommerce/store-notices'
|
||||
)
|
||||
);
|
||||
|
||||
if ( groupBlock ) {
|
||||
selectBlock( groupBlock.clientId );
|
||||
}
|
||||
};
|
||||
|
||||
const getDescription = () => {
|
||||
@@ -150,4 +228,10 @@ const getSkeleton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { isConversionPossible, getDescription, getSkeleton };
|
||||
const blockifyConfig = {
|
||||
getButtonLabel,
|
||||
onClickCallback,
|
||||
getBlockifiedTemplate,
|
||||
};
|
||||
|
||||
export { blockifyConfig, isConversionPossible, getDescription, getSkeleton };
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
} from '@wordpress/blocks';
|
||||
import { isWpVersion } from '@woocommerce/settings';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
|
||||
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
|
||||
PRODUCT_QUERY_VARIATION_NAME as productsVariationName,
|
||||
} from '@woocommerce/blocks/product-query/constants';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
|
||||
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
|
||||
} from '../product-query/constants';
|
||||
import { VARIATION_NAME as productsVariationName } from '../product-query/variations/product-query';
|
||||
import { createArchiveTitleBlock, createRowBlock } from './utils';
|
||||
import { OnClickCallbackParameter, type InheritedAttributes } from './types';
|
||||
|
||||
|
||||
@@ -6,36 +6,24 @@ import { getTemplateDetailsBySlug } from '../utils';
|
||||
|
||||
describe( 'getTemplateDetailsBySlug', function () {
|
||||
it( 'should return single-product object when given an exact match', () => {
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'single-product', TEMPLATES )
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'single-product', TEMPLATES )
|
||||
).toStrictEqual( TEMPLATES[ 'single-product' ] );
|
||||
} );
|
||||
|
||||
it( 'should return single-product object when given a partial match', () => {
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'single-product-hoodie', TEMPLATES )
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'single-product-hoodie', TEMPLATES )
|
||||
).toStrictEqual( TEMPLATES[ 'single-product' ] );
|
||||
} );
|
||||
|
||||
it( 'should return taxonomy-product object when given a partial match', () => {
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'taxonomy-product_tag', TEMPLATES )
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'taxonomy-product_tag', TEMPLATES )
|
||||
).toStrictEqual( TEMPLATES[ 'taxonomy-product_tag' ] );
|
||||
} );
|
||||
|
||||
it( 'should return taxonomy-product object when given an exact match', () => {
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'taxonomy-product_brands', TEMPLATES )
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
getTemplateDetailsBySlug( 'taxonomy-product_brands', TEMPLATES )
|
||||
).toStrictEqual( TEMPLATES[ 'taxonomy-product' ] );
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "woocommerce/collection-filters",
|
||||
"version": "1.0.0",
|
||||
"title": "Collection Filters",
|
||||
"description": "A block that adds product filters to the product collection.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce", "Filters" ],
|
||||
"textdomain": "woocommerce",
|
||||
"supports": {
|
||||
"html": false,
|
||||
"reusable": false
|
||||
},
|
||||
"usesContext": [ "query" ],
|
||||
"ancestor": [ "woocommerce/product-collection" ],
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
|
||||
|
||||
const Edit = () => {
|
||||
const blockProps = useBlockProps();
|
||||
const innerBlockProps = useInnerBlocksProps( blockProps );
|
||||
|
||||
return <nav { ...innerBlockProps } />;
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, more } from '@wordpress/icons';
|
||||
import { isExperimentalBuild } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import save from './save';
|
||||
|
||||
if ( isExperimentalBuild() ) {
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ more }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit,
|
||||
save,
|
||||
} );
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "woocommerce/collection-price-filter",
|
||||
"version": "1.0.0",
|
||||
"title": "Collection Price Filter",
|
||||
"description": "Enable customers to filter the product collection by choosing a price range.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [
|
||||
"WooCommerce"
|
||||
],
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
"viewScript": [
|
||||
"wc-collection-price-filter-block-frontend"
|
||||
],
|
||||
"ancestor": [
|
||||
"woocommerce/product-collection"
|
||||
],
|
||||
"supports": {
|
||||
"interactivity": true
|
||||
},
|
||||
"attributes": {
|
||||
"showInputFields": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"inlineInput": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { useCollectionData } from '@woocommerce/base-context/hooks';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import FilterResetButton from '@woocommerce/base-components/filter-reset-button';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getFormattedPrice } from './utils';
|
||||
import { EditProps } from './types';
|
||||
import { PriceSlider } from './price-slider';
|
||||
|
||||
const Edit = ( props: EditProps ) => {
|
||||
const blockProps = useBlockProps();
|
||||
const { results } = useCollectionData( {
|
||||
queryPrices: true,
|
||||
isEditor: true,
|
||||
queryState: {},
|
||||
} );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Disabled>
|
||||
<div className="controls">
|
||||
<PriceSlider
|
||||
{ ...props }
|
||||
collectionData={ getFormattedPrice( results ) }
|
||||
/>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<FilterResetButton onClick={ () => false } />
|
||||
</div>
|
||||
</Disabled>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store, navigate } from '@woocommerce/interactivity';
|
||||
import { formatPrice, getCurrency } from '@woocommerce/price-format';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ActionProps, StateProps } from './types';
|
||||
|
||||
const getHrefWithFilters = ( { state }: StateProps ) => {
|
||||
const { minPrice, maxPrice } = state.filters;
|
||||
const url = new URL( window.location.href );
|
||||
const { searchParams } = url;
|
||||
|
||||
if ( minPrice > 0 ) {
|
||||
searchParams.set( 'min_price', minPrice.toString() );
|
||||
} else {
|
||||
searchParams.delete( 'min_price' );
|
||||
}
|
||||
|
||||
if ( maxPrice < state.filters.maxRange ) {
|
||||
searchParams.set( 'max_price', maxPrice.toString() );
|
||||
} else {
|
||||
searchParams.delete( 'max_price' );
|
||||
}
|
||||
|
||||
searchParams.forEach( ( _, key ) => {
|
||||
if ( /query-[0-9]+-page/.test( key ) ) searchParams.delete( key );
|
||||
} );
|
||||
|
||||
return url.href;
|
||||
};
|
||||
|
||||
store( {
|
||||
state: {
|
||||
filters: {
|
||||
rangeStyle: ( { state }: StateProps ) => {
|
||||
const { minPrice, maxPrice, maxRange } = state.filters;
|
||||
return [
|
||||
`--low: ${ ( 100 * minPrice ) / maxRange }%`,
|
||||
`--high: ${ ( 100 * maxPrice ) / maxRange }%`,
|
||||
].join( ';' );
|
||||
},
|
||||
formattedMinPrice: ( { state }: StateProps ) => {
|
||||
const { minPrice } = state.filters;
|
||||
return formatPrice( minPrice, getCurrency( { minorUnit: 0 } ) );
|
||||
},
|
||||
formattedMaxPrice: ( { state }: StateProps ) => {
|
||||
const { maxPrice } = state.filters;
|
||||
return formatPrice( maxPrice, getCurrency( { minorUnit: 0 } ) );
|
||||
},
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
filters: {
|
||||
setMinPrice: ( { state, event }: ActionProps ) => {
|
||||
const value = parseFloat( event.target.value );
|
||||
state.filters.minPrice = Math.min(
|
||||
Number.isNaN( value ) ? state.filters.minRange : value,
|
||||
state.filters.maxRange - 1
|
||||
);
|
||||
state.filters.maxPrice = Math.max(
|
||||
state.filters.maxPrice,
|
||||
state.filters.minPrice + 1
|
||||
);
|
||||
},
|
||||
setMaxPrice: ( { state, event }: ActionProps ) => {
|
||||
const value = parseFloat( event.target.value );
|
||||
state.filters.maxPrice = Math.max(
|
||||
Number.isNaN( value ) ? state.filters.maxRange : value,
|
||||
state.filters.minRange + 1
|
||||
);
|
||||
state.filters.minPrice = Math.min(
|
||||
state.filters.minPrice,
|
||||
state.filters.maxPrice - 1
|
||||
);
|
||||
},
|
||||
updateProducts: ( { state }: ActionProps ) => {
|
||||
navigate( getHrefWithFilters( { state } ) );
|
||||
},
|
||||
reset: ( { state }: ActionProps ) => {
|
||||
state.filters.minPrice = 0;
|
||||
state.filters.maxPrice = state.filters.maxRange;
|
||||
navigate( getHrefWithFilters( { state } ) );
|
||||
},
|
||||
},
|
||||
},
|
||||
} );
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, currencyDollar } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import metadata from './block.json';
|
||||
import Edit from './edit';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ currencyDollar }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
} );
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { FilterComponentProps } from '../types';
|
||||
import { Inspector } from './inspector';
|
||||
import './style.scss';
|
||||
|
||||
export const PriceSlider = ( {
|
||||
collectionData,
|
||||
...editProps
|
||||
}: FilterComponentProps ) => {
|
||||
const { showInputFields, inlineInput } = editProps.attributes;
|
||||
const { minPrice, maxPrice, formattedMinPrice, formattedMaxPrice } =
|
||||
collectionData;
|
||||
const onChange = () => null;
|
||||
const priceMin = showInputFields ? (
|
||||
<input
|
||||
className="min"
|
||||
type="text"
|
||||
value={ minPrice }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
) : (
|
||||
<span>{ formattedMinPrice }</span>
|
||||
);
|
||||
|
||||
const priceMax = showInputFields ? (
|
||||
<input
|
||||
className="max"
|
||||
type="text"
|
||||
value={ maxPrice }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
) : (
|
||||
<span>{ formattedMaxPrice }</span>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Inspector { ...editProps } />
|
||||
<div
|
||||
className={ classNames( 'price-slider', {
|
||||
'inline-input': inlineInput && showInputFields,
|
||||
} ) }
|
||||
>
|
||||
<div className="range">
|
||||
<div className="range-bar"></div>
|
||||
<input
|
||||
type="range"
|
||||
className="min"
|
||||
min={ minPrice }
|
||||
max={ maxPrice }
|
||||
value={ minPrice }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
className="max"
|
||||
min={ minPrice }
|
||||
max={ maxPrice }
|
||||
value={ maxPrice }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
</div>
|
||||
<div className="text">
|
||||
{ priceMin }
|
||||
{ priceMax }
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import {
|
||||
PanelBody,
|
||||
ToggleControl,
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { EditProps } from '../types';
|
||||
|
||||
export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
|
||||
const { showInputFields, inlineInput } = attributes;
|
||||
return (
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __( 'Settings', 'woo-gutenberg-products-block' ) }
|
||||
>
|
||||
<ToggleGroupControl
|
||||
label={ __(
|
||||
'Price Slider',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ showInputFields ? 'editable' : 'text' }
|
||||
onChange={ ( value: string ) =>
|
||||
setAttributes( {
|
||||
showInputFields: value === 'editable',
|
||||
} )
|
||||
}
|
||||
className="wc-block-price-filter__price-range-toggle"
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value="editable"
|
||||
label={ __(
|
||||
'Editable',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value="text"
|
||||
label={ __( 'Text', 'woo-gutenberg-products-block' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
{ showInputFields && (
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Inline input fields',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ inlineInput }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
inlineInput: ! inlineInput,
|
||||
} )
|
||||
}
|
||||
help={ __(
|
||||
'Show input fields inline with the slider.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,216 @@
|
||||
@mixin thumb {
|
||||
background: $white;
|
||||
background-position: 0 0;
|
||||
box-sizing: content-box;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid $gray-900;
|
||||
border-radius: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
pointer-events: auto;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
&:hover {
|
||||
@include thumbFocus;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin thumbFocus {
|
||||
background: $gray-900;
|
||||
border-color: $white;
|
||||
}
|
||||
|
||||
|
||||
@mixin track {
|
||||
cursor: default;
|
||||
height: 1px;
|
||||
/* Required for Samsung internet based browsers */
|
||||
outline: 0;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
@mixin reset {
|
||||
margin: 0;
|
||||
/* Use !important to prevent theme input styles from breaking the component.
|
||||
Reference https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/3902
|
||||
*/
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-collection-price-filter .price-slider {
|
||||
.range {
|
||||
--low: 0%;
|
||||
--high: 100%;
|
||||
--range-color: currentColor;
|
||||
--track-background: linear-gradient(to right, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 100% / 100% 100%;
|
||||
|
||||
.rtl & {
|
||||
--track-background: linear-gradient(to left, transparent var(--low), var(--range-color) 0, var(--range-color) var(--high), transparent 0) no-repeat 0 100% / 100% 100%;
|
||||
}
|
||||
|
||||
@include reset;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
clear: both;
|
||||
flex-grow: 1;
|
||||
height: 4px;
|
||||
margin: 15px 0;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: currentColor;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.range-bar {
|
||||
position: relative;
|
||||
height: 4px;
|
||||
background: var(--track-background);
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
@include reset;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
outline: none !important;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumb;
|
||||
margin: -5px 0 0 0;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include thumb;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include thumb;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
&::-webkit-slider-thumb {
|
||||
@include thumbFocus;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
@include thumbFocus;
|
||||
}
|
||||
&::-ms-thumb {
|
||||
@include thumbFocus;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include track;
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
@include track;
|
||||
}
|
||||
|
||||
&::-webkit-slider-progress {
|
||||
@include reset;
|
||||
}
|
||||
|
||||
&::-moz-range-progress {
|
||||
@include reset;
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&.min {
|
||||
&::-webkit-slider-thumb {
|
||||
margin-left: -2px;
|
||||
background-position-x: left;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
background-position-x: left;
|
||||
transform: translate(-2px, 2px);
|
||||
}
|
||||
&::-ms-thumb {
|
||||
background-position-x: left;
|
||||
}
|
||||
}
|
||||
|
||||
&.max {
|
||||
&::-webkit-slider-thumb {
|
||||
background-position-x: right;
|
||||
margin-left: 2px;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
background-position-x: right;
|
||||
transform: translate(2px, 2px);
|
||||
}
|
||||
&::-ms-thumb {
|
||||
background-position-x: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="range" i] {
|
||||
color: -internal-light-dark(rgb(16, 16, 16), rgb(255, 255, 255));
|
||||
padding: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
gap: 8px;
|
||||
|
||||
input[type="text"] {
|
||||
padding: 8px;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
max-width: 60px;
|
||||
min-width: 0;
|
||||
font-size: 0.875em;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.text {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.text .min {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user