eletrotupi / tcc/ frontend/components/ui/Input.tsxd02f513430f49f5c5ff57d78eab853a7284e0cf2
5.1 KB | 218 lines Raw
1
import React, { useState } from 'react';
2
import {
3
  TextInput,
4
  TextInputProps,
5
  StyleSheet,
6
  View,
7
  Text,
8
  TouchableOpacity,
9
  Platform,
10
} from 'react-native';
11
import { useThemeColor } from '@/hooks/use-theme-color';
12
import { Ionicons } from '@expo/vector-icons';
13

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

16
interface BaseInputProps {
17
  label?: string;
18
  error?: string;
19
  variant?: InputVariant;
20
}
21

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

25
  const textColor = useThemeColor({}, 'text');
26
  const defaultBackgroundColor = useThemeColor({}, 'inputBackgroundColor');
27
  const backgroundDarkGhostColor = useThemeColor({}, 'inputBackgroundDarkGhostColor');
28
  const borderColor = useThemeColor({}, 'inputBorderColor');
29
  const placeholderColor = useThemeColor({}, 'inputPlaceholderColor');
30
  const errorColor = '#ef4444';
31

32
  const isGhost = variant === 'ghost';
33
  const isDarkGhost = variant === 'darkGhost';
34

35
  if (isDarkGhost) {
36
    backgroundColor = backgroundDarkGhostColor;
37
  } else if (isGhost) {
38
    backgroundColor = 'transparent';
39
  } else {
40
    backgroundColor = defaultBackgroundColor;
41
  }
42

43
  const inputStyle = {
44
    color: textColor,
45
    backgroundColor: backgroundColor,
46
    borderColor: error ? errorColor : borderColor,
47
    borderWidth: isGhost || isDarkGhost ? 0 : 1,
48
    paddingRight: hasPasswordToggle ? 44 : 0
49
  };
50

51
  return { textColor, placeholderColor, errorColor, inputStyle };
52
}
53

54
interface InputProps extends TextInputProps, BaseInputProps {
55
  type?: 'text' | 'email' | 'password';
56
  showPasswordToggle?: boolean;
57
}
58

59
export const Input: React.FC<InputProps> = ({
60
  label,
61
  error,
62
  variant = 'default',
63
  type = 'text',
64
  showPasswordToggle = false,
65
  style,
66
  ...props
67
}) => {
68
  const [showPassword, setShowPassword] = useState(false);
69
  const {
70
    textColor,
71
    placeholderColor,
72
    errorColor,
73
    inputStyle
74
  } = useInputStyles(variant, error, showPasswordToggle);
75

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

79
  return (
80
    <View style={styles.container}>
81
      {label && (
82
        <Label text={label} />
83
      )}
84
      <View style={styles.inputContainer}>
85
        <TextInput
86
          key={`${type}-${showPassword}`}
87
          style={[styles.input, inputStyle, style]}
88
          placeholderTextColor={placeholderColor}
89
          keyboardType={keyboardType}
90
          secureTextEntry={secureTextEntry}
91
          autoCapitalize={type === 'email' ? 'none' : props.autoCapitalize}
92
          autoCorrect={type === 'email' || type === 'password' ? false : props.autoCorrect}
93
          {...props}
94
        />
95
        {type === 'password' && showPasswordToggle && (
96
          <TouchableOpacity
97
            style={styles.passwordToggle}
98
            onPress={() => setShowPassword((prev) => !prev)}
99
            hitSlop={8}
100
          >
101
            <Ionicons
102
              name={showPassword ? 'eye-off' : 'eye'}
103
              size={22}
104
              color={placeholderColor}
105
            />
106
          </TouchableOpacity>
107
        )}
108
      </View>
109
      {error && (
110
        <Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
111
      )}
112
    </View>
113
  );
114
};
115

116
interface TextAreaProps extends TextInputProps, BaseInputProps {
117
  minRows?: number;
118
  maxRows?: number;
119
}
120

121
export const TextArea: React.FC<TextAreaProps> = ({
122
  label,
123
  error,
124
  variant = 'default',
125
  minRows = 3,
126
  maxRows,
127
  style,
128
  ...props
129
}) => {
130
  const {
131
    textColor,
132
    placeholderColor,
133
    errorColor,
134
    inputStyle
135
  } = useInputStyles(variant, error);
136

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

140
  return (
141
    <View style={styles.container}>
142
      {label && (
143
        <Label text={label} />
144
      )}
145
      <TextInput
146
        style={[
147
          styles.textArea,
148
          inputStyle,
149
          { minHeight, maxHeight },
150
          style,
151
        ]}
152
        placeholderTextColor={placeholderColor}
153
        multiline
154
        textAlignVertical="top"
155
        scrollEnabled={!!maxRows}
156
        {...props}
157
      />
158
      {error && (
159
        <Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
160
      )}
161
    </View>
162
  );
163
};
164

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

169
export const Label: React.FC<LabelProps> = ({
170
  text,
171
  style,
172
  ...props
173
}) => {
174
  const textColor = useThemeColor({}, 'text');
175

176
  return (
177
    <Text style={[styles.label, { color: textColor }, style]} {...props}>
178
      {text}
179
    </Text>
180
  );
181
};
182

183
const styles = StyleSheet.create({
184
  container: {
185
    marginBottom: 16,
186
  },
187
  label: {
188
    fontSize: 14,
189
    fontWeight: '500',
190
    marginBottom: 6,
191
  },
192
  inputContainer: {
193
    position: 'relative',
194
  },
195
  input: {
196
    height: 48,
197
    paddingHorizontal: 12,
198
    borderWidth: 1,
199
    borderRadius: 8,
200
    fontSize: 16,
201
  },
202
  textArea: {
203
    paddingHorizontal: 12,
204
    paddingVertical: 12,
205
    borderWidth: 1,
206
    borderRadius: 8,
207
    fontSize: 16,
208
  },
209
  passwordToggle: {
210
    position: 'absolute',
211
    right: 12,
212
    top: 13,
213
  },
214
  errorText: {
215
    fontSize: 12,
216
    marginTop: 4,
217
  },
218
});