// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. // Note: This file has been adapted from the library https://github.com/csath/react-native-reanimated-text-input import React, {useState, useRef, useImperativeHandle, forwardRef, useMemo, useCallback} from 'react'; import {type LayoutChangeEvent, type NativeSyntheticEvent, type StyleProp, type TargetedEvent, TextInput, type TextInputFocusEventData, type TextInputProps, type TextStyle} from 'react-native'; import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import FloatingInputContainer from './floating_input_container'; import {onExecution} from './utils'; const DEFAULT_INPUT_HEIGHT = 48; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ input: { backgroundColor: 'transparent', borderWidth: 0, flex: 1, paddingHorizontal: 0, paddingTop: 0, paddingBottom: 0, flexDirection: 'row', color: theme.centerChannelColor, borderColor: changeOpacity(theme.centerChannelColor, 0.16), borderRadius: 4, ...typography('Body', 200), lineHeight: undefined, }, })); export type FloatingTextInputRef = { blur: () => void; focus: () => void; isFocused: () => boolean; } type FloatingTextInputProps = /*TextInputProps &*/ { editable?: boolean; endAdornment?: React.ReactNode; error?: string; errorIcon?: string; label: string; multiline?: boolean; multilineInputHeight?: number; onBlur?: (event: NativeSyntheticEvent) => void; onFocus?: (e: NativeSyntheticEvent) => void; onLayout?: (e: LayoutChangeEvent) => void; placeholder?: string; hideErrorIcon?: boolean; testID?: string; theme: Theme; value?: string; rawInput?: boolean; onChangeText?: TextInputProps['onChangeText']; defaultValue?: TextInputProps['defaultValue']; autoFocus?: TextInputProps['autoFocus']; enablesReturnKeyAutomatically?: TextInputProps['enablesReturnKeyAutomatically']; keyboardType?: TextInputProps['keyboardType']; onSubmitEditing?: TextInputProps['onSubmitEditing']; returnKeyType?: TextInputProps['returnKeyType']; secureTextEntry?: TextInputProps['secureTextEntry']; blurOnSubmit?: TextInputProps['blurOnSubmit']; autoComplete?: TextInputProps['autoComplete']; disableFullscreenUI?: TextInputProps['disableFullscreenUI']; maxLength?: TextInputProps['maxLength']; } const FloatingTextInput = forwardRef(({ editable = true, error, endAdornment, label = '', multiline, multilineInputHeight, onBlur, onFocus, onLayout, placeholder, hideErrorIcon = false, testID, theme, value, rawInput = false, ...textInputProps }: FloatingTextInputProps, ref) => { const [focused, setIsFocused] = useState(false); const focusedLabel = Boolean(focused || Boolean(value) || placeholder); const inputRef = useRef(null); const styles = getStyleSheet(theme); useImperativeHandle(ref, () => ({ blur: () => inputRef.current?.blur(), focus: () => inputRef.current?.focus(), isFocused: () => inputRef.current?.isFocused() || false, }), [inputRef]); const onTextInputBlur = useCallback((e: NativeSyntheticEvent) => onExecution(e, () => { setIsFocused(false); }, onBlur, ), [onBlur]); const onTextInputFocus = useCallback((e: NativeSyntheticEvent) => onExecution(e, () => { setIsFocused(true); }, onFocus, ), [onFocus]); const defaultHeight = multiline ? multilineInputHeight || 100 : DEFAULT_INPUT_HEIGHT; const combinedTextInputStyle = useMemo(() => { const res: StyleProp = [styles.input]; if (multiline) { const height = multilineInputHeight ? multilineInputHeight - 20 : 80; res.push({height, textAlignVertical: 'top'}); } return res; }, [styles, multiline, multilineInputHeight]); const focus = useCallback(() => { inputRef.current?.focus(); }, []); return ( {endAdornment} ); }); FloatingTextInput.displayName = 'FloatingTextInput'; export default FloatingTextInput;