import React, { ComponentType } from 'react'; import { TextInput as NativeTextInput, StyleSheet, View, Text, TouchableWithoutFeedback, StyleProp, TextStyle, ViewStyle, NativeSyntheticEvent, TextInputChangeEventData, TextInputSelectionChangeEventData, TextInputKeyPressEventData, } from 'react-native'; import { useTheme } from './ThemeContext'; import Icon from './Icon'; import noop from '../helpers/noop'; const isEmpty = (str: string) => str.length === 0; const emptyStyle = {}; export type keyboardType = | 'default' | 'number-pad' | 'decimal-pad' | 'numeric' | 'email-address' | 'phone-pad' | 'ascii-capable' | 'numbers-and-punctuation' | 'url' | 'name-phone-pad' | 'twitter' | 'web-search' | 'visible-password'; export type autoCapitalize = 'none' | 'words' | 'characters' | 'sentences'; export type Props = { readonly autoCapitalize?: autoCapitalize; readonly autoCorrect?: boolean; readonly autoFocus?: boolean; readonly children?: React.ReactNode; readonly defaultValue?: string; readonly disabled?: boolean; readonly error?: string; readonly errorStyle?: StyleProp; readonly iconStyle?: StyleProp<{ color?: string }>; readonly inputStyle?: StyleProp; readonly keyboardType?: keyboardType; readonly label?: string; readonly labelStyle?: StyleProp; readonly multiline?: boolean; readonly onBlur?: () => void; readonly onChange?: (e: NativeSyntheticEvent) => void; readonly onChangeText?: (text: string) => void; readonly onFocus?: () => void; readonly onKeyPress?: (e: NativeSyntheticEvent) => void; readonly onPressIcon?: () => void; readonly onSelectionChange?: (e: NativeSyntheticEvent) => void; readonly placeholder?: string; readonly rightIcon?: string; readonly secureTextEntry?: boolean; readonly testID?: string; readonly value?: string; readonly wrapperStyle?: StyleProp; }; const getPlaceholder = ({ placeholder, focused, label }: { placeholder: string; focused: boolean; label: string }) => { if (placeholder === '') { return focused ? '' : label; } return placeholder; }; const getLabel = ({ value, internalValue, defaultValue, focused, label, }: { value?: string; internalValue?: string; defaultValue?: string; focused: boolean; label: string; }) => { if (focused) { return label; } if (value !== undefined) { return !isEmpty(value) ? label : ''; } if (internalValue !== undefined) { if (!isEmpty(internalValue) || (internalValue === '' && defaultValue !== undefined && !isEmpty(defaultValue))) { return label; } return ''; } if (defaultValue !== undefined && !isEmpty(defaultValue)) { return label; } return ''; }; const TextInput: ComponentType = ({ testID, label = '', placeholder = '', keyboardType = 'default', autoCapitalize, defaultValue, value, onChange = noop, onChangeText = noop, onSelectionChange = noop, onKeyPress = noop, onFocus = noop, onBlur = noop, onPressIcon = noop, rightIcon = '', disabled = false, error = '', autoFocus = false, autoCorrect = false, secureTextEntry = false, multiline = false, wrapperStyle = emptyStyle, labelStyle = emptyStyle, inputStyle = emptyStyle, iconStyle = emptyStyle, errorStyle = emptyStyle, children = null, }: Props) => { const theme = useTheme(); const [focused, setFocused] = React.useState(false); const [internalValue, setInternalValue] = React.useState(defaultValue); const isUncontrolled = value === undefined; const actualPlaceholder = getPlaceholder({ placeholder, focused, label }); const actualLabel = getLabel({ value, internalValue, defaultValue, focused, label }); const handleFocus = React.useCallback(() => { setFocused(true); onFocus(); }, [onFocus, setFocused]); const handleBlur = React.useCallback(() => { setFocused(false); onBlur(); }, [onBlur, setFocused]); const handleChangeText = React.useCallback( text => { if (isUncontrolled) { setInternalValue(text); } onChangeText(text); }, [onChangeText, setInternalValue], ); const inputStyleColor = StyleSheet.flatten(inputStyle).color; return ( {actualLabel} {children} ([ theme.textInput.icon, focused ? theme.textInput.activeIcon : emptyStyle, !isEmpty(error) ? theme.textInput.errorIcon : emptyStyle, iconStyle, ]).color } /> {error} ); }; export default TextInput;