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" ]
}