From e741d47fc27fa89e5ce5f4bde94271de7c5f7153 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 11 Apr 2022 12:15:44 -0400 Subject: [PATCH] Fix float input label initial position (#6151) * Fix float input label initial position * fix focused label when field is empty --- .../animation_utils.ts | 83 ----------------- .../floating_text_input_label/index.tsx | 93 +++++-------------- 2 files changed, 23 insertions(+), 153 deletions(-) delete mode 100644 app/components/floating_text_input_label/animation_utils.ts diff --git a/app/components/floating_text_input_label/animation_utils.ts b/app/components/floating_text_input_label/animation_utils.ts deleted file mode 100644 index 35be0b11cb..0000000000 --- a/app/components/floating_text_input_label/animation_utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import Animated, {EasingNode, Value, block, clockRunning, cond, not, set, startClock, stopClock, timing as retiming} from 'react-native-reanimated'; - -type AnimationState = { - finished: Value; - frameTime: Value; - position: Value; - time: Value; -} - -type AnimationConfig = Animated.TimingConfig &{ - duration: number; - easing: Animated.EasingNodeFunction; - toValue: Value; -}; - -type AnimationTiming = { - clock: Animated.Clock; - animation: Value; - duration?: number; - from?: number; - to?: number; - easing?: Animated.EasingNodeFunction; -} - -type AnimateArgs = { - clock: Animated.Clock; - config: Animated.TimingConfig; - fn: typeof retiming; - from: number; - state: AnimationState; -} - -export const timingAnimation = (params: AnimationTiming) => { - const {clock, easing, duration, from, to} = { - duration: 250, - easing: EasingNode.linear, - from: 0, - to: 1, - ...params, - }; - - const state: AnimationState = { - finished: new Value(0), - frameTime: new Value(0), - position: new Value(0), - time: new Value(0), - }; - - const config: AnimationConfig = { - toValue: new Value(0), - duration, - easing, - }; - - return block([ - onInit(clock, [set(config.toValue, to), set(state.frameTime, 0)]), - animate({ - clock, - fn: retiming, - state, - config, - from, - }), - ]); -}; - -const animate = ({fn, clock, state, config, from}: AnimateArgs) => - block([ - onInit(clock, [ - set(state.finished, 0), - set(state.time, 0), - set(state.position, from), - startClock(clock), - ]), - fn(clock, state, config), - cond(state.finished, stopClock(clock)), - state.position, - ]); - -const onInit = (clock: Animated.Clock, sequence: Array>) => cond(not(clockRunning(clock)), sequence); diff --git a/app/components/floating_text_input_label/index.tsx b/app/components/floating_text_input_label/index.tsx index 3fb363449e..c9243d2806 100644 --- a/app/components/floating_text_input_label/index.tsx +++ b/app/components/floating_text_input_label/index.tsx @@ -6,39 +6,15 @@ import {debounce} from 'lodash'; import React, {useState, useEffect, useRef, useImperativeHandle, forwardRef, useMemo, useCallback} from 'react'; import {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, Platform, StyleProp, TargetedEvent, Text, TextInput, TextInputFocusEventData, TextInputProps, TextStyle, TouchableWithoutFeedback, View, ViewStyle} from 'react-native'; -import Animated, {useCode, interpolateNode, EasingNode, Value, set, Clock} from 'react-native-reanimated'; +import Animated, {useAnimatedStyle, withTiming, Easing} from 'react-native-reanimated'; import CompassIcon from '@components/compass_icon'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import {timingAnimation} from './animation_utils'; - const DEFAULT_INPUT_HEIGHT = 48; const BORDER_DEFAULT_WIDTH = 1; const BORDER_FOCUSED_WIDTH = 2; -const getTopStyle = (styles: any, animation: Value<0|1>, inputText?: string) => { - if (inputText) { - return getLabelPositions(styles.textInput, styles.label, styles.smallLabel)[1]; - } - - return interpolateNode(animation, { - inputRange: [0, 1], - outputRange: [...getLabelPositions(styles.textInput, styles.label, styles.smallLabel)], - }); -}; - -const getFontSize = (styles: any, animation: Value<0|1>, inputText?: string) => { - if (inputText) { - return styles.smallLabel.fontSize; - } - - return interpolateNode(animation, { - inputRange: [0, 1], - outputRange: [styles.textInput.fontSize, styles.smallLabel.fontSize], - }); -}; - const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ container: { height: DEFAULT_INPUT_HEIGHT + (2 * BORDER_DEFAULT_WIDTH), @@ -156,18 +132,14 @@ const FloatingTextInput = forwardRef { - const [focusedLabel, setIsFocusLabel] = useState(); const [focused, setIsFocused] = useState(Boolean(value) && editable); + const [focusedLabel, setIsFocusLabel] = useState(Boolean(placeholder || value)); const inputRef = useRef(null); - const [animation] = useState(new Value(focusedLabel ? 1 : 0)); const debouncedOnFocusTextInput = debounce(setIsFocusLabel, 500, {leading: true, trailing: false}); const styles = getStyleSheet(theme); - let from = 0; - let to = 0; - if (focusedLabel !== undefined) { - from = focusedLabel ? 0 : 1; - to = focusedLabel ? 1 : 0; - } + + const positions = useMemo(() => getLabelPositions(styles.textInput, styles.label, styles.smallLabel), [styles]); + const size = useMemo(() => [styles.textInput.fontSize, styles.smallLabel.fontSize], [styles]); useImperativeHandle(ref, () => ({ blur: () => inputRef.current?.blur(), @@ -175,21 +147,6 @@ const FloatingTextInput = forwardRef inputRef.current?.isFocused() || false, }), [inputRef]); - useCode( - () => set( - animation, - timingAnimation({ - animation, - duration: 150, - from, - to, - easing: EasingNode.linear, - clock: new Clock(), - }), - ), - [focusedLabel], - ); - useEffect( () => { if (!focusedLabel && value) { @@ -253,31 +210,27 @@ const FloatingTextInput = forwardRef { - const res = [styles.label]; - const inputText = value || placeholder; - res.push({ - top: getTopStyle(styles, animation, inputText), - fontSize: getFontSize(styles, animation, inputText), - backgroundColor: ( - focusedLabel || inputText ? theme.centerChannelBg : 'transparent' - ), - paddingHorizontal: focusedLabel || inputText ? 4 : 0, - color: styles.label.color, - }); - - if (focused) { - res.push({color: theme.buttonBg}); - } - - res.push(labelTextStyle); + const textAnimatedTextStyle = useAnimatedStyle(() => { + const inputText = placeholder || value; + const index = inputText || focusedLabel ? 1 : 0; + const toValue = positions[index]; + const toSize = size[index]; + let color = styles.label.color; if (shouldShowError) { - res.push({color: theme.errorTextColor}); + color = theme.errorTextColor; + } else if (focused) { + color = theme.buttonBg; } - return res; - }, [theme, styles, focused, shouldShowError, labelTextStyle, placeholder, animation, focusedLabel]); + return { + top: withTiming(toValue, {duration: 250, easing: Easing.linear}), + fontSize: withTiming(toSize, {duration: 250, easing: Easing.linear}), + backgroundColor: focusedLabel || inputText ? theme.centerChannelBg : 'transparent', + paddingHorizontal: focusedLabel || inputText ? 4 : 0, + color, + }; + }); return ( {label}