frontend/components: wrap a button component and input

Stoled from an old project hihi
Pedro Lucas Porcellis porcellis@eletrotupi.com 2 months ago ae44a253c2bb4d5eb651b570bfad2eed02f26360
Parents: fb4c3b9
4 file(s) changed
  • frontend/components/ui/Button.tsx +153 -0
  • frontend/components/ui/Input.tsx +115 -0
  • frontend/components/ui/buttons.tsx +0 -6
  • frontend/components/ui/index.ts +1 -0
frontend/components/ui/Button.tsx
@@ -0,0 +1,153 @@
1 + import React from 'react';
2 + import {
3 + TouchableOpacity,
4 + Text,
5 + StyleSheet,
6 + ActivityIndicator,
7 + TouchableOpacityProps,
8 + ViewStyle,
9 + TextStyle,
10 + } from 'react-native';
11 + import { useThemeColor } from '@/hooks/use-theme-color';
12 +
13 + interface ButtonProps extends TouchableOpacityProps {
14 + title: string;
15 + variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
16 + size?: 'small' | 'medium' | 'large';
17 + loading?: boolean;
18 + disabled?: boolean;
19 + style?: ViewStyle;
20 + textStyle?: TextStyle;
21 + }
22 +
23 + export const Button: React.FC<ButtonProps> = ({
24 + title,
25 + variant = 'primary',
26 + size = 'medium',
27 + loading = false,
28 + disabled = false,
29 + style,
30 + textStyle,
31 + onPress,
32 + ...props
33 + }) => {
34 + const primaryColor = useThemeColor({ light: '#0a7ea4', dark: '#4fb3d1' }, 'tint');
35 + const textColor = useThemeColor({}, 'text');
36 + const backgroundColor = useThemeColor({}, 'background');
37 + const borderColor = useThemeColor({ light: '#e0e0e0', dark: '#404040' }, 'border');
38 +
39 + const getBackgroundColor = () => {
40 + if (disabled) return useThemeColor({ light: '#e5e5e5', dark: '#404040' }, 'background');
41 + switch (variant) {
42 + case 'primary':
43 + return primaryColor;
44 + case 'secondary':
45 + return useThemeColor({ light: '#f3f4f6', dark: '#374151' }, 'background');
46 + case 'outline':
47 + case 'ghost':
48 + return 'transparent';
49 + default:
50 + return primaryColor;
51 + }
52 + };
53 +
54 + const getTextColor = () => {
55 + if (disabled) return useThemeColor({ light: '#9ca3af', dark: '#6b7280' }, 'text');
56 + switch (variant) {
57 + case 'primary':
58 + return '#fff';
59 + case 'secondary':
60 + case 'outline':
61 + case 'ghost':
62 + return textColor;
63 + default:
64 + return '#fff';
65 + }
66 + };
67 +
68 + const getBorderWidth = () => {
69 + return variant === 'outline' ? 1 : 0;
70 + };
71 +
72 + const getBorderColor = () => {
73 + if (variant === 'outline') {
74 + return disabled ? borderColor : primaryColor;
75 + }
76 + return 'transparent';
77 + };
78 +
79 + const getHeight = () => {
80 + switch (size) {
81 + case 'small':
82 + return 36;
83 + case 'medium':
84 + return 44;
85 + case 'large':
86 + return 52;
87 + default:
88 + return 44;
89 + }
90 + };
91 +
92 + const getFontSize = () => {
93 + switch (size) {
94 + case 'small':
95 + return 14;
96 + case 'medium':
97 + return 16;
98 + case 'large':
99 + return 18;
100 + default:
101 + return 16;
102 + }
103 + };
104 +
105 + return (
106 + <TouchableOpacity
107 + style={[
108 + styles.button,
109 + {
110 + backgroundColor: getBackgroundColor(),
111 + borderWidth: getBorderWidth(),
112 + borderColor: getBorderColor(),
113 + height: getHeight(),
114 + opacity: disabled || loading ? 0.6 : 1,
115 + },
116 + style,
117 + ]}
118 + disabled={disabled || loading}
119 + onPress={onPress}
120 + {...props}
121 + >
122 + {loading ? (
123 + <ActivityIndicator color={getTextColor()} />
124 + ) : (
125 + <Text
126 + style={[
127 + styles.text,
128 + {
129 + color: getTextColor(),
130 + fontSize: getFontSize(),
131 + },
132 + textStyle,
133 + ]}
134 + >
135 + {title}
136 + </Text>
137 + )}
138 + </TouchableOpacity>
139 + );
140 + };
141 +
142 + const styles = StyleSheet.create({
143 + button: {
144 + borderRadius: 8,
145 + paddingHorizontal: 20,
146 + alignItems: 'center',
147 + justifyContent: 'center',
148 + marginVertical: 8,
149 + },
150 + text: {
151 + fontWeight: '600',
152 + },
153 + });
frontend/components/ui/Input.tsx
@@ -0,0 +1,116 @@
1 + import React 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 + interface InputProps extends TextInputProps {
15 + label?: string;
16 + error?: string;
17 + type?: 'text' | 'email' | 'password';
18 + showPasswordToggle?: boolean;
19 + }
20 +
21 + export const Input: React.FC<InputProps> = ({
22 + label,
23 + error,
24 + type = 'text',
25 + showPasswordToggle = false,
26 + style,
27 + ...props
28 + }) => {
29 + const [showPassword, setShowPassword] = React.useState(false);
30 + const textColor = useThemeColor({}, 'text');
31 + const backgroundColor = useThemeColor({ light: '#f5f5f5', dark: '#2a2a2a' }, 'background');
32 + const borderColor = useThemeColor({ light: '#e0e0e0', dark: '#404040' }, 'border');
33 + const errorColor = '#ef4444';
34 + const placeholderColor = useThemeColor({ light: '#9ca3af', dark: '#6b7280' }, 'placeholder');
35 +
36 + const keyboardType = type === 'email' ? 'email-address' : 'default';
37 + const secureTextEntry = type === 'password' && !showPassword;
38 +
39 + return (
40 + <View style={styles.container}>
41 + {label && (
42 + <Text style={[styles.label, { color: textColor }]}>{label}</Text>
43 + )}
44 + <View style={styles.inputContainer}>
45 + <TextInput
46 + style={[
47 + styles.input,
48 + {
49 + color: textColor,
50 + backgroundColor,
51 + borderColor: error ? errorColor : borderColor,
52 + },
53 + style,
54 + ]}
55 + placeholderTextColor={placeholderColor}
56 + keyboardType={keyboardType}
57 + secureTextEntry={secureTextEntry}
58 + autoCapitalize={type === 'email' ? 'none' : props.autoCapitalize}
59 + autoCorrect={type === 'email' || type === 'password' ? false : props.autoCorrect}
60 + {...props}
61 + />
62 + {type === 'password' && showPasswordToggle && (
63 + <TouchableOpacity
64 + style={styles.passwordToggle}
65 + onPress={() => setShowPassword(!showPassword)}
66 + >
67 + <Ionicons
68 + name={showPassword ? 'eye-off' : 'eye'}
69 + size={22}
70 + color={placeholderColor}
71 + />
72 + </TouchableOpacity>
73 + )}
74 + </View>
75 + {error && (
76 + <Text style={[styles.error, { color: errorColor }]}>{error}</Text>
77 + )}
78 + </View>
79 + );
80 + };
81 +
82 + const styles = StyleSheet.create({
83 + container: {
84 + marginBottom: 16,
85 + },
86 + label: {
87 + fontSize: 14,
88 + fontWeight: '500',
89 + marginBottom: 6,
90 + },
91 + inputContainer: {
92 + position: 'relative',
93 + },
94 + input: {
95 + height: 48,
96 + paddingHorizontal: 12,
97 + paddingRight: 44,
98 + borderWidth: 1,
99 + borderRadius: 8,
100 + fontSize: 16,
101 + ...Platform.select({
102 + web: {
103 + outlineStyle: 'none',
104 + },
105 + }),
106 + },
107 + passwordToggle: {
108 + position: 'absolute',
109 + right: 12,
110 + top: 13,
111 + },
112 + error: {
113 + fontSize: 12,
114 + marginTop: 4,
115 + },
116 + });
@@ -1,6 +0,0 @@
1 - const PrimaryButton = () => (
2 - )
3 -
4 - export {
5 - PrimaryButton
6 - }
frontend/components/ui/index.ts
@@ -0,0 +1,2 @@
1 + export { Button } from './Button';
2 + export { Input } from './Input';