Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions src/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import TextInputFlat from './TextInputFlat';
import TextInputIcon from './Adornment/TextInputIcon';
import TextInputAffix from './Adornment/TextInputAffix';
import { withTheme } from '../../core/theming';
import type { RenderProps, State } from './types';
import type { RenderProps, State, TextInputLabelProp } from './types';
import type { $Omit } from '../../types';

const BLUR_ANIMATION_DURATION = 180;
Expand All @@ -36,9 +36,9 @@ export type TextInputProps = React.ComponentPropsWithRef<
*/
disabled?: boolean;
/**
* The text to use for the floating label.
* The text or component to use for the floating label.
*/
label?: string;
label?: TextInputLabelProp;
/**
* Placeholder for the input.
*/
Expand Down Expand Up @@ -233,9 +233,8 @@ class TextInput extends React.Component<TextInputProps, State> {
const isValueChanged = prevState.value !== this.state.value;
const isLabelLayoutChanged =
prevState.labelLayout !== this.state.labelLayout;
const isLabelChanged = prevProps.label !== this.props.label;
const isLabelChanged = !areLabelsEqual(prevProps.label, this.props.label);
const isErrorChanged = prevProps.error !== this.props.error;

if (
isFocusChanged ||
isValueChanged ||
Expand Down Expand Up @@ -477,4 +476,42 @@ class TextInput extends React.Component<TextInputProps, State> {
}
}

function areLabelsEqual(
label1?: TextInputLabelProp,
label2?: TextInputLabelProp
): boolean {
if (label1 === label2) {
// will also take care of equality for `string` type, or if both are undefined.
return true;
}

// At this point, both of them cannot be undefined.
// So, return false if any of them is falsy.
if (!label1 || !label2) {
return false;
}

// At this point, both of them has to be truthy.
// So, return false if they are not of the same type.
if (typeof label1 !== typeof label2) {
return false;
}

// At this point, both of them has to be of the same datatype.
if (
typeof label1 === 'string' ||
label1 instanceof String ||
// These last two OR checks are only here for Typescript's sake.
typeof label2 === 'string' ||
label2 instanceof String
) {
// They're strings, so they won't be equal; otherwise
// we would have returned 'true' earlier.
return false;
}

// At this point, both of them has to be of the datatype: `ReactElement`.
return label1.type == label2.type;
}

export default withTheme(TextInput);
3 changes: 2 additions & 1 deletion src/components/TextInput/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
FLAT_INPUT_OFFSET,
} from './constants';
import { AdornmentType, AdornmentSide } from './Adornment/enums';
import type { TextInputLabelProp } from './types';

type PaddingProps = {
height: number | null;
Expand All @@ -16,7 +17,7 @@ type PaddingProps = {
topPosition: number;
fontSize: number;
lineHeight?: number;
label?: string | null;
label?: TextInputLabelProp | null;
scale: number;
offset: number;
isAndroid: boolean;
Expand Down
4 changes: 3 additions & 1 deletion src/components/TextInput/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {
import type { TextInputProps } from './TextInput';
import type { $Omit } from './../../types';

export type TextInputLabelProp = string | React.ReactElement;

export type RenderProps = {
ref: (a?: NativeTextInput | null) => void;
onChangeText?: (a: string) => void;
Expand Down Expand Up @@ -62,7 +64,7 @@ export type LabelProps = {
labelTranslationXOffset?: number;
placeholderColor: string | null;
backgroundColor?: ColorValue;
label?: string | null;
label?: TextInputLabelProp | null;
hasActiveOutline?: boolean | null;
activeColor: string;
errorColor?: string;
Expand Down
14 changes: 13 additions & 1 deletion src/components/__tests__/TextInput.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, Text } from 'react-native';
import { render } from 'react-native-testing-library';
import TextInput from '../TextInput/TextInput';
import { red500 } from '../../styles/colors';
Expand Down Expand Up @@ -108,3 +108,15 @@ it('correctly applies height to multiline Outline TextInput', () => {

expect(toJSON()).toMatchSnapshot();
});

it('correctly applies a component as the text label', () => {
const { toJSON } = render(
<TextInput
label={<Text style={style.inputStyle}>Flat input</Text>}
placeholder="Type something"
value={'Some test value'}
/>
);

expect(toJSON()).toMatchSnapshot();
});
193 changes: 193 additions & 0 deletions src/components/__tests__/__snapshots__/TextInput.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,198 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`correctly applies a component as the text label 1`] = `
<View
style={
Array [
Object {
"backgroundColor": "rgb(231, 231, 231)",
"borderTopLeftRadius": 4,
"borderTopRightRadius": 4,
},
Object {},
]
}
>
<View
style={
Object {
"backgroundColor": "rgba(0, 0, 0, 0.26)",
"bottom": 0,
"height": 2,
"left": 0,
"position": "absolute",
"right": 0,
"transform": Array [
Object {
"scaleY": 0.5,
},
],
"zIndex": 1,
}
}
/>
<View
style={
Array [
Object {
"paddingBottom": 0,
"paddingTop": 0,
},
Object {
"minHeight": 64,
},
]
}
>
<View
pointerEvents="none"
style={
Object {
"bottom": 0,
"left": 0,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
"transform": Array [
Object {
"translateX": 3,
},
],
}
}
>
<Text
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "#6200ee",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": undefined,
"left": 0,
"opacity": 0,
"paddingLeft": 12,
"paddingRight": 12,
"position": "absolute",
"textAlign": "left",
"top": 34,
"transform": Array [
Object {
"translateX": 0,
},
Object {
"translateY": -16,
},
Object {
"scale": 0.75,
},
],
"writingDirection": "ltr",
}
}
>
<Text
style={
Object {
"color": "#f44336",
}
}
>
Flat input
</Text>
</Text>
<Text
numberOfLines={1}
style={
Object {
"color": "rgba(0, 0, 0, 0.54)",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": undefined,
"left": 0,
"opacity": 0,
"paddingLeft": 12,
"paddingRight": 12,
"position": "absolute",
"textAlign": "left",
"top": 34,
"transform": Array [
Object {
"translateX": 0,
},
Object {
"translateY": -16,
},
Object {
"scale": 0.75,
},
],
"writingDirection": "ltr",
}
}
>
<Text
style={
Object {
"color": "#f44336",
}
}
>
Flat input
</Text>
</Text>
</View>
<TextInput
allowFontScaling={true}
editable={true}
multiline={false}
onBlur={[Function]}
onChangeText={[Function]}
onFocus={[Function]}
placeholder=""
placeholderTextColor="rgba(0, 0, 0, 0.54)"
rejectResponderTermination={true}
selectionColor="#6200ee"
style={
Array [
Object {
"flexGrow": 1,
"margin": 0,
},
Object {
"paddingLeft": 12,
"paddingRight": 12,
},
Object {
"height": 64,
},
Object {
"paddingBottom": 4,
"paddingTop": 24,
},
Object {
"color": "#000000",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": undefined,
"textAlign": "left",
"textAlignVertical": "center",
},
false,
Array [
Object {},
],
]
}
underlineColorAndroid="transparent"
value="Some test value"
/>
</View>
</View>
`;

exports[`correctly applies default textAlign based on default RTL 1`] = `
<View
style={
Expand Down