frontend/input: fix password mode toggle & add text area
Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago
75d4838f53bb2844da192906e757c4251c602b26 Browse filesParents
8b59f81Changed files
2
Additions
+101
Deletions
-19
frontend/components/ui/Input.tsx
1 1
import React, { useState } from 'react';
2 2
import {
3 3
TextInput,
4 4
TextInputProps,
5 5
StyleSheet,
6 6
View,
7 7
Text,
8 8
TouchableOpacity,
9 -
Platform
9 +
Platform,
10 10
} from 'react-native';
11 11
import { useThemeColor } from '@/hooks/use-theme-color';
12 12
import { Ionicons } from '@expo/vector-icons';
13 13
14 -
interface InputProps extends TextInputProps {
14 +
type InputVariant = 'default' | 'ghost' | 'darkGhost';
15 +
16 +
interface BaseInputProps {
15 17
label?: string;
16 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 {
17 54
type?: 'text' | 'email' | 'password';
18 55
showPasswordToggle?: boolean;
19 56
}
20 57
21 58
export const Input: React.FC<InputProps> = ({
22 59
label,
23 60
error,
61 +
variant = 'default',
24 62
type = 'text',
25 63
showPasswordToggle = false,
26 64
style,
27 65
...props
28 66
}) => {
29 67
const [showPassword, setShowPassword] = useState(false);
30 -
const textColor = useThemeColor({}, 'text');
31 -
const backgroundColor = useThemeColor({}, 'inputBackgroundColor');
32 -
const borderColor = useThemeColor({}, 'inputBorderColor');
33 -
const errorColor = '#ef4444';
34 -
const placeholderColor = useThemeColor({}, 'inputPlaceholderColor');
68 +
const { textColor, placeholderColor, errorColor, inputStyle } = useInputStyles(variant, error);
35 69
36 70
const keyboardType = type === 'email' ? 'email-address' : 'default';
37 71
const secureTextEntry = type === 'password' && !showPassword;
38 72
39 73
return (
41 75
{label && (
42 76
<Text style={[styles.label, { color: textColor }]}>{label}</Text>
43 77
)}
44 78
<View style={styles.inputContainer}>
45 79
<TextInput
46 -
style={[
47 -
styles.input,
48 -
{
49 -
color: textColor,
50 -
backgroundColor,
51 -
borderColor: error ? errorColor : borderColor,
52 -
},
53 -
style,
54 -
]}
80 +
key={`${type}-${showPassword}`}
81 +
style={[styles.input, inputStyle, style]}
55 82
placeholderTextColor={placeholderColor}
56 83
keyboardType={keyboardType}
57 84
secureTextEntry={secureTextEntry}
58 85
autoCapitalize={type === 'email' ? 'none' : props.autoCapitalize}
59 86
autoCorrect={type === 'email' || type === 'password' ? false : props.autoCorrect}
60 87
{...props}
61 88
/>
62 89
{type === 'password' && showPasswordToggle && (
63 90
<TouchableOpacity
64 91
style={styles.passwordToggle}
65 -
onPress={() => setShowPassword(!showPassword)}
92 +
onPress={() => setShowPassword((prev) => !prev)}
93 +
hitSlop={8}
66 94
>
67 95
<Ionicons
68 96
name={showPassword ? 'eye-off' : 'eye'}
69 97
size={22}
70 98
color={placeholderColor}
71 99
/>
72 100
</TouchableOpacity>
73 101
)}
74 102
</View>
75 103
{error && (
76 -
<Text style={[styles.error, { color: errorColor }]}>{error}</Text>
104 +
<Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
105 +
)}
106 +
</View>
107 +
);
108 +
};
109 +
110 +
interface TextAreaProps extends TextInputProps, BaseInputProps {
111 +
minRows?: number;
112 +
maxRows?: number;
113 +
}
114 +
115 +
export const TextArea: React.FC<TextAreaProps> = ({
116 +
label,
117 +
error,
118 +
variant = 'default',
119 +
minRows = 3,
120 +
maxRows,
121 +
style,
122 +
...props
123 +
}) => {
124 +
const { textColor, placeholderColor, errorColor, inputStyle } = useInputStyles(variant, error);
125 +
126 +
const minHeight = minRows * 24;
127 +
const maxHeight = maxRows ? maxRows * 24 : undefined;
128 +
129 +
return (
130 +
<View style={styles.container}>
131 +
{label && (
132 +
<Text style={[styles.label, { color: textColor }]}>{label}</Text>
133 +
)}
134 +
<TextInput
135 +
style={[
136 +
styles.textArea,
137 +
inputStyle,
138 +
{ minHeight, maxHeight },
139 +
style,
140 +
]}
141 +
placeholderTextColor={placeholderColor}
142 +
multiline
143 +
textAlignVertical="top"
144 +
scrollEnabled={!!maxRows}
145 +
{...props}
146 +
/>
147 +
{error && (
148 +
<Text style={[styles.errorText, { color: errorColor }]}>{error}</Text>
77 149
)}
78 150
</View>
79 151
);
80 152
};
81 153
97 169
paddingRight: 44,
98 170
borderWidth: 1,
99 171
borderRadius: 8,
100 172
fontSize: 16,
101 173
},
174 +
textArea: {
175 +
paddingHorizontal: 12,
176 +
paddingVertical: 12,
177 +
borderWidth: 1,
178 +
borderRadius: 8,
179 +
fontSize: 16,
180 +
},
102 181
passwordToggle: {
103 182
position: 'absolute',
104 183
right: 12,
105 184
top: 13,
106 185
},
107 -
error: {
186 +
errorText: {
108 187
fontSize: 12,
109 188
marginTop: 4,
110 189
},
111 190
});
frontend/constants/theme.ts
1 1
/**
2 2
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
3 3
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
4 4
*/
5 5
10 10
const PRIMARY_DARK = '#0f172a';
11 11
12 12
// Surface & Background
13 13
const BACKGROUND_LIGHT = '#f6f8f6';
14 14
const SURFACE_LIGHT = '#ffffff';
15 +
const BACKGROUND_DARK_GHOST = '#F8FAFC';
15 16
16 17
// Text Colors
17 18
const TEXT_PRIMARY = '#0f172a';
18 19
const TEXT_SECONDARY = '#64748b';
19 20
const TEXT_PLACEHOLDER = '#94A3B8';
40 41
text: TEXT_PRIMARY,
41 42
textSecondary: TEXT_SECONDARY,
42 43
background: BACKGROUND_LIGHT,
43 44
surface: SURFACE_LIGHT,
44 45
tint: NEON_GREEN,
46 +
backgroundDarkGhost: BACKGROUND_DARK_GHOST,
45 47
46 48
// Interactive States
47 49
icon: TEXT_SECONDARY,
48 50
tabIconDefault: TEXT_SECONDARY,
49 51
tabIconSelected: NEON_GREEN,
50 52
51 53
// Form Inputs
52 54
inputBackgroundColor: SURFACE_LIGHT,
55 +
inputBackgroundDarkGhostColor: BACKGROUND_DARK_GHOST,
53 56
inputBorderColor: BORDER_SUBTLE_GRAY,
54 57
inputFocusBorderColor: NEON_GREEN,
55 58
inputPlaceholderColor: TEXT_PLACEHOLDER,
56 59
57 60
// Cards & Containers
184 187
serif: 'serif',
185 188
rounded: 'Inter',
186 189
mono: 'monospace',
187 190
},
188 191
});