diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx index 696db0fefae..c6e3e35865f 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx @@ -6,6 +6,8 @@ import { RadioControlOptionType, } from '@woocommerce/blocks-components'; import { CartShippingPackageShippingRate } from '@woocommerce/types'; +import { CART_STORE_KEY } from '@woocommerce/block-data'; +import { useSelect } from '@wordpress/data'; interface LocalPickupSelectProps { title?: string | undefined; @@ -31,9 +33,14 @@ export const LocalPickupSelect = ( { renderPickupLocation, packageCount, }: LocalPickupSelectProps ) => { + const internalPackageCount = useSelect( + ( select ) => + select( CART_STORE_KEY )?.getCartData()?.shippingRates?.length + ); // Hacky way to check if there are multiple packages, this way is borrowed from `assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx` // We have no built-in way of checking if other extensions have added packages. const multiplePackages = + internalPackageCount > 1 || document.querySelectorAll( '.wc-block-components-local-pickup-select .wc-block-components-radio-control' ).length > 1; @@ -45,6 +52,7 @@ export const LocalPickupSelect = ( { setSelectedOption( value ); onSelectRate( value ); } } + highlightChecked={ true } selected={ selectedOption } options={ pickupLocations.map( ( location ) => renderPickupLocation( location, packageCount ) diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx index 2da920e68d2..2b60f2ed2c1 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx @@ -5,10 +5,12 @@ import classNames from 'classnames'; import { _n, sprintf } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; import { Label, Panel } from '@woocommerce/blocks-components'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; import { useShippingData } from '@woocommerce/base-context/hooks'; import { sanitizeHTML } from '@woocommerce/utils'; import type { ReactElement } from 'react'; +import { useSelect } from '@wordpress/data'; +import { CART_STORE_KEY } from '@woocommerce/block-data'; /** * Internal dependencies @@ -25,9 +27,18 @@ export const ShippingRatesControlPackage = ( { packageData, collapsible, showItems, + highlightChecked = false, }: PackageProps ): ReactElement => { const { selectShippingRate, isSelectingRate } = useShippingData(); + + const internalPackageCount = useSelect( + ( select ) => + select( CART_STORE_KEY )?.getCartData()?.shippingRates?.length + ); + + // We have no built-in way of checking if other extensions have added packages e.g. if subscriptions has added them. const multiplePackages = + internalPackageCount > 1 || document.querySelectorAll( '.wc-block-components-shipping-rates-control__package' ).length > 1; @@ -102,8 +113,15 @@ export const ShippingRatesControlPackage = ( { ), renderOption, disabled: isSelectingRate, + highlightChecked, }; + const selectedOptionNumber = useMemo( () => { + return packageData?.shipping_rates?.findIndex( + ( rate ) => rate?.selected + ); + }, [ packageData?.shipping_rates ] ); + if ( shouldBeCollapsible ) { return ( diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-rates.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-rates.tsx index 31778fba701..6d946ee7369 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-rates.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/package-rates.tsx @@ -13,7 +13,7 @@ import { usePrevious } from '@woocommerce/base-hooks'; * Internal dependencies */ import { renderPackageRateOption } from './render-package-rate-option'; -import type { PackageRateRenderOption } from '../shipping-rates-control-package'; +import type { PackageRateRenderOption } from '../shipping-rates-control-package/types'; interface PackageRates { onSelectRate: ( selectedRateId: string ) => void; @@ -23,6 +23,8 @@ interface PackageRates { noResultsMessage: JSX.Element; selectedRate: CartShippingPackageShippingRate | undefined; disabled?: boolean; + // Should the selected rate be highlighted. + highlightChecked?: boolean; } const PackageRates = ( { @@ -33,6 +35,7 @@ const PackageRates = ( { renderOption = renderPackageRateOption, selectedRate, disabled = false, + highlightChecked = false, }: PackageRates ): JSX.Element => { const selectedRateId = selectedRate?.rate_id || ''; const previousSelectedRateId = usePrevious( selectedRateId ); @@ -76,6 +79,7 @@ const PackageRates = ( { setSelectedOption( value ); onSelectRate( value ); } } + highlightChecked={ highlightChecked } disabled={ disabled } selected={ selectedOption } options={ rates.map( renderOption ) } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss index a376d55c78a..8d13b81d2f5 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss @@ -1,6 +1,5 @@ .wc-block-components-shipping-rates-control__package { margin: 0; - border-bottom: 1px solid $universal-border-light; &.wc-block-components-panel { margin-bottom: 0; @@ -14,8 +13,6 @@ } &:last-child { - border-bottom: 0; - .wc-block-components-panel__button { padding-bottom: 0; } @@ -51,7 +48,7 @@ @include font-size(small); display: block; list-style: none; - margin: 0; + margin: 0 0 $gap-small 0; padding: 0; } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/types.ts b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/types.ts index f64b0dbb392..33aa2f55d13 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/types.ts +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control-package/types.ts @@ -46,4 +46,6 @@ export interface PackageProps { collapsible?: TernaryFlag; noResultsMessage: ReactElement; showItems?: TernaryFlag; + // Should the selected rate be highlighted. + highlightChecked?: boolean; } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control/index.tsx index 790593236df..067d35e7491 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-rates-control/index.tsx @@ -33,6 +33,7 @@ const Packages = ( { collapsible, noResultsMessage, renderOption, + context = '', }: PackagesProps ): JSX.Element | null => { // If there are no packages, return nothing. if ( ! packages.length ) { @@ -42,6 +43,7 @@ const Packages = ( { <> { packages.map( ( { package_id: packageId, ...packageData } ) => ( { } ); return isExpressPaymentMethodActive ? null : ( { return options.length > 0 ? ( <> ; selected: string | null; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } const RadioControlAccordion = ( { @@ -31,9 +34,14 @@ const RadioControlAccordion = ( { selected, onChange, options = [], + highlightChecked = false, }: RadioControlAccordionProps ): JSX.Element | null => { const radioControlId = id || instanceId; + const selectedOptionNumber = useMemo( () => { + return options.findIndex( ( option ) => option.value === selected ); + }, [ options, selected ] ); + if ( ! options.length ) { return null; } @@ -41,6 +49,15 @@ const RadioControlAccordion = ( {
@@ -50,7 +67,13 @@ const RadioControlAccordion = ( { const checked = option.value === selected; return (
{ const instanceId = useInstanceId( RadioControl ); const radioControlId = id || instanceId; + const selectedOptionNumber = useMemo( () => { + return options.findIndex( ( option ) => option.value === selected ); + }, [ options, selected ] ); + if ( ! options.length ) { return null; } @@ -29,11 +36,21 @@ const RadioControl = ( {
{ options.map( ( option ) => ( { const { value, label, description, secondaryLabel, secondaryDescription } = option; @@ -29,6 +30,8 @@ const Option = ( { { 'wc-block-components-radio-control__option-checked': checked, + 'wc-block-components-radio-control__option--checked-option-highlighted': + checked && highlightChecked, } ) } htmlFor={ `${ name }-${ value }` } diff --git a/plugins/woocommerce-blocks/packages/components/radio-control/style.scss b/plugins/woocommerce-blocks/packages/components/radio-control/style.scss index e18e2377954..6900bef4284 100644 --- a/plugins/woocommerce-blocks/packages/components/radio-control/style.scss +++ b/plugins/woocommerce-blocks/packages/components/radio-control/style.scss @@ -1,3 +1,103 @@ +.wc-block-components-radio-control--highlight-checked { + position: relative; + + div.wc-block-components-radio-control-accordion-option { + position: relative; + + // This ::after element is to fake a transparent border-top on each option. + // We can't just use border-top on the option itself because of the border around the entire accordion. + // Both borders have transparency so there's an overlap where the border is darker (due to adding two + // transparent colours together). Doing it with an ::after lets us bring the "border" in by one pixel on each + // side to avoid the overlap. + &::after { + content: ""; + background: $universal-border-light; + height: 1px; + right: 1px; + left: 1px; + top: 0; + position: absolute; + } + + // The first child doesn't need a fake border-top because it's handled by its parent's border-top. This stops + // a double border. + &:first-child::after { + display: none; + } + + // This rule removes the fake border-top from the selected element to prevent a double border. + &.wc-block-components-radio-control-accordion-option--checked-option-highlighted + div.wc-block-components-radio-control-accordion-option::after { + display: none; + } + } + + // Adds a "border" around the selected option. This is done with a box-shadow to prevent a double border on the left + // and right of the selected element, and top and bottom of the first/last elements. + label.wc-block-components-radio-control__option--checked-option-highlighted, + .wc-block-components-radio-control-accordion-option--checked-option-highlighted { + box-shadow: 0 0 0 1.5px currentColor inset; + border-radius: 4px; + } + + // Defines a border around the radio control. Cannot be done with normal CSS borders or outlines because when + // selecting an item we get a double border on the left and right. It's not possible to remove the outer border just + // for the selected element, but using a pseudo element gives us more control. + &::after { + content: ""; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + position: absolute; + border: 1px solid $universal-border-light; + border-radius: 4px; + width: 100%; + box-sizing: border-box; + } + + // Remove the top border when the first element is selected, this is so we don't get a double border with the + // box-shadow. + &.wc-block-components-radio-control--highlight-checked--first-selected::after { + border-top: 0; + margin-top: 2px; + } + + // Remove the bottom border when the last element is selected, this is so we don't get a double border with the + // box-shadow. + &.wc-block-components-radio-control--highlight-checked--last-selected::after { + margin-bottom: 2px; + border-bottom: 0; + } + + // Remove the fake border-top from the item after the selected element, this is to prevent a double border with the + // selected element's box-shadow. + .wc-block-components-radio-control__option--checked-option-highlighted + .wc-block-components-radio-control__option::after { + display: none; + } + + .wc-block-components-radio-control__option { + + // Add a fake border to the top of each radio option. This is because using CSS borders would result in an + // overlap and two transparent borders combining to make a darker pixel. This fake border allows us to bring the + // border in by one pixel on each side to avoid the overlap. + &::after { + content: ""; + background: $universal-border-light; + height: 1px; + right: 1px; + left: 1px; + top: 0; + position: absolute; + } + + // The first child doesn't need a fake border-top because it's handled by its parent's border-top. + &:first-child::after { + display: none; + } + } +} + .wc-block-components-radio-control__option { @include reset-color(); @include reset-typography(); @@ -5,7 +105,6 @@ margin: em($gap) 0; margin-top: 0; padding: 0 0 0 em($gap-larger); - position: relative; cursor: pointer; diff --git a/plugins/woocommerce-blocks/packages/components/radio-control/types.ts b/plugins/woocommerce-blocks/packages/components/radio-control/types.ts index af736c5a50b..e30af174b92 100644 --- a/plugins/woocommerce-blocks/packages/components/radio-control/types.ts +++ b/plugins/woocommerce-blocks/packages/components/radio-control/types.ts @@ -16,6 +16,8 @@ export interface RadioControlProps { options: RadioControlOption[]; // Is the control disabled. disabled?: boolean; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } export interface RadioControlOptionProps { @@ -24,6 +26,8 @@ export interface RadioControlOptionProps { onChange: ( value: string ) => void; option: RadioControlOption; disabled?: boolean; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } interface RadioControlOptionContent { diff --git a/plugins/woocommerce/changelog/update-shipping-borders b/plugins/woocommerce/changelog/update-shipping-borders new file mode 100644 index 00000000000..4de16f3808c --- /dev/null +++ b/plugins/woocommerce/changelog/update-shipping-borders @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Change styling for shipping, payment, and local pickup radio buttons in the Checkout block