All files / packages/ui/src/components/Input index.tsx

100% Statements 7/7
100% Branches 16/16
100% Functions 2/2
100% Lines 6/6

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115                                      2x         7x 7x   7x                                                                         2x   7x                                                                                                
import React, { forwardRef } from "react";
import {
  Text,
  TextInput as RNTextInput,
  TextInputProps,
  View,
} from "react-native";
 
import { makeStyles } from "@repo/ui/themes/makeStyles";
import { useTheme } from "@repo/ui/themes/ThemeContext";
 
type InputProps = TextInputProps & {
  leftIcon?: React.ReactNode;
  label?: string;
  errorMessage?: string;
  disabled?: boolean;
  testID?: string;
};
 
export const Input = forwardRef<RNTextInput, InputProps>(
  (
    { leftIcon, label, errorMessage, value, disabled = false, testID, ...rest },
    ref,
  ) => {
    const { theme } = useTheme();
    const styles = useStyles();
 
    return (
      <View style={styles.container}>
        {label && <Text style={styles.label}>{label}</Text>}
 
        <View
          style={[styles.inputWrapper, disabled && styles.inputWrapperDisabled]}
        >
          {leftIcon && (
            <View style={[styles.leftIcon, disabled && styles.iconDisabled]}>
              {leftIcon}
            </View>
          )}
 
          <RNTextInput
            ref={ref}
            style={[styles.input, disabled && styles.inputDisabled]}
            placeholderTextColor={theme.colors.text.tertiary}
            value={value}
            editable={!disabled}
            accessibilityLabel={
              rest.accessibilityLabel || label || "Input field"
            }
            testID={testID}
            {...rest}
          />
        </View>
 
        {errorMessage && (
          <Text style={styles.errorMessage} accessibilityLiveRegion="polite">
            {errorMessage}
          </Text>
        )}
      </View>
    );
  },
);
 
Input.displayName = "Input";
 
const useStyles = makeStyles((theme) => ({
  container: {
    marginVertical: theme.metrics.spacing[2],
  },
  label: {
    marginBottom: theme.metrics.spacing[1],
    fontSize: theme.metrics.textSize[14],
    fontWeight: theme.metrics.fontWeight.medium,
    color: theme.colors.text.info,
  },
  inputWrapper: {
    position: "relative",
    flexDirection: "row",
    alignItems: "center",
    borderWidth: theme.metrics.borderWidth.default,
    borderColor: theme.colors.border.tertiary,
    borderRadius: theme.metrics.borderRadius[1],
    height: theme.metrics.spacing[12.5],
    paddingHorizontal: theme.metrics.spacing[4],
    backgroundColor: theme.colors.background.default,
  },
  leftIcon: {
    marginRight: theme.metrics.spacing[2],
  },
  input: {
    flex: 1,
    fontSize: theme.metrics.textSize[16],
    borderWidth: theme.metrics.borderWidth[0],
    color: theme.colors.text.default,
    backgroundColor: "transparent",
  },
  errorMessage: {
    position: "absolute",
    bottom: -18,
    fontSize: theme.metrics.textSize[12],
    color: theme.colors.text.error,
  },
  inputWrapperDisabled: {
    backgroundColor: theme.colors.background.disabled,
    borderColor: theme.colors.border.disabled,
  },
  inputDisabled: {
    color: theme.colors.text.tertiary,
  },
  iconDisabled: {
    opacity: theme.metrics.opacity[40],
  },
}));