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;
}
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.
*/
@ -40,19 +54,7 @@ const Combobox = ( {
errorId: incomingErrorId,
instanceId = '0',
autoComplete = 'off',
}: {
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 => {
}: ComboboxProps ): JSX.Element => {
const {
getValidationError,
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;
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;
value: string;
onChange: ( value: string ) => void;
required?: boolean;
errorMessage?: string;
errorId: null | 'shipping-missing-country';
}
export type CountryInputWithCountriesProps = CountryInputProps & {
countries: Record< string, string >;
};
export interface CountryInputWithCountriesProps extends CountryInputProps {
/**
* 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 type { CountryInputWithCountriesProps } from './CountryInputProps';
const CountryInput = ( {
export const CountryInput = ( {
className,
countries,
id,
@ -30,10 +30,12 @@ const CountryInput = ( {
}: CountryInputWithCountriesProps ): JSX.Element => {
const options = useMemo(
() =>
Object.keys( countries ).map( ( key ) => ( {
value: key,
label: decodeEntities( countries[ key ] ),
} ) ),
Object.entries( countries ).map(
( [ countryCode, countryName ] ) => ( {
value: countryCode,
label: decodeEntities( countryName ),
} )
),
[ 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 ShippingCountryInput } from './shipping-country-input';

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",
"emitDeclarationOnly": true,
"esModuleInterop": true,
"exactOptionalPropertyTypes": true,
"resolveJsonModule": true,
"jsx": "preserve",
"target": "esnext",
@ -52,7 +53,7 @@
"@woocommerce/knobs": [ "storybook/knobs" ],
"@woocommerce/settings": [ "assets/js/settings/shared" ],
"@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/types": [ "assets/js/types" ]
}