Storybook and TS migration of some cart checkout components (https://github.com/woocommerce/woocommerce-blocks/pull/5324)
* Migrate stories for `ProductName` to latest Storybook. Also add props documentation and add named export. * Migrate `TotalsFooterItem` to TypeScript and latest Storybook * Add a `LooselyMustHave` utility type. * Export `allSettings` so that they can be manipulated in stories and tests * Implement a way to easily define and reuse Storybook controls Implement a currency control for a common use-case of selecting currencies. It currently implements EUR and USD as they have different properties. * Migrate `TotalsDiscount` to TypeScript and implement stories * Migrate `TotalsCoupon` to TypeScript and fix stories * Change Coupon name within Storybook * Nicer handling of removal of a coupon from Storybook It now dynamically calculates the discount from the actual coupons.
This commit is contained in:
parent
91c2ca2143
commit
703051b1bc
|
@ -7,7 +7,6 @@ import Button from '@woocommerce/base-components/button';
|
|||
import { ValidatedTextInput } from '@woocommerce/base-components/text-input';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import LoadingMask from '@woocommerce/base-components/loading-mask';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import {
|
||||
ValidationInputError,
|
||||
|
@ -20,12 +19,31 @@ import { Panel } from '@woocommerce/blocks-checkout';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
const TotalsCoupon = ( {
|
||||
export interface TotalsCouponProps {
|
||||
/**
|
||||
* Instance id of the input
|
||||
*/
|
||||
instanceId: string;
|
||||
/**
|
||||
* Whether the component is in a loading state
|
||||
*/
|
||||
isLoading?: boolean;
|
||||
/**
|
||||
* Whether the component's parent panel will begin in an open state
|
||||
*/
|
||||
initialOpen?: boolean;
|
||||
/**
|
||||
* Submit handler
|
||||
*/
|
||||
onSubmit?: ( couponValue: string ) => void;
|
||||
}
|
||||
|
||||
export const TotalsCoupon = ( {
|
||||
instanceId,
|
||||
isLoading = false,
|
||||
initialOpen = false,
|
||||
onSubmit = () => {},
|
||||
} ) => {
|
||||
onSubmit = () => void 0,
|
||||
}: TotalsCouponProps ): JSX.Element => {
|
||||
const [ couponValue, setCouponValue ] = useState( '' );
|
||||
const currentIsLoading = useRef( false );
|
||||
const { getValidationError, getValidationErrorId } = useValidationContext();
|
||||
|
@ -94,7 +112,9 @@ const TotalsCoupon = ( {
|
|||
className="wc-block-components-totals-coupon__button"
|
||||
disabled={ isLoading || ! couponValue }
|
||||
showSpinner={ isLoading }
|
||||
onClick={ ( e ) => {
|
||||
onClick={ (
|
||||
e: React.MouseEvent< HTMLElement, 'click' >
|
||||
) => {
|
||||
e.preventDefault();
|
||||
onSubmit( couponValue );
|
||||
} }
|
||||
|
@ -113,9 +133,4 @@ const TotalsCoupon = ( {
|
|||
);
|
||||
};
|
||||
|
||||
TotalsCoupon.propTypes = {
|
||||
onSubmit: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withInstanceId( TotalsCoupon );
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { text, boolean } from '@storybook/addon-knobs';
|
||||
import {
|
||||
useValidationContext,
|
||||
ValidationContextProvider,
|
||||
} from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TotalsCoupon from '../';
|
||||
|
||||
export default {
|
||||
title:
|
||||
'WooCommerce Blocks/@base-components/cart-checkout/totals/TotalsCoupon',
|
||||
component: TotalsCoupon,
|
||||
};
|
||||
|
||||
const StoryComponent = ( { validCoupon, isLoading, invalidCouponText } ) => {
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
const onSubmit = ( coupon ) => {
|
||||
if ( coupon !== validCoupon ) {
|
||||
setValidationErrors( { coupon: invalidCouponText } );
|
||||
}
|
||||
};
|
||||
return <TotalsCoupon isLoading={ isLoading } onSubmit={ onSubmit } />;
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const validCoupon = text( 'A valid coupon code', 'validcoupon' );
|
||||
const invalidCouponText = text(
|
||||
'Error message for invalid code',
|
||||
'Invalid coupon code.'
|
||||
);
|
||||
const isLoading = boolean( 'Toggle isLoading state', false );
|
||||
return (
|
||||
<ValidationContextProvider>
|
||||
<StoryComponent
|
||||
validCoupon={ validCoupon }
|
||||
isLoading={ isLoading }
|
||||
invalidCouponText={ invalidCouponText }
|
||||
/>
|
||||
</ValidationContextProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useArgs } from '@storybook/client-api';
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import {
|
||||
useValidationContext,
|
||||
ValidationContextProvider,
|
||||
} from '@woocommerce/base-context';
|
||||
import { INTERACTION_TIMEOUT } from '@woocommerce/storybook-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TotalsCoupon, TotalsCouponProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/totals/Coupon',
|
||||
component: TotalsCoupon,
|
||||
args: {
|
||||
initialOpen: true,
|
||||
},
|
||||
} as Meta< TotalsCouponProps >;
|
||||
|
||||
const INVALID_COUPON_ERROR = {
|
||||
hidden: false,
|
||||
message: 'Invalid coupon code',
|
||||
};
|
||||
|
||||
const Template: Story< TotalsCouponProps > = ( args ) => {
|
||||
const [ {}, setArgs ] = useArgs();
|
||||
|
||||
const onSubmit = ( code: string ) => {
|
||||
args.onSubmit?.( code );
|
||||
setArgs( { isLoading: true } );
|
||||
|
||||
setTimeout(
|
||||
() => setArgs( { isLoading: false } ),
|
||||
INTERACTION_TIMEOUT
|
||||
);
|
||||
};
|
||||
|
||||
return <TotalsCoupon { ...args } onSubmit={ onSubmit } />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind( {} );
|
||||
Default.args = {};
|
||||
|
||||
export const LoadingState = Template.bind( {} );
|
||||
LoadingState.args = {
|
||||
isLoading: true,
|
||||
};
|
||||
|
||||
export const ErrorState: Story< TotalsCouponProps > = ( args ) => {
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
|
||||
setValidationErrors( { coupon: INVALID_COUPON_ERROR } );
|
||||
|
||||
return <TotalsCoupon { ...args } />;
|
||||
};
|
||||
|
||||
ErrorState.decorators = [
|
||||
( StoryComponent ) => {
|
||||
return (
|
||||
<ValidationContextProvider>
|
||||
<StoryComponent />
|
||||
</ValidationContextProvider>
|
||||
);
|
||||
},
|
||||
];
|
|
@ -4,18 +4,37 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import LoadingMask from '@woocommerce/base-components/loading-mask';
|
||||
import { RemovableChip } from '@woocommerce/base-components/chip';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
__experimentalApplyCheckoutFilter,
|
||||
TotalsItem,
|
||||
} from '@woocommerce/blocks-checkout';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
CartResponseCouponItemWithLabel,
|
||||
CartTotalsItem,
|
||||
Currency,
|
||||
} from '@woocommerce/types';
|
||||
import { LooselyMustHave } from '@woocommerce/type-defs/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
export interface TotalsDiscountProps {
|
||||
cartCoupons: LooselyMustHave<
|
||||
CartResponseCouponItemWithLabel,
|
||||
'code' | 'label' | 'totals'
|
||||
>[];
|
||||
currency: Currency;
|
||||
isRemovingCoupon: boolean;
|
||||
removeCoupon: ( couponCode: string ) => void;
|
||||
values: LooselyMustHave<
|
||||
CartTotalsItem,
|
||||
'total_discount' | 'total_discount_tax'
|
||||
>;
|
||||
}
|
||||
|
||||
const filteredCartCouponsFilterArg = {
|
||||
context: 'summary',
|
||||
};
|
||||
|
@ -26,7 +45,7 @@ const TotalsDiscount = ( {
|
|||
isRemovingCoupon,
|
||||
removeCoupon,
|
||||
values,
|
||||
} ) => {
|
||||
}: TotalsDiscountProps ): JSX.Element | null => {
|
||||
const {
|
||||
total_discount: totalDiscount,
|
||||
total_discount_tax: totalDiscountTax,
|
||||
|
@ -110,19 +129,4 @@ const TotalsDiscount = ( {
|
|||
);
|
||||
};
|
||||
|
||||
TotalsDiscount.propTypes = {
|
||||
cartCoupons: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
code: PropTypes.string.isRequired,
|
||||
} )
|
||||
),
|
||||
currency: PropTypes.object.isRequired,
|
||||
isRemovingCoupon: PropTypes.bool.isRequired,
|
||||
removeCoupon: PropTypes.func.isRequired,
|
||||
values: PropTypes.shape( {
|
||||
total_discount: PropTypes.string,
|
||||
total_discount_tax: PropTypes.string,
|
||||
} ).isRequired,
|
||||
};
|
||||
|
||||
export default TotalsDiscount;
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { text, boolean } from '@storybook/addon-knobs';
|
||||
import { currencyKnob } from '@woocommerce/knobs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TotalsDiscount from '../';
|
||||
|
||||
export default {
|
||||
title:
|
||||
'WooCommerce Blocks/@base-components/cart-checkout/totals/TotalsDiscount',
|
||||
component: TotalsDiscount,
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const cartCoupons = [ { code: 'COUPON' } ];
|
||||
const currency = currencyKnob();
|
||||
const isRemovingCoupon = boolean( 'Toggle isRemovingCoupon state', false );
|
||||
const totalDiscount = text( 'Total discount', '1000' );
|
||||
const totalDiscountTax = text( 'Total discount tax', '200' );
|
||||
|
||||
return (
|
||||
<TotalsDiscount
|
||||
cartCoupons={ cartCoupons }
|
||||
currency={ currency }
|
||||
isRemovingCoupon={ isRemovingCoupon }
|
||||
removeCoupon={ () => void null }
|
||||
values={ {
|
||||
total_discount: totalDiscount,
|
||||
total_discount_tax: totalDiscountTax,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useArgs } from '@storybook/client-api';
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import {
|
||||
currenciesAPIShape as currencies,
|
||||
currencyControl,
|
||||
INTERACTION_TIMEOUT,
|
||||
} from '@woocommerce/storybook-controls';
|
||||
import { LooselyMustHave } from '@woocommerce/type-defs/utils';
|
||||
import {
|
||||
CartResponseCouponItemWithLabel,
|
||||
CartTotalsItem,
|
||||
} from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Discount, { TotalsDiscountProps } from '..';
|
||||
|
||||
const EXAMPLE_COUPONS: CartResponseCouponItemWithLabel[] = [
|
||||
{
|
||||
code: 'AWSMSB',
|
||||
discount_type: '',
|
||||
label: 'Awesome Storybook coupon',
|
||||
totals: {
|
||||
...currencies.EUR,
|
||||
total_discount: '5000',
|
||||
total_discount_tax: '250',
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'STONKS',
|
||||
discount_type: '',
|
||||
label: 'Most valuable coupon',
|
||||
totals: {
|
||||
...currencies.EUR,
|
||||
total_discount: '10000',
|
||||
total_discount_tax: '1000',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function extractValuesFromCoupons(
|
||||
coupons: LooselyMustHave< CartResponseCouponItemWithLabel, 'totals' >[]
|
||||
) {
|
||||
return coupons.reduce(
|
||||
( acc, curr ) => {
|
||||
const totalDiscount =
|
||||
Number( acc.total_discount ) +
|
||||
Number( curr.totals.total_discount );
|
||||
const totalDiscountTax =
|
||||
Number( acc.total_discount_tax ) +
|
||||
Number( curr.totals.total_discount_tax );
|
||||
|
||||
return {
|
||||
total_discount: String( totalDiscount ),
|
||||
total_discount_tax: String( totalDiscountTax ),
|
||||
};
|
||||
},
|
||||
{ total_discount: '0', total_discount_tax: '0' } as LooselyMustHave<
|
||||
CartTotalsItem,
|
||||
'total_discount' | 'total_discount_tax'
|
||||
>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/totals/Discount',
|
||||
component: Discount,
|
||||
argTypes: {
|
||||
currency: currencyControl,
|
||||
removeCoupon: { action: 'Removing coupon with code' },
|
||||
},
|
||||
args: {
|
||||
cartCoupons: EXAMPLE_COUPONS,
|
||||
isRemovingCoupon: false,
|
||||
values: extractValuesFromCoupons( EXAMPLE_COUPONS ),
|
||||
},
|
||||
} as Meta< TotalsDiscountProps >;
|
||||
|
||||
const Template: Story< TotalsDiscountProps > = ( args ) => {
|
||||
const [ {}, setArgs ] = useArgs();
|
||||
|
||||
const removeCoupon = ( code: string ) => {
|
||||
args.removeCoupon( code );
|
||||
setArgs( { isRemovingCoupon: true } );
|
||||
|
||||
const cartCoupons = args.cartCoupons.filter(
|
||||
( coupon ) => coupon.code !== code
|
||||
);
|
||||
|
||||
const values = extractValuesFromCoupons( cartCoupons );
|
||||
|
||||
setTimeout(
|
||||
() => setArgs( { cartCoupons, values, isRemovingCoupon: false } ),
|
||||
INTERACTION_TIMEOUT
|
||||
);
|
||||
};
|
||||
|
||||
return <Discount { ...args } removeCoupon={ removeCoupon } />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind( {} );
|
||||
Default.args = {};
|
||||
|
||||
export const RemovingCoupon = Template.bind( {} );
|
||||
RemovingCoupon.args = {
|
||||
isRemovingCoupon: true,
|
||||
};
|
|
@ -4,23 +4,48 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
__experimentalApplyCheckoutFilter,
|
||||
TotalsItem,
|
||||
} from '@woocommerce/blocks-checkout';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { CartResponseTotals, Currency } from '@woocommerce/types';
|
||||
import { LooselyMustHave } from '@woocommerce/type-defs/utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const TotalsFooterItem = ( { currency, values } ) => {
|
||||
export interface TotalsFooterItemProps {
|
||||
/**
|
||||
* The currency object with which to display the item
|
||||
*/
|
||||
currency: Currency;
|
||||
/**
|
||||
* An object containing the total price and the total tax
|
||||
*
|
||||
* It accepts the entire `CartResponseTotals` to be passed, for
|
||||
* convenience, but will use only these two properties.
|
||||
*/
|
||||
values: LooselyMustHave< CartResponseTotals, 'total_price' | 'total_tax' >;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total at the bottom of the cart
|
||||
*
|
||||
* Can show how much of the total is in taxes if the settings
|
||||
* `taxesEnabled` and `displayCartPricesIncludingTax` are both
|
||||
* enabled.
|
||||
*/
|
||||
const TotalsFooterItem = ( {
|
||||
currency,
|
||||
values,
|
||||
}: TotalsFooterItemProps ): JSX.Element => {
|
||||
const SHOW_TAXES =
|
||||
getSetting( 'taxesEnabled', true ) &&
|
||||
getSetting( 'displayCartPricesIncludingTax', false );
|
||||
getSetting< boolean >( 'taxesEnabled', true ) &&
|
||||
getSetting< boolean >( 'displayCartPricesIncludingTax', false );
|
||||
|
||||
const { total_price: totalPrice, total_tax: totalTax } = values;
|
||||
|
||||
|
@ -69,12 +94,4 @@ const TotalsFooterItem = ( { currency, values } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
TotalsFooterItem.propTypes = {
|
||||
currency: PropTypes.object.isRequired,
|
||||
values: PropTypes.shape( {
|
||||
total_price: PropTypes.string,
|
||||
total_tax: PropTypes.string,
|
||||
} ).isRequired,
|
||||
};
|
||||
|
||||
export default TotalsFooterItem;
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { currencyKnob } from '@woocommerce/knobs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TotalsFooterItem from '../';
|
||||
|
||||
export default {
|
||||
title:
|
||||
'WooCommerce Blocks/@base-components/cart-checkout/totals/TotalsFooterItem',
|
||||
component: TotalsFooterItem,
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const currency = currencyKnob();
|
||||
const totalPrice = text( 'Total price', '1200' );
|
||||
const totalTax = text( 'Total tax', '200' );
|
||||
|
||||
return (
|
||||
<TotalsFooterItem
|
||||
currency={ currency }
|
||||
values={ {
|
||||
total_price: totalPrice,
|
||||
total_tax: totalTax,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { allSettings } from '@woocommerce/settings';
|
||||
import { Currency } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import FooterItem, { TotalsFooterItemProps } from '..';
|
||||
|
||||
const NZD: Currency = {
|
||||
code: 'nzd',
|
||||
symbol: '$',
|
||||
thousandSeparator: ' ',
|
||||
decimalSeparator: '.',
|
||||
minorUnit: 2,
|
||||
prefix: '$',
|
||||
suffix: '',
|
||||
};
|
||||
|
||||
export default {
|
||||
title:
|
||||
'WooCommerce Blocks/@base-components/cart-checkout/totals/FooterItem',
|
||||
component: FooterItem,
|
||||
args: {
|
||||
currency: NZD,
|
||||
values: { total_price: '2500', total_tax: '550' },
|
||||
},
|
||||
} as Meta< TotalsFooterItemProps >;
|
||||
|
||||
const Template: Story< TotalsFooterItemProps > = ( args ) => (
|
||||
<FooterItem { ...args } />
|
||||
);
|
||||
|
||||
export const Default = Template.bind( {} );
|
||||
Default.decorators = [
|
||||
( StoryComponent ) => {
|
||||
allSettings.displayCartPricesIncludingTax = false;
|
||||
|
||||
return <StoryComponent />;
|
||||
},
|
||||
];
|
||||
|
||||
export const IncludingTaxes = Template.bind( {} );
|
||||
IncludingTaxes.decorators = [
|
||||
( StoryComponent ) => {
|
||||
allSettings.displayCartPricesIncludingTax = true;
|
||||
|
||||
return <StoryComponent />;
|
||||
},
|
||||
];
|
|
@ -1,79 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TotalsFooterItem Does not show the "including %s of tax" line if tax is 0 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="wc-block-components-totals-item wc-block-components-totals-footer-item"
|
||||
>
|
||||
<span
|
||||
class="wc-block-components-totals-item__label"
|
||||
>
|
||||
Total
|
||||
</span>
|
||||
<span
|
||||
class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-totals-item__value"
|
||||
>
|
||||
£85.00
|
||||
</span>
|
||||
<div
|
||||
class="wc-block-components-totals-item__description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TotalsFooterItem Does not show the "including %s of tax" line if tax is disabled 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="wc-block-components-totals-item wc-block-components-totals-footer-item"
|
||||
>
|
||||
<span
|
||||
class="wc-block-components-totals-item__label"
|
||||
>
|
||||
Total
|
||||
</span>
|
||||
<span
|
||||
class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-totals-item__value"
|
||||
>
|
||||
£85.00
|
||||
</span>
|
||||
<div
|
||||
class="wc-block-components-totals-item__description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TotalsFooterItem Shows the "including %s of tax" line if tax is greater than 0 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="wc-block-components-totals-item wc-block-components-totals-footer-item"
|
||||
>
|
||||
<span
|
||||
class="wc-block-components-totals-item__label"
|
||||
>
|
||||
Total
|
||||
</span>
|
||||
<span
|
||||
class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-totals-item__value"
|
||||
>
|
||||
£85.00
|
||||
</span>
|
||||
<div
|
||||
class="wc-block-components-totals-item__description"
|
||||
>
|
||||
<p
|
||||
class="wc-block-components-totals-footer-item-tax"
|
||||
>
|
||||
Including
|
||||
<span
|
||||
class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-totals-footer-item-tax-value"
|
||||
>
|
||||
£1.00
|
||||
</span>
|
||||
in taxes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -2,12 +2,12 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { allSettings } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TotalsFooterItem from '../index';
|
||||
import { allSettings } from '../../../../../../settings/shared/settings-init';
|
||||
|
||||
describe( 'TotalsFooterItem', () => {
|
||||
beforeEach( () => {
|
|
@ -10,11 +10,26 @@ import { AnchorHTMLAttributes, HTMLAttributes } from 'react';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
interface ProductNameProps extends AnchorHTMLAttributes< HTMLAnchorElement > {
|
||||
export interface ProductNameProps
|
||||
extends AnchorHTMLAttributes< HTMLAnchorElement > {
|
||||
/**
|
||||
* If `true` renders a `span` element instead of a link
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* The product name
|
||||
*
|
||||
* Note: can be an HTML string
|
||||
*/
|
||||
name: string;
|
||||
permalink?: string;
|
||||
/**
|
||||
* Click handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
/**
|
||||
* Link for the product
|
||||
*/
|
||||
permalink?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,7 +37,7 @@ interface ProductNameProps extends AnchorHTMLAttributes< HTMLAnchorElement > {
|
|||
*
|
||||
* The store API runs titles through `wp_kses_post()` which removes dangerous HTML tags, so using it inside `dangerouslySetInnerHTML` is considered safe.
|
||||
*/
|
||||
export default ( {
|
||||
export const ProductName = ( {
|
||||
className = '',
|
||||
disabled = false,
|
||||
name,
|
||||
|
@ -59,3 +74,5 @@ export default ( {
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductName;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductName from '../';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/ProductName',
|
||||
component: ProductName,
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const disabled = boolean( 'disabled', false );
|
||||
|
||||
return (
|
||||
<ProductName
|
||||
disabled={ disabled }
|
||||
name={ 'Test product' }
|
||||
permalink={ '/' }
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductName, { ProductNameProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/ProductName',
|
||||
component: ProductName,
|
||||
args: {
|
||||
name: 'Test product',
|
||||
permalink: '#',
|
||||
},
|
||||
} as Meta< ProductNameProps >;
|
||||
|
||||
const Template: Story< ProductNameProps > = ( args ) => (
|
||||
<ProductName { ...args } />
|
||||
);
|
||||
|
||||
export const Default = Template.bind( {} );
|
||||
Default.args = {
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export const DisabledProduct = Template.bind( {} );
|
||||
DisabledProduct.args = {
|
||||
disabled: true,
|
||||
};
|
|
@ -4,6 +4,7 @@
|
|||
"include": [
|
||||
".",
|
||||
"../../types",
|
||||
"../../../../storybook",
|
||||
"../../../../packages/prices",
|
||||
"../../../../packages/checkout",
|
||||
"../context",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { useArgs } from '@storybook/client-api';
|
||||
import { INTERACTION_TIMEOUT } from '@woocommerce/storybook-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -21,7 +22,10 @@ const Template: Story< ErrorPlaceholderProps > = ( args ) => {
|
|||
? () => {
|
||||
setArgs( { isLoading: true } );
|
||||
|
||||
setTimeout( () => setArgs( { isLoading: false } ), 3500 );
|
||||
setTimeout(
|
||||
() => setArgs( { isLoading: false } ),
|
||||
INTERACTION_TIMEOUT
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
|
|
@ -6,3 +6,4 @@ import '../../filters/exclude-draft-status-from-analytics';
|
|||
export * from './default-constants';
|
||||
export * from './default-address-fields';
|
||||
export * from './utils';
|
||||
export { allSettings } from './settings-init';
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Allow for the entire object to be passed, with only some properties
|
||||
* required.
|
||||
*/
|
||||
export type LooselyMustHave< T, K extends keyof T > = Partial< T > &
|
||||
Pick< T, K >;
|
|
@ -12928,7 +12928,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
|
@ -21164,7 +21163,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
|
@ -21244,8 +21242,7 @@
|
|||
"map-obj": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
|
||||
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="
|
||||
},
|
||||
"map-or-similar": {
|
||||
"version": "1.5.0",
|
||||
|
@ -22502,7 +22499,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lower-case": "^2.0.2",
|
||||
"tslib": "^2.0.3"
|
||||
|
@ -28179,6 +28175,32 @@
|
|||
"sentence-case": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"snakecase-keys": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-5.1.2.tgz",
|
||||
"integrity": "sha512-fvtDQZqPBqYb0dEY97TGuOMbN2NJ05Tj4MaoKwjTKkmjcG6mrd58JYGr23UWZRi6Aqv49Fk4HtjTIStOQenaug==",
|
||||
"requires": {
|
||||
"map-obj": "^4.1.0",
|
||||
"snake-case": "^3.0.4",
|
||||
"type-fest": "^2.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
|
||||
"requires": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.8.0.tgz",
|
||||
"integrity": "sha512-O+V9pAshf9C6loGaH0idwsmugI2LxVNR7DtS40gVo2EXZVYFgz9OuNtOhgHLdHdapOEWNdvz9Ob/eeuaWwwlxA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
"html-react-parser": "0.14.3",
|
||||
"react-number-format": "4.4.3",
|
||||
"reakit": "1.3.11",
|
||||
"snakecase-keys": "^5.1.2",
|
||||
"trim-html": "0.1.9",
|
||||
"use-debounce": "7.0.1",
|
||||
"wordpress-components": "npm:@wordpress/components@14.2.0"
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Currency, CurrencyResponse } from '@woocommerce/types';
|
||||
import snakecaseKeys from 'snakecase-keys';
|
||||
|
||||
export const currencies: Record< string, Currency > = {
|
||||
EUR: {
|
||||
code: 'EUR',
|
||||
symbol: '€',
|
||||
thousandSeparator: '.',
|
||||
decimalSeparator: ',',
|
||||
minorUnit: 2,
|
||||
prefix: '',
|
||||
suffix: '€',
|
||||
},
|
||||
USD: {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
thousandSeparator: ',',
|
||||
decimalSeparator: '.',
|
||||
minorUnit: 2,
|
||||
prefix: '$',
|
||||
suffix: '',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const currenciesAPIShape: Record<
|
||||
string,
|
||||
CurrencyResponse
|
||||
> = Object.fromEntries(
|
||||
Object.entries( currencies ).map( ( [ key, value ] ) => [
|
||||
key,
|
||||
snakecaseKeys( value ),
|
||||
] )
|
||||
);
|
||||
|
||||
export const currencyControl = {
|
||||
control: 'select',
|
||||
defaultValue: currencies.USD,
|
||||
mapping: currencies,
|
||||
options: Object.keys( currencies ),
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export * from './currency';
|
||||
|
||||
export const INTERACTION_TIMEOUT = 1500;
|
|
@ -55,7 +55,8 @@
|
|||
"@woocommerce/shared-context": [ "assets/js/shared/context" ],
|
||||
"@woocommerce/shared-hocs": [ "assets/js/shared/hocs" ],
|
||||
"@woocommerce/type-defs/*": [ "assets/js/types/type-defs/*" ],
|
||||
"@woocommerce/types": [ "assets/js/types" ]
|
||||
"@woocommerce/types": [ "assets/js/types" ],
|
||||
"@woocommerce/storybook-controls": [ "storybook/custom-controls" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"include": [
|
||||
"./assets/js/**/*",
|
||||
"./assets/js/blocks/cart-checkout/checkout/inner-blocks/**/block.json",
|
||||
"./assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/**/block.json"
|
||||
"./assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/**/block.json",
|
||||
"./storybook/**/*"
|
||||
],
|
||||
"exclude": [ "./assets/js/data" ],
|
||||
"references": [
|
||||
|
|
Loading…
Reference in New Issue