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                              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";
import { textStyles } from "@repo/ui/themes/typography";
 
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"}
            accessibilityState={{ disabled }}
            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: {
    ...textStyles.content.medium,
    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: {
    ...textStyles.content.regular,
    flex: 1,
    fontSize: theme.metrics.textSize[16],
    borderWidth: theme.metrics.borderWidth[0],
    color: theme.colors.text.default,
    backgroundColor: "transparent",
  },
  errorMessage: {
    ...textStyles.content.regular,
    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],
  },
}));