Skip to content

The new TextField (Incubator) #854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e360aa0
initial creation of the new TextField under incubator
ethanshar Jul 15, 2020
64399b4
Support label color by state
ethanshar Jul 15, 2020
10e9ce3
remove warning
ethanshar Jul 15, 2020
3953c91
Forward ref to input
ethanshar Jul 15, 2020
ee6f4ea
Allow styling Field Label
ethanshar Jul 15, 2020
9a131dd
Add Field state for keeping focus state and valid state
ethanshar Jul 15, 2020
aaec67d
add validation events (blur, change, start)
ethanshar Jul 16, 2020
c3ef3f2
Add basic support to FloatingPlaceholder
ethanshar Jul 16, 2020
501dfbf
Merge branch 'master' into feat/NewTextField
ethanshar Jul 20, 2020
6d920af
Revert PlaygroundScreen changes
ethanshar Jul 20, 2020
489e9e1
Create a dedicated example screen for the New TextField
ethanshar Jul 20, 2020
0034739
Fix issue with floating placeholder behavior
ethanshar Jul 20, 2020
6f4eee0
normalize android input spacings
ethanshar Jul 20, 2020
f5dd2ad
add calculation for the floating placeholder animation
ethanshar Jul 20, 2020
e256010
Render ValidationMessage only when needed and set its color
ethanshar Jul 21, 2020
7bbc5b4
Merge branch 'master' into feat/NewTextField
ethanshar Jul 26, 2020
7048488
Allow passing style to floatingPlaceholder
ethanshar Jul 26, 2020
f41f753
Support styling validationMessageError
ethanshar Jul 26, 2020
165eb87
pass field state as an object
ethanshar Jul 26, 2020
e36a188
Support showing char counter
ethanshar Jul 26, 2020
e0d3fdc
Minor refactor related to Label
ethanshar Jul 28, 2020
a4c7321
Merge branch 'master' into feat/NewTextField
ethanshar Jul 28, 2020
66cbcd3
Support multiline
ethanshar Jul 30, 2020
5534fe0
Merge branch 'master' into feat/NewTextField
ethanshar Jul 30, 2020
af0da01
Support validation messge position and hints
ethanshar Jul 30, 2020
0c86548
add example with connected value
ethanshar Jul 30, 2020
37509d3
Support color by state for floating placeholder
ethanshar Jul 30, 2020
9ce3dd8
update example screen
ethanshar Jul 30, 2020
290879a
Fix TS errors
ethanshar Jul 30, 2020
20efe64
Merge branch 'master' into feat/NewTextField
ethanshar Jul 31, 2020
8767167
Fix more TS errors
ethanshar Jul 31, 2020
96e31ce
Fix how we forward ref and add theme props support
ethanshar Aug 1, 2020
38b46e8
Set displayName
ethanshar Aug 1, 2020
aea23d3
Merge branch 'master' into feat/NewTextField
ethanshar Aug 3, 2020
c9cb556
Fix ref warning
ethanshar Aug 3, 2020
827e830
Set value to another example
ethanshar Aug 3, 2020
6b2c80d
Replace leading/trailin icon with button for press functionality
ethanshar Aug 3, 2020
84b889a
add accessibilityState to TextField Input
ethanshar Aug 3, 2020
ffa3e5d
Merge branch 'master' into feat/NewTextField
ethanshar Aug 10, 2020
ba191af
Fix TextField Color by state behavior - add missing error state and f…
ethanshar Aug 10, 2020
5dc229e
Fix RTL issues
ethanshar Aug 10, 2020
ee1f682
Merge branch 'master' into feat/NewTextField
ethanshar Aug 13, 2020
a0ad3f6
Move logic out of CharCounter
ethanshar Aug 13, 2020
7dd7472
Fix default color in getColorByState
ethanshar Aug 13, 2020
8ed3c83
update maxLength to a smaller number in TextField example screen
ethanshar Aug 13, 2020
55c5fc6
Fixed disabled accessibility state
ethanshar Aug 18, 2020
1b6dc0f
make maxLength optional to fit with TextInput types and some logic in…
ethanshar Aug 18, 2020
3ffbcf6
Update typings
ethanshar Aug 18, 2020
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
3 changes: 2 additions & 1 deletion demo/src/screens/MenuStructure.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ export const navigationData = {
Incubator: {
title: 'Incubator (Experimental)',
screens: [
{title: 'Native TouchableOpacity', tags: 'touchable native', screen: 'unicorn.incubator.TouchableOpacityScreen'}
{title: 'Native TouchableOpacity', tags: 'touchable native', screen: 'unicorn.incubator.TouchableOpacityScreen'},
{title: '(New) TextField', tags: 'text field input', screen: 'unicorn.components.IncubatorTextFieldScreen'}
]
},
Inspirations: {
Expand Down
178 changes: 178 additions & 0 deletions demo/src/screens/componentScreens/IncubatorTextFieldScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import _ from 'lodash';
import React, {Component} from 'react';
import {TextInput, StyleSheet, ScrollView} from 'react-native';
import {
Assets,
Colors,
Spacings,
Typography,
View,
Text,
Button,
Incubator
} from 'react-native-ui-lib'; //eslint-disable-line
const {TextField} = Incubator;

export default class TextFieldScreen extends Component {
input = React.createRef<TextInput>();
input2 = React.createRef<TextInput>();
state = {
errorPosition: TextField.validationMessagePositions.TOP,
shouldDisable: false,
value: 'Initial Value'
};

componentDidMount() {
this.input.current?.focus();
}

resetFieldValue = () => {
this.input2.current?.clear();
}

render() {
const {errorPosition, shouldDisable} = this.state;
return (
<ScrollView keyboardShouldPersistTaps="always">
<View flex padding-page>
<Text h1>TextField</Text>
<Text h3 blue50 marginV-s4>
Default
</Text>
<TextField
ref={this.input}
label="Name"
placeholder="Enter first and last name"
/>

<Text h3 blue50 marginV-s4>
Static vs Floating Placeholder
</Text>
<View row bottom>
<TextField
placeholder="Floating placeholder"
floatingPlaceholder
floatingPlaceholderColor={{
focus: Colors.grey10,
default: Colors.grey30
}}
// floatingPlaceholderStyle={Typography.text60}
// style={Typography.text60}
containerStyle={{flex: 1}}
fieldStyle={styles.withUnderline}
/>
<TextField
placeholder="Placeholder"
containerStyle={{flex: 1, marginLeft: Spacings.s4}}
fieldStyle={styles.withUnderline}
/>
</View>
<Text h3 blue50 marginV-s4>
Leading/Trailing Button
</Text>

<TextField
ref={this.input2}
placeholder="Enter text..."
leadingButton={{iconSource: Assets.icons.demo.search}}
trailingButton={{iconSource: Assets.icons.demo.refresh, onPress: this.resetFieldValue}}
fieldStyle={styles.withUnderline}
/>

<View row marginV-s4 spread>
<Text h3 blue50>
Validation
</Text>
<Button
size={Button.sizes.xSmall}
label={`Error Position: ${_.upperCase(errorPosition)}`}
onPress={() =>
this.setState({
errorPosition:
errorPosition === TextField.validationMessagePositions.TOP
? TextField.validationMessagePositions.BOTTOM
: TextField.validationMessagePositions.TOP
})
}
/>
</View>

<TextField
value={this.state.value}
onChangeText={(value) => this.setState({value})}
label="Email"
placeholder="Enter email"
enableErrors
validationMessage="Email is invalid"
validationMessageStyle={Typography.text90R}
validationMessagePosition={errorPosition}
validate={'email'}
validateOnChange
// validateOnStart
// validateOnBlur
fieldStyle={styles.withUnderline}
/>

<View row centerV spread>
<Text h3 blue50 marginV-s4>
Colors By State
</Text>
<Button
label={shouldDisable ? 'Enable' : 'Disable'}
onPress={() => this.setState({shouldDisable: !shouldDisable})}
size={Button.sizes.xSmall}
/>
</View>

<TextField
label="Email"
labelColor={{default: Colors.grey10, focus: Colors.blue20, error: Colors.red30, disabled: Colors.grey40}}
placeholder="Enter valid email"
validationMessage="Email is invalid"
validate={'email'}
validateOnChange
fieldStyle={styles.withFrame}
editable={!shouldDisable}
/>

<Text h3 blue50 marginV-s4>
Char Counter
</Text>

<TextField
label="Description"
placeholder="Enter text..."
multiline
showCharCounter
charCounterStyle={{color: Colors.blue30}}
maxLength={20}
fieldStyle={styles.withFrame}
/>
<Text h3 blue50 marginV-s4>
Hint
</Text>
<TextField
placeholder="Enter password"
hint="1-6 chars including numeric chars"
fieldStyle={styles.withUnderline}
/>
</View>
</ScrollView>
);
}
}

const styles = StyleSheet.create({
container: {},
withUnderline: {
borderBottomWidth: 1,
borderColor: Colors.grey40,
paddingBottom: 4
},
withFrame: {
borderWidth: 1,
borderColor: Colors.grey40,
padding: 4,
borderRadius: 2
}
});
2 changes: 2 additions & 0 deletions demo/src/screens/componentScreens/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,7 @@ export function registerScreens(registrar) {
registrar('unicorn.components.WithScrollEnablerScreen', () => require('./WithScrollEnablerScreen').default);
registrar('unicorn.components.WithScrollReachedScreen', () => require('./WithScrollReachedScreen').default);
registrar('unicorn.components.FaderScreen', () => require('./FaderScreen').default);
// Incubator Screens
registrar('unicorn.components.IncubatorTextFieldScreen', () => require('./IncubatorTextFieldScreen').default);
}

4 changes: 4 additions & 0 deletions generatedTypes/incubator/TextField/AccessoryButton.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// <reference types="react" />
import { ButtonPropTypes } from '../../components/button';
declare const _default: (props: ButtonPropTypes) => JSX.Element;
export default _default;
9 changes: 9 additions & 0 deletions generatedTypes/incubator/TextField/CharCounter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference types="react" />
import { TextStyle } from 'react-native';
export interface CharCounterProps {
showCharCounter?: boolean;
maxLength?: number;
charCounterStyle?: TextStyle;
}
declare const _default: ({ maxLength, charCounterStyle }: CharCounterProps) => JSX.Element | null;
export default _default;
10 changes: 10 additions & 0 deletions generatedTypes/incubator/TextField/FieldContext.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference types="react" />
export declare type ContextType = {
value?: string;
isFocused: boolean;
hasValue: boolean;
isValid: boolean;
disabled: boolean;
};
declare const FieldContext: import("react").Context<ContextType>;
export default FieldContext;
10 changes: 10 additions & 0 deletions generatedTypes/incubator/TextField/FloatingPlaceholder.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference types="react" />
import { TextStyle } from 'react-native';
import { ColorType } from './types';
export interface FloatingPlaceholderProps {
placeholder?: string;
floatingPlaceholderColor?: ColorType;
floatingPlaceholderStyle?: TextStyle;
}
declare const _default: ({ placeholder, floatingPlaceholderColor, floatingPlaceholderStyle }: FloatingPlaceholderProps) => JSX.Element;
export default _default;
4 changes: 4 additions & 0 deletions generatedTypes/incubator/TextField/Icon.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// <reference types="react" />
import { ImageProps } from '../../components/image';
declare const _default: (props: ImageProps) => JSX.Element;
export default _default;
8 changes: 8 additions & 0 deletions generatedTypes/incubator/TextField/Input.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import { TextInput, TextInputProps } from 'react-native';
import { ForwardRefInjectedProps } from '../../commons/new';
export interface InputProps extends TextInputProps, React.ComponentPropsWithRef<typeof TextInput> {
hint?: string;
}
declare const Input: ({ style, hint, forwardedRef, ...props }: InputProps & ForwardRefInjectedProps) => JSX.Element;
export default Input;
13 changes: 13 additions & 0 deletions generatedTypes/incubator/TextField/Label.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference types="react" />
import { TextStyle } from 'react-native';
import { TextPropTypes } from '../../components/text';
import { ColorType, ValidationMessagePosition } from './types';
export interface LabelProps {
label?: string;
labelColor?: ColorType;
labelStyle?: TextStyle;
labelProps?: TextPropTypes;
validationMessagePosition?: ValidationMessagePosition;
}
declare const _default: ({ label, labelColor, labelStyle, labelProps, validationMessagePosition }: LabelProps) => JSX.Element | null;
export default _default;
3 changes: 3 additions & 0 deletions generatedTypes/incubator/TextField/Presenter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ContextType } from './FieldContext';
import { ColorType } from './types';
export declare function getColorByState(color: ColorType, context?: ContextType): string | undefined;
10 changes: 10 additions & 0 deletions generatedTypes/incubator/TextField/ValidationMessage.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference types="react" />
import { TextStyle } from 'react-native';
export interface ValidationMessageProps {
enableErrors?: boolean;
validationMessage?: string;
validationMessageStyle?: TextStyle;
retainSpace?: boolean;
}
declare const _default: ({ validationMessage, enableErrors, validationMessageStyle, retainSpace }: ValidationMessageProps) => JSX.Element | null;
export default _default;
26 changes: 26 additions & 0 deletions generatedTypes/incubator/TextField/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { ViewStyle, TextStyle } from 'react-native';
import { ButtonPropTypes } from '../../components/button';
import { ValidationMessagePosition } from './types';
import { InputProps } from './Input';
import { ValidationMessageProps } from './ValidationMessage';
import { LabelProps } from './Label';
import { FieldStateProps } from './withFieldState';
import { FloatingPlaceholderProps } from './FloatingPlaceholder';
import { CharCounterProps } from './CharCounter';
interface TextFieldProps extends InputProps, LabelProps, FloatingPlaceholderProps, FieldStateProps, ValidationMessageProps, Omit<CharCounterProps, 'maxLength'> {
leadingButton?: ButtonPropTypes;
trailingButton?: ButtonPropTypes;
floatingPlaceholder?: boolean;
floatingPlaceholderStyle?: TextStyle;
validationMessagePosition?: ValidationMessagePosition;
fieldStyle?: ViewStyle;
containerStyle?: ViewStyle;
}
interface StaticMembers {
validationMessagePositions: typeof ValidationMessagePosition;
}
declare const _default: React.ComponentClass<TextFieldProps & {
useCustomTheme?: boolean | undefined;
}, any> & StaticMembers;
export default _default;
10 changes: 10 additions & 0 deletions generatedTypes/incubator/TextField/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export declare type ColorType = string | {
default?: string;
focus?: string;
error?: string;
disabled?: string;
};
export declare enum ValidationMessagePosition {
TOP = "top",
BOTTOM = "bottom"
}
8 changes: 8 additions & 0 deletions generatedTypes/incubator/TextField/validators.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare const validators: {
required: (value: string) => boolean;
email: (value: string) => boolean;
url: (value: string) => boolean;
number: (value: string) => boolean;
price: (value: string) => boolean;
};
export default validators;
27 changes: 27 additions & 0 deletions generatedTypes/incubator/TextField/withFieldState.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { TextInputProps } from 'react-native';
import validators from './validators';
export declare type Validator = Function | keyof typeof validators;
interface FieldState {
value?: string;
isFocused: boolean;
isValid: boolean;
hasValue: boolean;
}
export interface FieldStateInjectedProps {
fieldState: FieldState;
onFocus: Function;
onBlur: Function;
ref?: any;
}
export interface FieldStateProps extends TextInputProps {
validateOnStart?: boolean;
validateOnChange?: boolean;
validateOnBlur?: boolean;
validate?: Validator | Validator[];
}
declare function withFieldState(WrappedComponent: React.ComponentType<FieldStateInjectedProps & TextInputProps>): {
({ validate, validateOnBlur, validateOnChange, validateOnStart, ...props }: FieldStateProps): JSX.Element;
displayName: string | undefined;
};
export default withFieldState;
1 change: 1 addition & 0 deletions generatedTypes/incubator/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as TabController } from './TabController';
export { default as TextField } from './TextField';
export { default as TouchableOpacity } from './TouchableOpacity';
8 changes: 8 additions & 0 deletions src/incubator/TextField/AccessoryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import Button, {ButtonPropTypes} from '../../components/button';

export default (props: ButtonPropTypes) => {
return (
<Button link grey10 activeOpacity={props.onPress ? 0.6 : 1} {...props} />
);
};
32 changes: 32 additions & 0 deletions src/incubator/TextField/CharCounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, {useContext} from 'react';
import {TextStyle, StyleSheet} from 'react-native';
import _ from 'lodash';
import Text from '../../components/text';
import FieldContext from './FieldContext';

export interface CharCounterProps {
showCharCounter?: boolean;
maxLength?: number;
charCounterStyle?: TextStyle;
}

export default ({maxLength, charCounterStyle}: CharCounterProps) => {
const {value} = useContext(FieldContext);

if (_.isUndefined(maxLength)) {
return null;
}

return (
<Text grey30 style={[styles.container, charCounterStyle]}>
{`${_.size(value)}/${maxLength}`}
</Text>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
textAlign: 'right'
}
});
Loading