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:
parent
de068d2fc3
commit
77c237d6a2
|
@ -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,
|
||||||
|
|
|
@ -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 >;
|
||||||
|
}
|
||||||
|
|
|
@ -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 ]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -248,4 +248,4 @@ export const countries = {
|
||||||
YE: 'Yemen',
|
YE: 'Yemen',
|
||||||
ZM: 'Zambia',
|
ZM: 'Zambia',
|
||||||
ZW: 'Zimbabwe',
|
ZW: 'Zimbabwe',
|
||||||
};
|
} as const;
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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,
|
||||||
|
};
|
|
@ -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" ]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue