eletrotupi / tcc / frontend/components/ui/Input.tsx master
5.1 KB Raw
import React, { useState } from 'react';
import {
  TextInput,
  TextInputProps,
  StyleSheet,
  View,
  Text,
  TouchableOpacity,
  Platform,
} from 'react-native';
import { useThemeColor } from '@/hooks/use-theme-color';
import { Ionicons } from '@expo/vector-icons';

type InputVariant = 'default' | 'ghost' | 'darkGhost';

interface BaseInputProps {
  label?: string;
  error?: string;
  variant?: InputVariant;
}

function useInputStyles(variant: InputVariant, error?: string, hasPasswordToggle?: boolean) {
  let backgroundColor = null;

  const textColor = useThemeColor({}, 'text');
  const defaultBackgroundColor = useThemeColor({}, 'inputBackgroundColor');
  const backgroundDarkGhostColor = useThemeColor({}, 'inputBackgroundDarkGhostColor');
  const borderColor = useThemeColor({}, 'inputBorderColor');
  const placeholderColor = useThemeColor({}, 'inputPlaceholderColor');
  const errorColor = '#ef4444';

  const isGhost = variant === 'ghost';
  const isDarkGhost = variant === 'darkGhost';

  if (isDarkGhost) {
    backgroundColor = backgroundDarkGhostColor;
  } else if (isGhost) {
    backgroundColor = 'transparent';
  } else {
    backgroundColor = defaultBackgroundColor;
  }

  const inputStyle = {
    color: textColor,
    backgroundColor: backgroundColor,
    borderColor: error ? errorColor : borderColor,
    borderWidth: isGhost || isDarkGhost ? 0 : 1,
    paddingRight: hasPasswordToggle ? 44 : 0
  };

  return { textColor, placeholderColor, errorColor, inputStyle };
}

interface InputProps extends TextInputProps, BaseInputProps {
  type?: 'text' | 'email' | 'password';
  showPasswordToggle?: boolean;
}

export const Input: React.FC<InputProps> = ({
  label,
  error,
  variant = 'default',
  type = 'text',
  showPasswordToggle = false,
  style,
  ...props
}) => {
  const [showPassword, setShowPassword] = useState(false);
  const {
    textColor,
    placeholderColor,
    errorColor,
    inputStyle
  } = useInputStyles(variant, error, showPasswordToggle);

  const keyboardType = type === 'email' ? 'email-address' : 'default';
  const secureTextEntry = type === 'password' && !showPassword;

  return (
    <View style={styles.container}>
      {label && (
        <Label text={label} />
      )}
      <View style={styles.inputContainer}>
        <TextInput
          key={`${type}-${showPassword}`}
          style={[styles.input, inputStyle, style]}
          placeholderTextColor={placeholderColor}
          keyboardType={keyboardType}
          secureTextEntry={secureTextEntry}
          autoCapitalize={type === 'email' ? 'none' : props.autoCapitalize}
          autoCorrect={type === 'email' || type === 'password' ? false : props.autoCorrect}
          {...props}
        />
        {type === 'password' && showPasswordToggle && (
          <TouchableOpacity
            style={styles.passwordToggle}
            onPress={() => setShowPassword((prev) => !prev)}
            hitSlop={8}
          >
            <Ionicons
              name={showPassword ? 'eye-off' : 'eye'}
              size={22}
              color={placeholderColor}
            />
          </TouchableOpacity>
        )}
      </View>
      {error && (
        <Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
      )}
    </View>
  );
};

interface TextAreaProps extends TextInputProps, BaseInputProps {
  minRows?: number;
  maxRows?: number;
}

export const TextArea: React.FC<TextAreaProps> = ({
  label,
  error,
  variant = 'default',
  minRows = 3,
  maxRows,
  style,
  ...props
}) => {
  const {
    textColor,
    placeholderColor,
    errorColor,
    inputStyle
  } = useInputStyles(variant, error);

  const minHeight = minRows * 24;
  const maxHeight = maxRows ? maxRows * 24 : undefined;

  return (
    <View style={styles.container}>
      {label && (
        <Label text={label} />
      )}
      <TextInput
        style={[
          styles.textArea,
          inputStyle,
          { minHeight, maxHeight },
          style,
        ]}
        placeholderTextColor={placeholderColor}
        multiline
        textAlignVertical="top"
        scrollEnabled={!!maxRows}
        {...props}
      />
      {error && (
        <Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
      )}
    </View>
  );
};

interface LabelProps extends React.ComponentProps<typeof Text> {
  text: string;
}

export const Label: React.FC<LabelProps> = ({
  text,
  style,
  ...props
}) => {
  const textColor = useThemeColor({}, 'text');

  return (
    <Text style={[styles.label, { color: textColor }, style]} {...props}>
      {text}
    </Text>
  );
};

const styles = StyleSheet.create({
  container: {
    marginBottom: 16,
  },
  label: {
    fontSize: 14,
    fontWeight: '500',
    marginBottom: 6,
  },
  inputContainer: {
    position: 'relative',
  },
  input: {
    height: 48,
    paddingHorizontal: 12,
    borderWidth: 1,
    borderRadius: 8,
    fontSize: 16,
  },
  textArea: {
    paddingHorizontal: 12,
    paddingVertical: 12,
    borderWidth: 1,
    borderRadius: 8,
    fontSize: 16,
  },
  passwordToggle: {
    position: 'absolute',
    right: 12,
    top: 13,
  },
  errorText: {
    fontSize: 12,
    marginTop: 4,
  },
});