Files
hipmi-mobile/components/TextInput/TextInputCustom.tsx
bagasbanuna 98aaa126a1 QC: Inno dan Pak Jun
Fix:
- app/(application)/(user)/collaboration/create.tsx
- app/(application)/(user)/event/[id]/edit.tsx
- app/(application)/(user)/event/create.tsx
- app/(application)/(user)/profile/[id]/blocked-list.tsx
- app/(application)/(user)/profile/[id]/index.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- components/Button/FloatingButton.tsx
- components/TextArea/TextAreaCustom.tsx
- components/TextInput/TextInputCustom.tsx
- constants/color-palet.ts
- screens/Authentication/LoginView.tsx
- screens/Home/topFeatureSection.tsx
- screens/Portofolio/SocialMediaSection.tsx
- screens/Voting/BoxDetailHasilVotingSection.tsx
- styles/global-styles.ts

### No Issue
2025-12-01 17:43:20 +08:00

145 lines
3.7 KiB
TypeScript

import { PlaceholderColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import Ionicons from "@expo/vector-icons/Ionicons";
import React, { useState } from "react";
import {
TextInput as RNTextInput,
StyleProp,
Text,
TouchableOpacity,
View,
ViewStyle,
useColorScheme
} from "react-native";
type IconType = React.ReactNode | string;
type Props = {
iconLeft?: IconType;
iconRight?: IconType;
label?: string;
required?: boolean;
error?: string;
secureTextEntry?: boolean;
fontColor?: string;
disabled?: boolean;
borderRadius?: number;
style?: StyleProp<ViewStyle>;
maxLength?: number;
containerStyle?: StyleProp<ViewStyle>;
} & Omit<React.ComponentProps<typeof RNTextInput>, "style">;
const TextInputCustom = ({
iconLeft,
iconRight,
label,
required = false,
error: externalError = "",
secureTextEntry = false,
fontColor = "#000",
disabled = false,
borderRadius = 8,
style,
keyboardType,
onChangeText,
maxLength,
containerStyle,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [internalError, setInternalError] = useState("");
// Helper untuk render ikon
const renderIcon = (icon: IconType) => {
if (!icon) return null;
return typeof icon === "string" ? (
<Text style={GStyles.inputIconText}>{icon}</Text>
) : (
icon
);
};
// Validasi email jika keyboardType = email-address
const handleTextChange = (text: string) => {
if (keyboardType === "email-address") {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text);
if (!isValid) {
setInternalError("Masukkan email yang valid");
} else {
setInternalError("");
}
}
// Panggil onChangeText eksternal jika ada
if (onChangeText) {
onChangeText(text);
}
};
const colorScheme = useColorScheme();
const theme = PlaceholderColor[colorScheme || "light"];
return (
<View style={[GStyles.inputContainerArea, containerStyle]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
{iconLeft && (
<View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)}
<RNTextInput
style={[
GStyles.inputText,
{ color: fontColor },
disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled
]}
placeholderTextColor={theme.placeholder}
editable={!disabled}
secureTextEntry={secureTextEntry && !isPasswordVisible}
keyboardType={keyboardType}
onChangeText={handleTextChange}
maxLength={maxLength}
{...rest}
/>
{secureTextEntry && (
<TouchableOpacity
onPress={() => setIsPasswordVisible((prev) => !prev)}
style={GStyles.inputIcon}
>
<Ionicons
name={isPasswordVisible ? "eye-off" : "eye"}
size={20}
color="#888"
/>
</TouchableOpacity>
)}
{iconRight && (
<View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)}
</View>
{/* Prioritaskan error eksternal */}
{externalError || internalError ? (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
) : null}
</View>
);
};
export default TextInputCustom;