Fix float input label initial position (#6151)

* Fix float input label initial position

* fix focused label when field is empty
This commit is contained in:
Elias Nahum
2022-04-11 12:15:44 -04:00
committed by GitHub
parent d9109691fd
commit e741d47fc2
2 changed files with 23 additions and 153 deletions

View File

@@ -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<number>;
frameTime: Value<number>;
position: Value<number>;
time: Value<number>;
}
type AnimationConfig = Animated.TimingConfig &{
duration: number;
easing: Animated.EasingNodeFunction;
toValue: Value<number>;
};
type AnimationTiming = {
clock: Animated.Clock;
animation: Value<number>;
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<Animated.Node<number>>) => cond(not(clockRunning(clock)), sequence);

View File

@@ -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<FloatingTextInputRef, FloatingTextInputProp
testID,
...props
}: FloatingTextInputProps, ref) => {
const [focusedLabel, setIsFocusLabel] = useState<boolean | undefined>();
const [focused, setIsFocused] = useState(Boolean(value) && editable);
const [focusedLabel, setIsFocusLabel] = useState<boolean | undefined>(Boolean(placeholder || value));
const inputRef = useRef<TextInput>(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<FloatingTextInputRef, FloatingTextInputProp
isFocused: () => 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<FloatingTextInputRef, FloatingTextInputProp
return res;
}, [styles, theme, shouldShowError, focused, textInputStyle, focusedLabel, multiline]);
const textAnimatedTextStyle = useMemo(() => {
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 (
<TouchableWithoutFeedback
@@ -287,7 +240,7 @@ const FloatingTextInput = forwardRef<FloatingTextInputRef, FloatingTextInputProp
<View style={combinedContainerStyle}>
<Animated.Text
onPress={onAnimatedTextPress}
style={textAnimatedTextStyle}
style={[styles.label, labelTextStyle, textAnimatedTextStyle]}
suppressHighlighting={true}
>
{label}