Skip to content

Commit

Permalink
Merge pull request #31356 from kubabutkiewicz/ts-migration/TextInput/…
Browse files Browse the repository at this point in the history
…component

[TS migration] Migrate 'TextInput' component to TypeScript
  • Loading branch information
Beamanator authored Dec 30, 2023
2 parents 89ee303 + 2632af4 commit 035f468
Show file tree
Hide file tree
Showing 14 changed files with 520 additions and 348 deletions.
4 changes: 2 additions & 2 deletions src/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {ForwardedRef, forwardRef, KeyboardEvent as ReactKeyboardEvent} from 'react';
import React, {type ForwardedRef, forwardRef, type MouseEventHandler, type KeyboardEvent as ReactKeyboardEvent} from 'react';
import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
Expand Down Expand Up @@ -29,7 +29,7 @@ type CheckboxProps = Partial<ChildrenProps> & {
containerStyle?: StyleProp<ViewStyle>;

/** Callback that is called when mousedown is triggered. */
onMouseDown?: () => void;
onMouseDown?: MouseEventHandler;

/** The size of the checkbox container */
containerSize?: number;
Expand Down
6 changes: 4 additions & 2 deletions src/components/RNTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, {ForwardedRef} from 'react';
import React, {Component, ForwardedRef} from 'react';
// eslint-disable-next-line no-restricted-imports
import {TextInput, TextInputProps} from 'react-native';
import Animated, {AnimatedProps} from 'react-native-reanimated';
import useTheme from '@hooks/useTheme';

type AnimatedTextInputRef = Component<AnimatedProps<TextInputProps>>;
// Convert the underlying TextInput into an Animated component so that we can take an animated ref and pass it to a worklet
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function RNTextInputWithRef(props: TextInputProps, ref: ForwardedRef<React.Component<AnimatedProps<TextInputProps>>>) {
const theme = useTheme();

Expand All @@ -31,3 +31,5 @@ function RNTextInputWithRef(props: TextInputProps, ref: ForwardedRef<React.Compo
RNTextInputWithRef.displayName = 'RNTextInputWithRef';

export default React.forwardRef(RNTextInputWithRef);

export type {AnimatedTextInputRef};

Large diffs are not rendered by default.

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions src/components/TextInput/BaseTextInput/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import type {Component, ForwardedRef} from 'react';
import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native';
import type {AnimatedProps} from 'react-native-reanimated';
import type {SrcProps} from '@components/Icon';

Check failure on line 5 in src/components/TextInput/BaseTextInput/types.ts

View workflow job for this annotation

GitHub Actions / typecheck / typecheck

Module '"@components/Icon"' has no exported member 'SrcProps'. Did you mean to use 'import SrcProps from "@components/Icon"' instead?
import type {MaybePhraseKey} from '@libs/Localize';

type CustomBaseTextInputProps = {
/** Input label */
label?: string;

/** Name attribute for the input */
name?: string;

/** Input value */
value?: string;

/** Default value - used for non controlled inputs */
defaultValue?: string;

/** Input value placeholder */
placeholder?: string;

/** Error text to display */
errorText?: MaybePhraseKey;

/** Icon to display in right side of text input */
icon: ((props: SrcProps) => React.ReactNode) | null;

/** Customize the TextInput container */
textInputContainerStyles?: StyleProp<ViewStyle>;

/** Customize the main container */
containerStyles?: StyleProp<ViewStyle>;

/** input style */
inputStyle?: StyleProp<TextStyle>;

/** If present, this prop forces the label to remain in a position where it will not collide with input text */
forceActiveLabel?: boolean;

/** Should the input auto focus? */
autoFocus?: boolean;

/** Disable the virtual keyboard */
disableKeyboard?: boolean;

/**
* Autogrow input container length based on the entered text.
* Note: If you use this prop, the text input has to be controlled
* by a value prop.
*/
autoGrow?: boolean;

/**
* Autogrow input container height based on the entered text
* Note: If you use this prop, the text input has to be controlled
* by a value prop.
*/
autoGrowHeight?: boolean;

/** Hide the focus styles on TextInput */
hideFocusedState?: boolean;

/** Hint text to display below the TextInput */
hint?: string;

/** Prefix character */
prefixCharacter?: string;

/** Whether autoCorrect functionality should enable */
autoCorrect?: boolean;

/** Form props */
/** The ID used to uniquely identify the input in a Form */
inputID?: string;

/** Saves a draft of the input value when used in a form */
shouldSaveDraft?: boolean;

/** Callback to update the value on Form when input is used in the Form component. */
onInputChange?: (value: string) => void;

/** Whether we should wait before focusing the TextInput, useful when using transitions */
shouldDelayFocus?: boolean;

/** Indicate whether pressing Enter on multiline input is allowed to submit the form. */
submitOnEnter?: boolean;

/** Indicate whether input is multiline */
multiline?: boolean;

/** Set the default value to the input if there is a valid saved value */
shouldUseDefaultValue?: boolean;

/** Indicate whether or not the input should prevent swipe actions in tabs */
shouldInterceptSwipe?: boolean;

/** Should there be an error displayed */
hasError?: boolean;

/** On Press handler */
onPress?: (event: GestureResponderEvent | KeyboardEvent) => void;

/** Should loading state should be displayed */
isLoading?: boolean;

/** Type of autocomplete */
autoCompleteType?: string;
};

type BaseTextInputRef = ForwardedRef<HTMLFormElement | Component<AnimatedProps<TextInputProps>>>;

type BaseTextInputProps = CustomBaseTextInputProps & TextInputProps;

export type {CustomBaseTextInputProps, BaseTextInputRef, BaseTextInputProps};
25 changes: 0 additions & 25 deletions src/components/TextInput/TextInputLabel/TextInputLabelPropTypes.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React, {useState} from 'react';
import {Animated} from 'react-native';
import * as styleConst from '@components/TextInput/styleConst';
import useThemeStyles from '@hooks/useThemeStyles';
import * as TextInputLabelPropTypes from './TextInputLabelPropTypes';
import type TextInputLabelProps from './types';

function TextInputLabel(props) {
function TextInputLabel({isLabelActive, label, labelScale, labelTranslateY}: TextInputLabelProps) {
const styles = useThemeStyles();
const [width, setWidth] = useState(0);

Expand All @@ -17,29 +17,27 @@ function TextInputLabel(props) {
style={[
styles.textInputLabel,
styles.textInputLabelTransformation(
props.labelTranslateY,
props.labelScale.interpolate({
labelTranslateY,
labelScale.interpolate({
inputRange: [styleConst.ACTIVE_LABEL_SCALE, styleConst.INACTIVE_LABEL_SCALE],
outputRange: [-(width - width * styleConst.ACTIVE_LABEL_SCALE) / 2, 0],
}),
props.labelScale,
labelScale,
),
// If the label is active but the width is not ready yet, the above translateX value will be 0,
// making the label sits at the top center instead of the top left of the input. To solve it
// move the label by a percentage value with left style as translateX doesn't support percentage value.
width === 0 &&
props.isLabelActive && {
isLabelActive && {
left: `${-((1 - styleConst.ACTIVE_LABEL_SCALE) * 100) / 2}%`,
},
]}
>
{props.label}
{label}
</Animated.Text>
);
}

TextInputLabel.propTypes = TextInputLabelPropTypes.propTypes;
TextInputLabel.defaultProps = TextInputLabelPropTypes.defaultProps;
TextInputLabel.displayName = 'TextInputLabel';

export default TextInputLabel;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, {useEffect, useRef} from 'react';
import {Animated} from 'react-native';
import {Animated, Text} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import {defaultProps, propTypes} from './TextInputLabelPropTypes';
import type TextInputLabelProps from './types';

function TextInputLabel({for: inputId, label, labelTranslateY, labelScale}) {
function TextInputLabel({for: inputId = '', label, labelTranslateY, labelScale}: TextInputLabelProps) {
const styles = useThemeStyles();
const labelRef = useRef(null);
const labelRef = useRef<Text & HTMLFormElement>(null);

useEffect(() => {
if (!inputId || !labelRef.current) {
Expand All @@ -28,7 +28,5 @@ function TextInputLabel({for: inputId, label, labelTranslateY, labelScale}) {
}

TextInputLabel.displayName = 'TextInputLabel';
TextInputLabel.propTypes = propTypes;
TextInputLabel.defaultProps = defaultProps;

export default React.memo(TextInputLabel);
20 changes: 20 additions & 0 deletions src/components/TextInput/TextInputLabel/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Animated} from 'react-native';

type TextInputLabelProps = {
/** Label */
label: string;

/** Label vertical translate */
labelTranslateY: Animated.Value;

/** Label scale */
labelScale: Animated.Value;

/** Whether the label is currently active or not */
isLabelActive: boolean;

/** For attribute for label */
for?: string;
};

export default TextInputLabelProps;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, {forwardRef, useEffect} from 'react';
import {AppState, Keyboard} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import BaseTextInput from './BaseTextInput';
import * as baseTextInputPropTypes from './BaseTextInput/baseTextInputPropTypes';
import type {BaseTextInputProps, BaseTextInputRef} from './BaseTextInput/types';

const TextInput = forwardRef((props, ref) => {
function TextInput(props: BaseTextInputProps, ref: BaseTextInputRef) {
const styles = useThemeStyles();

useEffect(() => {
if (!props.disableKeyboard) {
return;
Expand All @@ -30,15 +31,13 @@ const TextInput = forwardRef((props, ref) => {
{...props}
// Setting autoCompleteType to new-password throws an error on Android/iOS, so fall back to password in that case
// eslint-disable-next-line react/jsx-props-no-multi-spaces
ref={ref}
autoCompleteType={props.autoCompleteType === 'new-password' ? 'password' : props.autoCompleteType}
innerRef={ref}
inputStyle={[styles.baseTextInput, ...props.inputStyle]}
inputStyle={[styles.baseTextInput, props.inputStyle]}
/>
);
});
}

TextInput.propTypes = baseTextInputPropTypes.propTypes;
TextInput.defaultProps = baseTextInputPropTypes.defaultProps;
TextInput.displayName = 'TextInput';

export default TextInput;
export default forwardRef(TextInput);
Loading

0 comments on commit 035f468

Please sign in to comment.