Skip to content

Commit

Permalink
Storybook and TS migration of some cart checkout components (woocomme…
Browse files Browse the repository at this point in the history
…rce#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.
  • Loading branch information
sunyatasattva authored and jonny-bull committed Dec 16, 2021
1 parent 3449783 commit de8931f
Show file tree
Hide file tree
Showing 25 changed files with 460 additions and 279 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -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 );
} }
Expand All @@ -113,9 +133,4 @@ const TotalsCoupon = ( {
);
};

TotalsCoupon.propTypes = {
onSubmit: PropTypes.func,
isLoading: PropTypes.bool,
};

export default withInstanceId( TotalsCoupon );

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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>
);
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
Expand All @@ -26,7 +45,7 @@ const TotalsDiscount = ( {
isRemovingCoupon,
removeCoupon,
values,
} ) => {
}: TotalsDiscountProps ): JSX.Element | null => {
const {
total_discount: totalDiscount,
total_discount_tax: totalDiscountTax,
Expand Down Expand Up @@ -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;

This file was deleted.

Loading

0 comments on commit de8931f

Please sign in to comment.