Storybook migration of `CountryInput` component (https://github.com/woocommerce/woocommerce-blocks/pull/5312)

* Enable `exactOptionalPropertyTypes` in TS config

This option will create more strict types out of optional properties.
Read more: https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/#exact-optional-property-types

* Extract `ComboBoxProps` as own interface

This way we can export it to use it somewhere else. In this specific case,
I'll need to use it as a base to extend the `CountryInputProps`.

Also fixed some types to be optional, as the component requires.

* Fix stories and props for `CountryInput` (https://github.com/woocommerce/woocommerce-blocks/pull/5252)
This commit is contained in:
M. L. Giannotta 2021-12-07 16:07:21 +01:00 committed by GitHub
parent de068d2fc3
commit 77c237d6a2
8 changed files with 130 additions and 85 deletions

View File

@ -22,6 +22,20 @@ export interface ComboboxControlOption {
value: string; value: string;
} }
export interface ComboboxProps {
autoComplete?: string;
className?: string;
errorId: string | null;
errorMessage?: string;
id: string;
instanceId?: string;
label: string;
onChange: ( filterValue: string ) => void;
options: ComboboxControlOption[];
required?: boolean;
value: string;
}
/** /**
* Wrapper for the WordPress ComboboxControl which supports validation. * Wrapper for the WordPress ComboboxControl which supports validation.
*/ */
@ -40,19 +54,7 @@ const Combobox = ( {
errorId: incomingErrorId, errorId: incomingErrorId,
instanceId = '0', instanceId = '0',
autoComplete = 'off', autoComplete = 'off',
}: { }: ComboboxProps ): JSX.Element => {
id: string;
className: string;
label: string;
onChange: ( filterValue: string ) => void;
options: ComboboxControlOption[];
value: string;
required: boolean;
errorMessage: string;
errorId: string;
instanceId: string;
autoComplete: string;
} ): JSX.Element => {
const { const {
getValidationError, getValidationError,
setValidationErrors, setValidationErrors,

View File

@ -1,15 +1,27 @@
export interface CountryInputProps { /**
* Internal dependencies
*/
import { ComboboxProps } from '../combobox';
import { countries } from './stories/countries-filler';
export interface CountryInputProps extends Omit< ComboboxProps, 'options' > {
/**
* Classes to assign to the wrapper component of the input
*/
className?: string; className?: string;
label: string; /**
id: string; * Whether input elements can by default have their values automatically completed by the browser.
*
* This value gets assigned to both the wrapper `Combobox` and the wrapped input element.
*/
autoComplete?: string; autoComplete?: string;
value: string;
onChange: ( value: string ) => void;
required?: boolean;
errorMessage?: string;
errorId: null | 'shipping-missing-country';
} }
export type CountryInputWithCountriesProps = CountryInputProps & { export interface CountryInputWithCountriesProps extends CountryInputProps {
countries: Record< string, string >; /**
}; * List of countries to allow in the selection
*
* Object shape should be: `{ [Alpha-2 Country Code]: 'Full country name' }`
*/
countries: Partial< typeof countries >;
}

View File

@ -13,7 +13,7 @@ import Combobox from '../combobox';
import './style.scss'; import './style.scss';
import type { CountryInputWithCountriesProps } from './CountryInputProps'; import type { CountryInputWithCountriesProps } from './CountryInputProps';
const CountryInput = ( { export const CountryInput = ( {
className, className,
countries, countries,
id, id,
@ -30,10 +30,12 @@ const CountryInput = ( {
}: CountryInputWithCountriesProps ): JSX.Element => { }: CountryInputWithCountriesProps ): JSX.Element => {
const options = useMemo( const options = useMemo(
() => () =>
Object.keys( countries ).map( ( key ) => ( { Object.entries( countries ).map(
value: key, ( [ countryCode, countryName ] ) => ( {
label: decodeEntities( countries[ key ] ), value: countryCode,
} ) ), label: decodeEntities( countryName ),
} )
),
[ countries ] [ countries ]
); );

View File

@ -1,3 +1,7 @@
export { default as CountryInput } from './country-input'; export type {
CountryInputProps,
CountryInputWithCountriesProps,
} from './CountryInputProps';
export { CountryInput } from './country-input';
export { default as BillingCountryInput } from './billing-country-input'; export { default as BillingCountryInput } from './billing-country-input';
export { default as ShippingCountryInput } from './shipping-country-input'; export { default as ShippingCountryInput } from './shipping-country-input';

View File

@ -248,4 +248,4 @@ export const countries = {
YE: 'Yemen', YE: 'Yemen',
ZM: 'Zambia', ZM: 'Zambia',
ZW: 'Zimbabwe', ZW: 'Zimbabwe',
}; } as const;

View File

@ -1,53 +0,0 @@
/**
* External dependencies
*/
import { text } from '@storybook/addon-knobs';
import { useState, useEffect } from '@wordpress/element';
import {
ValidationContextProvider,
useValidationContext,
} from '@woocommerce/base-context';
/**
* Internal dependencies
*/
import { CountryInput } from '../';
import { countries as exampleCountries } from './countries-filler';
export default {
title: 'WooCommerce Blocks/@base-components/CountryInput',
component: CountryInput,
};
const StoryComponent = ( { label, errorMessage } ) => {
const [ selectedCountry, selectCountry ] = useState();
const {
setValidationErrors,
clearValidationError,
} = useValidationContext();
useEffect( () => {
setValidationErrors( { country: errorMessage } );
}, [ errorMessage, setValidationErrors ] );
const updateCountry = ( country ) => {
clearValidationError( 'country' );
selectCountry( country );
};
return (
<CountryInput
countries={ exampleCountries }
label={ label }
value={ selectedCountry }
onChange={ updateCountry }
/>
);
};
export const Default = () => {
const label = text( 'Input Label', 'Countries:' );
const errorMessage = text( 'Error Message', '' );
return (
<ValidationContextProvider>
<StoryComponent label={ label } errorMessage={ errorMessage } />
</ValidationContextProvider>
);
};

View File

@ -0,0 +1,77 @@
/**
* External dependencies
*/
import { Story, Meta } from '@storybook/react';
import {
useValidationContext,
ValidationContextProvider,
} from '@woocommerce/base-context';
import { useState, useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { CountryInput, CountryInputWithCountriesProps } from '..';
import { countries } from './countries-filler';
type CountryCode = keyof typeof countries;
export default {
title: 'WooCommerce Blocks/@base-components/CountryInput',
component: CountryInput,
args: {
countries,
autoComplete: 'off',
id: 'country',
label: 'Countries: ',
required: false,
},
argTypes: {
countries: { control: false },
options: { table: { disable: true } },
value: { control: false },
},
decorators: [
( StoryComponent ) => (
<ValidationContextProvider>
<StoryComponent />
</ValidationContextProvider>
),
],
} as Meta< CountryInputWithCountriesProps >;
const Template: Story< CountryInputWithCountriesProps > = ( args ) => {
const [ selectedCountry, selectCountry ] = useState< CountryCode | '' >(
''
);
const {
clearValidationError,
showValidationError,
} = useValidationContext();
useEffect( () => {
showValidationError( 'country' );
}, [ showValidationError ] );
function updateCountry( country: CountryCode ) {
clearValidationError( 'country' );
selectCountry( country );
}
return (
<CountryInput
{ ...args }
onChange={ ( value ) => updateCountry( value as CountryCode ) }
value={ selectedCountry }
/>
);
};
export const Default = Template.bind( {} );
export const WithError = Template.bind( {} );
WithError.args = {
errorId: 'country',
errorMessage: 'Please select a country',
required: true,
};

View File

@ -7,6 +7,7 @@
"module": "esnext", "module": "esnext",
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"esModuleInterop": true, "esModuleInterop": true,
"exactOptionalPropertyTypes": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"jsx": "preserve", "jsx": "preserve",
"target": "esnext", "target": "esnext",
@ -52,7 +53,7 @@
"@woocommerce/knobs": [ "storybook/knobs" ], "@woocommerce/knobs": [ "storybook/knobs" ],
"@woocommerce/settings": [ "assets/js/settings/shared" ], "@woocommerce/settings": [ "assets/js/settings/shared" ],
"@woocommerce/shared-context": [ "assets/js/shared/context" ], "@woocommerce/shared-context": [ "assets/js/shared/context" ],
"@woocommerce/shared-hocs": [ "assets/js/shared/hocs" ], "@woocommerce/shared-hocs": [ "assets/js/shared/hocs" ],
"@woocommerce/type-defs/*": [ "assets/js/types/type-defs/*" ], "@woocommerce/type-defs/*": [ "assets/js/types/type-defs/*" ],
"@woocommerce/types": [ "assets/js/types" ] "@woocommerce/types": [ "assets/js/types" ]
} }