eletrotupi / tcc/ frontend/components/ui/Input.tsx0cf854c934d9213cafffde1bada0ac913e4716ab
5.0 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) {
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
  };
49

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

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

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

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

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

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

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

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

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

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

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

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

182
const styles = StyleSheet.create({
183
  container: {
184
    marginBottom: 16,
185
  },
186
  label: {
187
    fontSize: 14,
188
    fontWeight: '500',
189
    marginBottom: 6,
190
  },
191
  inputContainer: {
192
    position: 'relative',
193
  },
194
  input: {
195
    height: 48,
196
    paddingHorizontal: 12,
197
    paddingRight: 44,
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
});