import { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { Image, LayoutAnimation, StyleSheet, TextInput, type TextStyle, TouchableOpacity, View, type ViewStyle, } from 'react-native'; import Icons from './Icons'; import { type CreditCardFormData, type CreditCardFormField, type CreditCardIssuer, useCreditCardForm, } from './useCreditCardForm'; interface Props { autoFocus?: boolean; style?: ViewStyle; inputStyle?: TextStyle; placeholderColor?: string; placeholders?: { number: string; expiry: string; cvc: string; }; onChange: (formData: CreditCardFormData) => void; onFocusField?: (field: CreditCardFormField) => void; testID?: string; acceptedCards?: CreditCardIssuer[]; } const s = StyleSheet.create({ container: { paddingVertical: 10, paddingHorizontal: 15, flexDirection: 'row', alignItems: 'center', overflow: 'hidden', }, icon: { width: 48, height: 40, resizeMode: 'contain', }, expanded: { flex: 1, }, hidden: { width: 0, }, leftPart: { overflow: 'hidden', }, rightPart: { overflow: 'hidden', flexDirection: 'row', }, last4: { flex: 1, justifyContent: 'center', }, numberInput: { width: 1000, }, expiryInput: { width: 80, }, cvcInput: { width: 80, }, last4Input: { width: 60, marginLeft: 20, }, input: { height: 40, fontSize: 16, // @ts-expect-error outlineWidth is used to hide the text-input outline on react-native-web outlineWidth: 0, }, }); const LiteCreditCardInput = (props: Props) => { const { autoFocus = false, style, inputStyle, placeholderColor = 'darkgray', placeholders = { number: '1234 5678 1234 5678', expiry: 'MM/YY', cvc: 'CVC', }, onChange, onFocusField = () => {}, testID, acceptedCards, } = props; const _onChange = (formData: CreditCardFormData): void => { // Focus next field when number/expiry field become valid if (status.number !== 'valid' && formData.status.number === 'valid') { toggleFormState(); expiryInput.current?.focus(); } if (status.expiry !== 'valid' && formData.status.expiry === 'valid') { cvcInput.current?.focus(); } onChange(formData); }; const { values, status, onChangeValue } = useCreditCardForm(_onChange); const [showRightPart, setShowRightPart] = useState(false); const toggleFormState = useCallback(() => { LayoutAnimation.easeInEaseOut(); setShowRightPart((v) => !v); }, []); const numberInput = useRef(null); const expiryInput = useRef(null); const cvcInput = useRef(null); useEffect(() => { if (autoFocus) numberInput.current?.focus(); }, [autoFocus]); const cardIcons = useMemo(() => { if (values.type && Icons[values.type]) return [Icons[values.type]]; return acceptedCards?.map((card) => Icons[card]) || [Icons.placeholder]; }, [values.type, acceptedCards]); return ( onChangeValue('number', v)} onFocus={() => onFocusField('number')} autoCorrect={false} underlineColorAndroid={'transparent'} testID="CC_NUMBER" /> {cardIcons.map((icon, index) => ( ))} onChangeValue('expiry', v)} onFocus={() => onFocusField('expiry')} autoCorrect={false} underlineColorAndroid={'transparent'} testID="CC_EXPIRY" /> onChangeValue('cvc', v)} onFocus={() => onFocusField('cvc')} autoCorrect={false} underlineColorAndroid={'transparent'} testID="CC_CVC" /> ); }; export default LiteCreditCardInput;