DeFiCh/wallet

View on GitHub
mobile-app/app/components/WalletTextInputV2.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { forwardRef, useCallback, useState } from "react";
import {
  NativeSyntheticEvent,
  Platform,
  StyleProp,
  Text,
  TextInputFocusEventData,
  TextInputProps,
  TouchableOpacity,
  View,
  ViewStyle,
} from "react-native";
import { useBottomSheetInternal } from "@gorhom/bottom-sheet";
import {
  ThemedIcon,
  ThemedProps,
  ThemedSectionTitleV2,
  ThemedTextInputV2,
  ThemedViewV2,
} from "@components/themed";
import { getColor, tailwind } from "@tailwind";
import { MaterialIcons } from "@expo/vector-icons";

type WalletTextInputProps = React.PropsWithChildren<TextInputProps> &
  IWalletTextInputProps;
export type InputType = "default" | "numeric" | "number-pad";

interface IWalletTextInputProps {
  inputType: InputType;
  title?: string;
  titleTestID?: string;
  valid?: boolean;
  editable?: boolean;
  inlineText?: {
    type: "error" | "helper";
    text?: string | JSX.Element;
    style?: StyleProp<ViewStyle>;
  };
  displayClearButton?: boolean;
  onClearButtonPress?: () => void;
  displayFocusStyle?: boolean;
  containerStyle?: string;
  inputContainerStyle?: StyleProp<ViewStyle>;
  onBlur?: () => void;
  hasBottomSheet?: boolean;
  inputFooter?: React.ReactElement;
  displayTickIcon?: boolean;
  helperContainerStyle?: StyleProp<ViewStyle>;
  borderContainerStyle?: StyleProp<ViewStyle>;
  titleStyle?: StyleProp<ViewStyle>;
}

export const WalletTextInputV2 = forwardRef<any, WalletTextInputProps>(
  (props: WalletTextInputProps, ref: React.Ref<any>): JSX.Element => {
    const [isFocus, setIsFocus] = useState(false);
    const {
      title,
      titleTestID,
      valid = true,
      inlineText,
      editable = true,
      displayClearButton = false,
      onClearButtonPress,
      children,
      containerStyle,
      inputContainerStyle,
      onBlur,
      hasBottomSheet,
      inputFooter,
      displayTickIcon,
      helperContainerStyle,
      borderContainerStyle,
      titleStyle,
      ...otherProps
    } = props;

    const textInputComponents = {
      ios: TextInputIOS,
      default: TextInputDefault,
    };
    const TextInput =
      Platform.OS === "ios" && hasBottomSheet === true
        ? textInputComponents.ios
        : textInputComponents.default;

    const hasClearButton = (): boolean => {
      return displayClearButton && onClearButtonPress !== undefined;
    };

    return (
      <ThemedViewV2
        light={tailwind("bg-transparent")}
        dark={tailwind("bg-transparent")}
        style={tailwind(`${containerStyle ?? "w-full flex-col"}`)}
      >
        {title !== undefined && (
          <ThemedSectionTitleV2
            testID={titleTestID}
            text={title}
            customStyle={titleStyle}
          />
        )}
        <ThemedViewV2
          light={tailwind("bg-mono-light-v2-00 border-mono-light-v2-00", {
            "border-mono-light-v2-800": isFocus,
            "border-red-v2": !valid,
          })}
          dark={tailwind("bg-mono-dark-v2-00 border-mono-dark-v2-00", {
            "border-mono-dark-v2-800": isFocus,
            "border-red-v2": !valid,
          })}
          style={[
            tailwind("flex-col w-full border-0.5 rounded-lg-v2"),
            borderContainerStyle,
          ]}
        >
          <View
            style={[
              tailwind("flex-row items-center py-2 pl-5 pr-3 justify-between"),
              props.multiline === true && { minHeight: 54 },
              inputContainerStyle,
            ]}
          >
            <TextInput
              onFocus={() => setIsFocus(true)}
              onBlur={() => {
                if (onBlur !== undefined) {
                  onBlur();
                }
                setIsFocus(false);
              }}
              ref={ref}
              editable={editable}
              style={tailwind("font-normal-v2 flex-1 h-5")}
              selectionColor={getColor("brand-v2-500")}
              {...otherProps}
            />
            {displayTickIcon === true && (
              <MaterialIcons
                size={16}
                name="check-circle"
                iconType="MaterialIcons"
                style={tailwind("text-green-v2 ml-2")}
                testID={
                  props.testID !== undefined
                    ? `${props.testID}_check_button`
                    : undefined
                }
              />
            )}
            {hasClearButton() && (
              <ClearButtonV2
                onPress={onClearButtonPress}
                testID={
                  props.testID !== undefined
                    ? `${props.testID}_clear_button`
                    : undefined
                }
              />
            )}
            {children}
          </View>
          <View>{inputFooter}</View>
        </ThemedViewV2>
        <View style={props.helperContainerStyle}>
          {inlineText?.type === "error" && !valid && (
            <Text
              style={[
                tailwind("text-xs mt-2 text-red-v2 font-normal-v2"),
                inlineText.style,
              ]}
              testID={
                props.testID !== undefined ? `${props.testID}_error` : undefined
              }
            >
              {inlineText?.text}
            </Text>
          )}
          {inlineText?.type === "helper" &&
            typeof inlineText?.text === "string" && (
              <Text
                style={[
                  tailwind("text-xs text-gray-500 mt-2 font-normal-v2"),
                  inlineText.style,
                ]}
                testID={
                  props.testID !== undefined
                    ? `${props.testID}_error`
                    : undefined
                }
              >
                {inlineText?.text}
              </Text>
            )}

          {inlineText?.type === "helper" &&
            typeof inlineText?.text !== "string" &&
            inlineText?.text}
        </View>
      </ThemedViewV2>
    );
  },
);

export function ClearButtonV2(props: {
  onPress?: () => void;
  testID?: string;
  iconThemedProps?: ThemedProps;
}): JSX.Element {
  return (
    <TouchableOpacity
      testID={props.testID}
      style={tailwind("flex flex-row items-center bg-transparent ml-2")}
      onPress={props.onPress}
    >
      <ThemedIcon
        iconType="MaterialIcons"
        name="cancel"
        size={18}
        light={{ color: "#8E8E93" }}
        dark={{ color: "#8E8E93" }}
        {...props.iconThemedProps}
      />
    </TouchableOpacity>
  );
}

const TextInputDefault = forwardRef(
  (props: WalletTextInputProps, ref: React.Ref<any>) => {
    const { inputType, ...otherProps } = props;
    return (
      <ThemedTextInputV2 keyboardType={inputType} ref={ref} {...otherProps} />
    );
  },
);

const TextInputIOS = forwardRef(
  (props: WalletTextInputProps, ref: React.Ref<any>) => {
    const { inputType, onBlur, onFocus, ...otherProps } = props;
    const { shouldHandleKeyboardEvents } = useBottomSheetInternal();
    const handleOnFocus = useCallback(
      (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
        shouldHandleKeyboardEvents.value = true;

        if (onFocus !== undefined) {
          onFocus(e);
        }
      },
      [shouldHandleKeyboardEvents],
    );
    const handleOnBlur = useCallback(() => {
      shouldHandleKeyboardEvents.value = true;

      if (onBlur !== undefined) {
        onBlur();
      }
    }, [shouldHandleKeyboardEvents]);

    return (
      <ThemedTextInputV2
        keyboardType={inputType}
        ref={ref}
        onBlur={handleOnBlur}
        onFocus={handleOnFocus}
        {...otherProps}
      />
    );
  },
);