diff --git a/plugins/woocommerce-blocks/assets/js/base/components/combobox/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/combobox/index.tsx index 68372da1920..c29df06c5c4 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/combobox/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/combobox/index.tsx @@ -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, diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/CountryInputProps.ts b/plugins/woocommerce-blocks/assets/js/base/components/country-input/CountryInputProps.ts index 7122a350618..81ad22c1130 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/country-input/CountryInputProps.ts +++ b/plugins/woocommerce-blocks/assets/js/base/components/country-input/CountryInputProps.ts @@ -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 >; +} diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/country-input.tsx b/plugins/woocommerce-blocks/assets/js/base/components/country-input/country-input.tsx index d8eba8a8e6e..7ef10861325 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/country-input/country-input.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/country-input/country-input.tsx @@ -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 ] ); diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/index.ts b/plugins/woocommerce-blocks/assets/js/base/components/country-input/index.ts index b91d8c76cd8..d98e0d51368 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/country-input/index.ts +++ b/plugins/woocommerce-blocks/assets/js/base/components/country-input/index.ts @@ -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'; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.js b/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.ts similarity index 99% rename from plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.js rename to plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.ts index a7091e5a205..2e9ce8ccbed 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/countries-filler.ts @@ -248,4 +248,4 @@ export const countries = { YE: 'Yemen', ZM: 'Zambia', ZW: 'Zimbabwe', -}; +} as const; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.js b/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.js deleted file mode 100644 index 1ca3a1c9a7a..00000000000 --- a/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.js +++ /dev/null @@ -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 ( - - ); -}; - -export const Default = () => { - const label = text( 'Input Label', 'Countries:' ); - const errorMessage = text( 'Error Message', '' ); - return ( - - - - ); -}; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.tsx new file mode 100644 index 00000000000..a065b72d2f4 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/components/country-input/stories/index.tsx @@ -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 ) => ( + + + + ), + ], +} 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 ( + 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, +}; diff --git a/plugins/woocommerce-blocks/tsconfig.base.json b/plugins/woocommerce-blocks/tsconfig.base.json index 04c373a04eb..b819937e0dd 100644 --- a/plugins/woocommerce-blocks/tsconfig.base.json +++ b/plugins/woocommerce-blocks/tsconfig.base.json @@ -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" ] }