* Handle multiple packages coming from slots

* fix missing prop

* fix typo

* remove collapse param

* rename trinary to ternary
This commit is contained in:
Seghir Nadir 2022-12-15 15:17:22 +01:00 committed by GitHub
parent e71a2fdbb0
commit b58c5beb3e
7 changed files with 62 additions and 43 deletions

View File

@ -44,6 +44,10 @@ export type PackageRateRenderOption = (
option: CartShippingPackageShippingRate
) => PackageRateOption;
// A flag can be ternary if true, false, and undefined are all valid options.
// In our case, we use this for collapsible and showItems, having a boolean will force that
// option, having undefined will let the component decide the logic based on other factors.
export type TernaryFlag = boolean | undefined;
interface PackageProps {
/* PackageId can be a string, WooCommerce Subscriptions uses strings for example, but WooCommerce core uses numbers */
packageId: string | number;
@ -51,9 +55,9 @@ interface PackageProps {
collapse?: boolean;
packageData: PackageData;
className?: string;
collapsible?: boolean;
collapsible?: TernaryFlag;
noResultsMessage: ReactElement;
showItems?: boolean;
showItems?: TernaryFlag;
}
export const ShippingRatesControlPackage = ( {
@ -62,15 +66,26 @@ export const ShippingRatesControlPackage = ( {
noResultsMessage,
renderOption,
packageData,
collapsible = false,
collapse = false,
showItems = false,
collapsible,
showItems,
}: PackageProps ): ReactElement => {
const { selectShippingRate } = useSelectShippingRate();
const multiplePackages =
document.querySelectorAll(
'.wc-block-components-shipping-rates-control__package'
).length > 1;
// If showItems is not set, we check if we have multiple packages.
// We sometimes don't want to show items even if we have multiple packages.
const shouldShowItems = showItems ?? multiplePackages;
// If collapsible is not set, we check if we have multiple packages.
// We sometimes don't want to collapse even if we have multiple packages.
const shouldBeCollapsible = collapsible ?? multiplePackages;
const header = (
<>
{ ( showItems || collapsible ) && (
{ ( shouldBeCollapsible || shouldShowItems ) && (
<div
className="wc-block-components-shipping-rates-control__package-title"
dangerouslySetInnerHTML={ {
@ -78,7 +93,7 @@ export const ShippingRatesControlPackage = ( {
} }
/>
) }
{ showItems && (
{ shouldShowItems && (
<ul className="wc-block-components-shipping-rates-control__package-items">
{ Object.values( packageData.items ).map( ( v ) => {
const name = decodeEntities( v.name );
@ -127,11 +142,15 @@ export const ShippingRatesControlPackage = ( {
renderOption={ renderOption }
/>
);
if ( collapsible ) {
if ( shouldBeCollapsible ) {
return (
<Panel
className="wc-block-components-shipping-rates-control__package"
initialOpen={ ! collapse }
// initialOpen remembers only the first value provided to it, so by the
// time we know we have several packages, initialOpen would be hardcoded to true.
// If we're rendering a panel, we're more likely rendering several
// packages and we want to them to be closed initially.
initialOpen={ false }
title={ header }
>
{ body }

View File

@ -5,6 +5,10 @@
margin-bottom: 0;
}
&.wc-block-components-panel {
margin-bottom: 0;
}
.wc-block-components-panel__button {
margin-bottom: 0;
margin-top: 0;

View File

@ -19,13 +19,13 @@ import { ReactElement } from 'react';
*/
import ShippingRatesControlPackage, {
PackageRateRenderOption,
TernaryFlag,
} from '../shipping-rates-control-package';
interface PackagesProps {
packages: CartResponseShippingRate[];
collapse?: boolean;
collapsible?: boolean;
showItems?: boolean;
collapsible?: TernaryFlag;
showItems?: TernaryFlag;
noResultsMessage: ReactElement;
renderOption: PackageRateRenderOption;
}
@ -35,18 +35,15 @@ interface PackagesProps {
*
* @param {Object} props Incoming props.
* @param {Array} props.packages Array of packages.
* @param {boolean} props.collapsible If the package should be rendered as a
* @param {boolean|undefined} props.collapsible If the package should be rendered as a
* @param {ReactElement} props.noResultsMessage Rendered when there are no rates in a package.
* collapsible panel.
* @param {boolean} props.collapse If the panel should be collapsed by default,
* only works if collapsible is true.
* @param {boolean} props.showItems If we should items below the package name.
* @param {boolean|undefined} props.showItems If we should items below the package name.
* @param {PackageRateRenderOption} [props.renderOption] Function to render a shipping rate.
* @return {JSX.Element|null} Rendered components.
*/
const Packages = ( {
packages,
collapse,
showItems,
collapsible,
noResultsMessage,
@ -63,9 +60,8 @@ const Packages = ( {
key={ packageId }
packageId={ packageId }
packageData={ packageData }
collapsible={ !! collapsible }
collapse={ !! collapse }
showItems={ showItems || packages.length > 1 }
collapsible={ collapsible }
showItems={ showItems }
noResultsMessage={ noResultsMessage }
renderOption={ renderOption }
/>
@ -75,7 +71,8 @@ const Packages = ( {
};
interface ShippingRatesControlProps {
collapsible?: boolean;
collapsible?: TernaryFlag;
showItems?: TernaryFlag;
shippingRates: CartResponseShippingRate[];
className?: string;
isLoadingRates: boolean;
@ -86,20 +83,22 @@ interface ShippingRatesControlProps {
/**
* Renders the shipping rates control element.
*
* @param {Object} props Incoming props.
* @param {Array} props.shippingRates Array of packages containing shipping rates.
* @param {boolean} props.isLoadingRates True when rates are being loaded.
* @param {string} props.className Class name for package rates.
* @param {boolean} [props.collapsible] If true, when multiple packages are rendered they can be toggled open and closed.
* @param {ReactElement} props.noResultsMessage Rendered when there are no packages.
* @param {Function} [props.renderOption] Function to render a shipping rate.
* @param {string} [props.context] String equal to the block name where the Slot is rendered
* @param {Object} props Incoming props.
* @param {Array} props.shippingRates Array of packages containing shipping rates.
* @param {boolean} props.isLoadingRates True when rates are being loaded.
* @param {string} props.className Class name for package rates.
* @param {boolean|undefined} [props.collapsible] If true, when multiple packages are rendered they can be toggled open and closed.
* @param {boolean|undefined} [props.showItems] If true, when multiple packages are rendered, you can see each package's items.
* @param {ReactElement} props.noResultsMessage Rendered when there are no packages.
* @param {Function} [props.renderOption] Function to render a shipping rate.
* @param {string} [props.context] String equal to the block name where the Slot is rendered
*/
const ShippingRatesControl = ( {
shippingRates,
isLoadingRates,
className,
collapsible = false,
collapsible,
showItems,
noResultsMessage,
renderOption,
context,
@ -157,6 +156,7 @@ const ShippingRatesControl = ( {
const slotFillProps = {
className,
collapsible,
showItems,
noResultsMessage,
renderOption,
extensions,
@ -165,7 +165,6 @@ const ShippingRatesControl = ( {
ShippingRatesControlPackage,
},
context,
shippingRates,
};
const { isEditor } = useEditorContext();
@ -191,7 +190,6 @@ const ShippingRatesControl = ( {
/>
<ExperimentalOrderShippingPackages>
<Packages
showItems={ shippingRates.length > 1 }
packages={ shippingRates }
noResultsMessage={ noResultsMessage }
renderOption={ renderOption }

View File

@ -23,7 +23,6 @@ const ShippingRateSelector = ( {
<legend className="screen-reader-text">{ legend }</legend>
<ShippingRatesControl
className="wc-block-components-totals-shipping__options"
collapsible={ true }
noResultsMessage={
<Notice
isDismissible={ false }

View File

@ -99,6 +99,7 @@ const Block = (): JSX.Element | null => {
</Notice>
}
renderOption={ renderShippingRatesControlOption }
collapsible={ false }
shippingRates={ shippingRates }
isLoadingRates={ isLoadingRates }
context="woocommerce/checkout"

View File

@ -47,9 +47,9 @@ Checkout:
### Passed parameters
- `collapsible`: `Boolean` If a shipping package panel should be collapsible or not, this is false in Checkout and true in Cart.
- `collapse`: `Boolean` If a panel should be collapsed by default, this is true if there's more than 1 fill registered (Core Shipping options are registered as a fill and they're counted).
- `showItems`: `Boolean` If we should show the content of each package, this is true if there's more than 1 fill registered (Core Shipping options are registered as a fill and they're counted).
- `collapsible`: `Boolean|undefined` If a shipping package panel should be collapsible or not, this is false in Checkout and undefined in Cart.
- `collapse`: `Boolean` If a panel should be collapsed by default, this is true if if panels are collapsible.
- `showItems`: `Boolean|undefined` If we should show the content of each package, this is undefined in Cart and Checkout and is left to the actual package logic to decide.
- `noResultsMessage`: A React element that you can render if there are no shipping options.
- `renderOption`: a render function that takes a rate object and returns a render option.
- `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188)

View File

@ -6,7 +6,7 @@ import classnames from 'classnames';
/**
* Internal dependencies
*/
import { createSlotFill, useSlot } from '../../slot';
import { createSlotFill } from '../../slot';
const slotName = '__experimentalOrderShippingPackages';
const {
@ -16,17 +16,15 @@ const {
const Slot = ( {
className,
collapsible,
noResultsMessage,
renderOption,
extensions,
cart,
components,
context,
shippingRates,
collapsible,
showItems,
} ) => {
const { fills } = useSlot( slotName );
const hasMultiplePackages = fills.length > 1 || shippingRates?.length > 1;
return (
<OrderShippingPackagesSlot
className={ classnames(
@ -34,9 +32,9 @@ const Slot = ( {
className
) }
fillProps={ {
collapse: collapsible,
collapsible,
collapse: hasMultiplePackages,
showItems: hasMultiplePackages,
showItems,
noResultsMessage,
renderOption,
extensions,