eletrotupi / tcc/ commit / 75d4838

frontend/input: fix password mode toggle & add text area

Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago 75d4838f53bb2844da192906e757c4251c602b26 Browse files
Parents 8b59f81
Changed 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
});