diff --git a/assets/js/base/components/text-input/index.js b/assets/js/base/components/text-input/index.ts
similarity index 100%
rename from assets/js/base/components/text-input/index.js
rename to assets/js/base/components/text-input/index.ts
diff --git a/assets/js/base/components/text-input/text-input.js b/assets/js/base/components/text-input/text-input.tsx
similarity index 72%
rename from assets/js/base/components/text-input/text-input.js
rename to assets/js/base/components/text-input/text-input.tsx
index 414777d83f1..44f2782a4b1 100644
--- a/assets/js/base/components/text-input/text-input.js
+++ b/assets/js/base/components/text-input/text-input.tsx
@@ -1,8 +1,7 @@
/**
* External dependencies
*/
-import { forwardRef } from 'react';
-import PropTypes from 'prop-types';
+import { forwardRef, InputHTMLAttributes } from 'react';
import classnames from 'classnames';
import { useState } from '@wordpress/element';
import { Label } from '@woocommerce/blocks-checkout';
@@ -12,7 +11,24 @@ import { Label } from '@woocommerce/blocks-checkout';
*/
import './style.scss';
-const TextInput = forwardRef(
+interface TextInputProps
+ extends Omit<
+ InputHTMLAttributes< HTMLInputElement >,
+ 'onChange' | 'onBlur'
+ > {
+ id: string;
+ ariaLabel?: string;
+ label?: string;
+ ariaDescribedBy?: string;
+ screenReaderLabel?: string;
+ help?: string;
+ feedback?: boolean | JSX.Element;
+ autoComplete?: string;
+ onChange: ( newValue: string ) => void;
+ onBlur?: ( newValue: string ) => void;
+}
+
+const TextInput = forwardRef< HTMLInputElement, TextInputProps >(
(
{
className,
@@ -29,7 +45,9 @@ const TextInput = forwardRef(
value = '',
onChange,
required = false,
- onBlur = () => {},
+ onBlur = () => {
+ /* Do nothing */
+ },
feedback,
},
ref
@@ -57,8 +75,8 @@ const TextInput = forwardRef(
onChange( event.target.value );
} }
onFocus={ () => setIsActive( true ) }
- onBlur={ () => {
- onBlur();
+ onBlur={ ( event ) => {
+ onBlur( event.target.value );
setIsActive( false );
} }
aria-label={ ariaLabel || label }
@@ -93,19 +111,4 @@ const TextInput = forwardRef(
}
);
-TextInput.propTypes = {
- id: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- value: PropTypes.string,
- ariaLabel: PropTypes.string,
- ariaDescribedBy: PropTypes.string,
- label: PropTypes.string,
- screenReaderLabel: PropTypes.string,
- disabled: PropTypes.bool,
- help: PropTypes.string,
- autoCapitalize: PropTypes.string,
- autoComplete: PropTypes.string,
- required: PropTypes.bool,
-};
-
export default TextInput;
diff --git a/assets/js/base/components/text-input/validated-text-input.js b/assets/js/base/components/text-input/validated-text-input.tsx
similarity index 62%
rename from assets/js/base/components/text-input/validated-text-input.js
rename to assets/js/base/components/text-input/validated-text-input.tsx
index 67bfe802e61..92448f3d077 100644
--- a/assets/js/base/components/text-input/validated-text-input.js
+++ b/assets/js/base/components/text-input/validated-text-input.tsx
@@ -3,7 +3,6 @@
*/
import { __ } from '@wordpress/i18n';
import { useCallback, useRef, useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
ValidationInputError,
@@ -17,6 +16,29 @@ import { withInstanceId } from '@woocommerce/base-hocs/with-instance-id';
import TextInput from './text-input';
import './style.scss';
+interface ValidatedTextInputPropsWithId {
+ instanceId?: string;
+ id: string;
+}
+
+interface ValidatedTextInputPropsWithInstanceId {
+ instanceId: string;
+ id?: string;
+}
+
+type ValidatedTextInputProps = (
+ | ValidatedTextInputPropsWithId
+ | ValidatedTextInputPropsWithInstanceId
+ ) & {
+ className?: string;
+ ariaDescribedBy?: string;
+ errorId?: string;
+ validateOnMount?: boolean;
+ focusOnMount?: boolean;
+ showError?: boolean;
+ onChange: ( newValue: string ) => void;
+};
+
const ValidatedTextInput = ( {
className,
instanceId,
@@ -28,9 +50,9 @@ const ValidatedTextInput = ( {
onChange,
showError = true,
...rest
-} ) => {
+}: ValidatedTextInputProps ) => {
const [ isPristine, setIsPristine ] = useState( true );
- const inputRef = useRef();
+ const inputRef = useRef< HTMLInputElement >( null );
const {
getValidationError,
hideValidationError,
@@ -39,8 +61,9 @@ const ValidatedTextInput = ( {
getValidationErrorId,
} = useValidationContext();
- const textInputId = id || 'textinput-' + instanceId;
- errorId = errorId || textInputId;
+ const textInputId =
+ typeof id !== 'undefined' ? id : 'textinput-' + instanceId;
+ const errorIdString = errorId !== undefined ? errorId : textInputId;
const validateInput = useCallback(
( errorsHidden = true ) => {
@@ -52,10 +75,10 @@ const ValidatedTextInput = ( {
inputObject.value = inputObject.value.trim();
const inputIsValid = inputObject.checkValidity();
if ( inputIsValid ) {
- clearValidationError( errorId );
+ clearValidationError( errorIdString );
} else {
setValidationErrors( {
- [ errorId ]: {
+ [ errorIdString ]: {
message:
inputObject.validationMessage ||
__(
@@ -67,13 +90,13 @@ const ValidatedTextInput = ( {
} );
}
},
- [ clearValidationError, errorId, setValidationErrors ]
+ [ clearValidationError, errorIdString, setValidationErrors ]
);
useEffect( () => {
if ( isPristine ) {
if ( focusOnMount ) {
- inputRef.current.focus();
+ inputRef.current?.focus();
}
setIsPristine( false );
}
@@ -91,15 +114,19 @@ const ValidatedTextInput = ( {
// Remove validation errors when unmounted.
useEffect( () => {
return () => {
- clearValidationError( errorId );
+ clearValidationError( errorIdString );
};
- }, [ clearValidationError, errorId ] );
+ }, [ clearValidationError, errorIdString ] );
- const errorMessage = getValidationError( errorId ) || {};
+ // @todo - When useValidationContext is converted to TypeScript, remove this cast and use the correct type.
+ const errorMessage = ( getValidationError( errorIdString ) || {} ) as {
+ message?: string;
+ hidden?: boolean;
+ };
const hasError = errorMessage.message && ! errorMessage.hidden;
const describedBy =
- showError && hasError && getValidationErrorId( errorId )
- ? getValidationErrorId( errorId )
+ showError && hasError && getValidationErrorId( errorIdString )
+ ? getValidationErrorId( errorIdString )
: ariaDescribedBy;
return (
@@ -112,11 +139,13 @@ const ValidatedTextInput = ( {
validateInput( false );
} }
feedback={
- showError &&
+ showError && (
+
+ )
}
ref={ inputRef }
onChange={ ( val ) => {
- hideValidationError( errorId );
+ hideValidationError( errorIdString );
onChange( val );
} }
ariaDescribedBy={ describedBy }
@@ -125,15 +154,4 @@ const ValidatedTextInput = ( {
);
};
-ValidatedTextInput.propTypes = {
- onChange: PropTypes.func.isRequired,
- id: PropTypes.string,
- value: PropTypes.string,
- ariaDescribedBy: PropTypes.string,
- errorId: PropTypes.string,
- validateOnMount: PropTypes.bool,
- focusOnMount: PropTypes.bool,
- showError: PropTypes.bool,
-};
-
export default withInstanceId( ValidatedTextInput );
diff --git a/assets/js/base/context/index.js b/assets/js/base/context/index.ts
similarity index 100%
rename from assets/js/base/context/index.js
rename to assets/js/base/context/index.ts
diff --git a/packages/checkout/label/index.tsx b/packages/checkout/label/index.tsx
index 582a96a5d5f..5a7dcf5d8df 100644
--- a/packages/checkout/label/index.tsx
+++ b/packages/checkout/label/index.tsx
@@ -3,13 +3,13 @@
*/
import { Fragment } from '@wordpress/element';
import classNames from 'classnames';
-import type { ReactElement, HTMLAttributes } from 'react';
+import type { ReactElement, HTMLProps } from 'react';
-interface LabelProps {
+interface LabelProps extends HTMLProps< HTMLElement > {
label?: string;
screenReaderLabel?: string;
wrapperElement?: string;
- wrapperProps?: HTMLAttributes< HTMLElement >;
+ wrapperProps?: HTMLProps< HTMLElement >;
}
/**