From 761e3cbe78d641c45d621c89e40478f1148b6376 Mon Sep 17 00:00:00 2001 From: Luke Walczak <lukasz.walczak.pwr@gmail.com> Date: Mon, 18 Sep 2023 22:09:51 +0200 Subject: [PATCH] fix: set input min width (#3941) --- example/src/Examples/TextInputExample.tsx | 64 +++++++++++++++++++ src/components/TextInput/Label/InputLabel.tsx | 8 ++- src/components/TextInput/TextInput.tsx | 22 +++++++ src/components/TextInput/TextInputFlat.tsx | 4 ++ .../TextInput/TextInputOutlined.tsx | 6 +- src/components/TextInput/types.tsx | 5 ++ .../__snapshots__/TextInput.test.tsx.snap | 25 +++++++- 7 files changed, 130 insertions(+), 4 deletions(-) diff --git a/example/src/Examples/TextInputExample.tsx b/example/src/Examples/TextInputExample.tsx index a8cab508da..d6bdd29196 100644 --- a/example/src/Examples/TextInputExample.tsx +++ b/example/src/Examples/TextInputExample.tsx @@ -697,6 +697,57 @@ const TextInputExample = () => { /> </View> ) : null} + <View style={styles.row}> + <TextInput + mode="outlined" + label="CVV" + placeholder="CVV" + keyboardType="phone-pad" + maxLength={3} + /> + </View> + <View style={styles.row}> + <TextInput + mode="flat" + label="CVV" + placeholder="CVV" + keyboardType="phone-pad" + maxLength={3} + /> + </View> + <View style={styles.row}> + <TextInput + mode="outlined" + label="Code" + placeholder="Code" + keyboardType="phone-pad" + maxLength={4} + /> + </View> + <View style={styles.row}> + <TextInput + mode="flat" + label="Code" + placeholder="Code" + keyboardType="phone-pad" + maxLength={4} + /> + </View> + <View style={styles.row}> + <TextInput + mode="flat" + label="Month" + placeholder="Month" + style={styles.month} + /> + <TextInput + mode="flat" + label="Year" + placeholder="Year" + keyboardType="phone-pad" + style={styles.year} + /> + </View> </List.Accordion> </List.AccordionGroup> </ScreenWrapper> @@ -745,6 +796,19 @@ const styles = StyleSheet.create({ fixedHeight: { height: 100, }, + row: { + margin: 8, + justifyContent: 'space-between', + flexDirection: 'row', + }, + month: { + flex: 1, + marginRight: 4, + }, + year: { + flex: 1, + marginLeft: 4, + }, }); export default TextInputExample; diff --git a/src/components/TextInput/Label/InputLabel.tsx b/src/components/TextInput/Label/InputLabel.tsx index a2d8d7845a..df5ade3812 100644 --- a/src/components/TextInput/Label/InputLabel.tsx +++ b/src/components/TextInput/Label/InputLabel.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Animated, StyleSheet } from 'react-native'; +import { Animated, StyleSheet, Dimensions } from 'react-native'; import AnimatedText from '../../Typography/AnimatedText'; import type { InputLabelProps } from '../types'; @@ -16,6 +16,7 @@ const InputLabel = (props: InputLabelProps) => { label, labelError, onLayoutAnimatedText, + onLabelTextLayout, hasActiveOutline, activeColor, placeholderStyle, @@ -40,6 +41,8 @@ const InputLabel = (props: InputLabelProps) => { testID, } = props; + const { width } = Dimensions.get('window'); + const paddingOffset = paddingLeft && paddingRight ? { paddingLeft, paddingRight } : {}; @@ -107,7 +110,7 @@ const InputLabel = (props: InputLabelProps) => { style={[ StyleSheet.absoluteFill, styles.labelContainer, - { opacity }, + { opacity, width }, labelTranslationX, ]} > @@ -127,6 +130,7 @@ const InputLabel = (props: InputLabelProps) => { <AnimatedText variant="bodySmall" onLayout={onLayoutAnimatedText} + onTextLayout={onLabelTextLayout} style={[ placeholderStyle, { diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx index 7ecf3d1a4b..9b84397341 100644 --- a/src/components/TextInput/TextInput.tsx +++ b/src/components/TextInput/TextInput.tsx @@ -6,6 +6,8 @@ import { TextInput as NativeTextInput, TextStyle, ViewStyle, + NativeSyntheticEvent, + TextLayoutEventData, } from 'react-native'; import TextInputAffix, { @@ -248,6 +250,10 @@ const TextInput = forwardRef<TextInputHandles, Props>( // Use value from props instead of local state when input is controlled const value = isControlled ? rest.value : uncontrolledValue; + const [labelTextLayout, setLabelTextLayout] = React.useState({ + width: 33, + }); + const [labelLayout, setLabelLayout] = React.useState<{ measured: boolean; width: number; @@ -447,6 +453,18 @@ const TextInput = forwardRef<TextInputHandles, Props>( [labelLayout.height, labelLayout.width] ); + const handleLabelTextLayout = React.useCallback( + ({ nativeEvent }: NativeSyntheticEvent<TextLayoutEventData>) => { + setLabelTextLayout({ + width: nativeEvent.lines.reduce( + (acc, line) => acc + Math.ceil(line.width), + 0 + ), + }); + }, + [] + ); + const forceFocus = React.useCallback(() => root.current?.focus(), []); const { maxFontSizeMultiplier = 1.5 } = rest; @@ -469,6 +487,7 @@ const TextInput = forwardRef<TextInputHandles, Props>( focused, placeholder, value, + labelTextLayout, labelLayout, leftLayout, rightLayout, @@ -481,6 +500,7 @@ const TextInput = forwardRef<TextInputHandles, Props>( onBlur={handleBlur} onChangeText={handleChangeText} onLayoutAnimatedText={handleLayoutAnimatedText} + onLabelTextLayout={handleLabelTextLayout} onLeftAffixLayoutChange={onLeftAffixLayoutChange} onRightAffixLayoutChange={onRightAffixLayoutChange} maxFontSizeMultiplier={maxFontSizeMultiplier} @@ -506,6 +526,7 @@ const TextInput = forwardRef<TextInputHandles, Props>( focused, placeholder, value, + labelTextLayout, labelLayout, leftLayout, rightLayout, @@ -518,6 +539,7 @@ const TextInput = forwardRef<TextInputHandles, Props>( onBlur={handleBlur} onChangeText={handleChangeText} onLayoutAnimatedText={handleLayoutAnimatedText} + onLabelTextLayout={handleLabelTextLayout} onLeftAffixLayoutChange={onLeftAffixLayoutChange} onRightAffixLayoutChange={onRightAffixLayoutChange} maxFontSizeMultiplier={maxFontSizeMultiplier} diff --git a/src/components/TextInput/TextInputFlat.tsx b/src/components/TextInput/TextInputFlat.tsx index f90b4bedcd..44e163838a 100644 --- a/src/components/TextInput/TextInputFlat.tsx +++ b/src/components/TextInput/TextInputFlat.tsx @@ -65,6 +65,7 @@ const TextInputFlat = ({ onBlur, onChangeText, onLayoutAnimatedText, + onLabelTextLayout, onLeftAffixLayoutChange, onRightAffixLayoutChange, left, @@ -254,6 +255,7 @@ const TextInputFlat = ({ const labelProps = { label, onLayoutAnimatedText, + onLabelTextLayout, placeholderOpacity, labelError: error, placeholderStyle: styles.placeholder, @@ -405,6 +407,8 @@ const TextInputFlat = ({ : I18nManager.getConstants().isRTL ? 'right' : 'left', + minWidth: + parentState.labelTextLayout.width + 2 * FLAT_INPUT_OFFSET, }, Platform.OS === 'web' && { outline: 'none' }, adornmentStyleAdjustmentForNativeInput, diff --git a/src/components/TextInput/TextInputOutlined.tsx b/src/components/TextInput/TextInputOutlined.tsx index 025316c395..b8430c0ce9 100644 --- a/src/components/TextInput/TextInputOutlined.tsx +++ b/src/components/TextInput/TextInputOutlined.tsx @@ -65,6 +65,7 @@ const TextInputOutlined = ({ onBlur, onChangeText, onLayoutAnimatedText, + onLabelTextLayout, onLeftAffixLayoutChange, onRightAffixLayoutChange, left, @@ -200,6 +201,7 @@ const TextInputOutlined = ({ const labelProps = { label, onLayoutAnimatedText, + onLabelTextLayout, placeholderOpacity, labelError: error, placeholderStyle, @@ -376,6 +378,9 @@ const TextInputOutlined = ({ ? 'right' : 'left', paddingHorizontal: INPUT_PADDING_HORIZONTAL, + minWidth: + parentState.labelTextLayout.width + + 2 * INPUT_PADDING_HORIZONTAL, }, Platform.OS === 'web' && { outline: 'none' }, adornmentStyleAdjustmentForNativeInput, @@ -398,7 +403,6 @@ const styles = StyleSheet.create({ }, input: { margin: 0, - zIndex: 1, }, inputOutlined: { paddingTop: 8, diff --git a/src/components/TextInput/types.tsx b/src/components/TextInput/types.tsx index 8e1b30b86b..659c960e8b 100644 --- a/src/components/TextInput/types.tsx +++ b/src/components/TextInput/types.tsx @@ -8,6 +8,8 @@ import type { StyleProp, ViewProps, ViewStyle, + NativeSyntheticEvent, + TextLayoutEventData, } from 'react-native'; import type { $Omit, InternalTheme, ThemeProp } from './../../types'; @@ -70,6 +72,7 @@ export type State = { focused: boolean; placeholder?: string; value?: string; + labelTextLayout: { width: number }; labelLayout: { measured: boolean; width: number; height: number }; leftLayout: { height: number | null; width: number | null }; rightLayout: { height: number | null; width: number | null }; @@ -83,6 +86,7 @@ export type ChildTextInputProps = { forceFocus: () => void; onChangeText?: (value: string) => void; onLayoutAnimatedText: (args: any) => void; + onLabelTextLayout: (event: NativeSyntheticEvent<TextLayoutEventData>) => void; onLeftAffixLayoutChange: (event: LayoutChangeEvent) => void; onRightAffixLayoutChange: (event: LayoutChangeEvent) => void; } & $Omit<TextInputTypesWithoutMode, 'theme'> & { theme: InternalTheme }; @@ -114,6 +118,7 @@ export type LabelProps = { errorColor?: string; labelError?: boolean | null; onLayoutAnimatedText: (args: any) => void; + onLabelTextLayout: (event: NativeSyntheticEvent<TextLayoutEventData>) => void; roundness: number; maxFontSizeMultiplier?: number | undefined | null; testID?: string; diff --git a/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap b/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap index d977929c39..1938f874e0 100644 --- a/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -62,6 +62,7 @@ exports[`correctly applies a component as the text label 1`] = ` "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -71,6 +72,7 @@ exports[`correctly applies a component as the text label 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -188,6 +190,7 @@ exports[`correctly applies a component as the text label 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 16, "textAlign": "left", @@ -270,6 +273,7 @@ exports[`correctly applies cursorColor prop 1`] = ` "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -279,6 +283,7 @@ exports[`correctly applies cursorColor prop 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -380,6 +385,7 @@ exports[`correctly applies cursorColor prop 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 16, "textAlign": "left", @@ -462,6 +468,7 @@ exports[`correctly applies default textAlign based on default RTL 1`] = ` "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -471,6 +478,7 @@ exports[`correctly applies default textAlign based on default RTL 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -572,6 +580,7 @@ exports[`correctly applies default textAlign based on default RTL 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 16, "textAlign": "left", @@ -648,6 +657,7 @@ exports[`correctly applies height to multiline Outline TextInput 1`] = ` "translateX": 3, }, ], + "width": 750, "zIndex": 3, } } @@ -698,6 +708,7 @@ exports[`correctly applies height to multiline Outline TextInput 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -782,7 +793,6 @@ exports[`correctly applies height to multiline Outline TextInput 1`] = ` [ { "margin": 0, - "zIndex": 1, }, { "height": 100, @@ -798,6 +808,7 @@ exports[`correctly applies height to multiline Outline TextInput 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingHorizontal": 16, "textAlign": "left", "textAlignVertical": "top", @@ -880,6 +891,7 @@ exports[`correctly applies paddingLeft from contentStyleProp 1`] = ` "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -889,6 +901,7 @@ exports[`correctly applies paddingLeft from contentStyleProp 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -990,6 +1003,7 @@ exports[`correctly applies paddingLeft from contentStyleProp 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 16, "textAlign": "left", @@ -1074,6 +1088,7 @@ exports[`correctly applies textAlign center 1`] = ` "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -1083,6 +1098,7 @@ exports[`correctly applies textAlign center 1`] = ` maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -1184,6 +1200,7 @@ exports[`correctly applies textAlign center 1`] = ` "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 16, "textAlign": "center", @@ -1266,6 +1283,7 @@ exports[`correctly renders left-side affix adornment, and right-side icon adornm "translateX": 4, }, ], + "width": 750, "zIndex": 3, } } @@ -1275,6 +1293,7 @@ exports[`correctly renders left-side affix adornment, and right-side icon adornm maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -1376,6 +1395,7 @@ exports[`correctly renders left-side affix adornment, and right-side icon adornm "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 16, "paddingRight": 56, "textAlign": "left", @@ -1660,6 +1680,7 @@ exports[`correctly renders left-side icon adornment, and right-side affix adornm "translateX": 14, }, ], + "width": 750, "zIndex": 3, } } @@ -1669,6 +1690,7 @@ exports[`correctly renders left-side icon adornment, and right-side affix adornm maxFontSizeMultiplier={1.5} numberOfLines={1} onLayout={[Function]} + onTextLayout={[Function]} style={ { "color": "rgba(103, 80, 164, 1)", @@ -1770,6 +1792,7 @@ exports[`correctly renders left-side icon adornment, and right-side affix adornm "fontWeight": undefined, "letterSpacing": 0.15, "lineHeight": 19.2, + "minWidth": 65, "paddingLeft": 56, "paddingRight": 56, "textAlign": "left",