Skip to content

Commit 1493474

Browse files
authored
Incubator.TextField - add readonly state (#2455)
* Incubator.TextField - add readonly state * Add readonly to API
1 parent 4d6f3b6 commit 1493474

File tree

11 files changed

+78
-17
lines changed

11 files changed

+78
-17
lines changed

demo/src/screens/incubatorScreens/IncubatorTextFieldScreen.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default class TextFieldScreen extends Component {
1414
state = {
1515
errorPosition: TextField.validationMessagePositions.TOP,
1616
shouldDisable: false,
17+
isReadonly: false,
1718
value: 'Initial Value',
1819
searching: false,
1920
preset: 'withUnderline'
@@ -50,7 +51,7 @@ export default class TextFieldScreen extends Component {
5051
}
5152

5253
render() {
53-
const {errorPosition, shouldDisable, price, preset} = this.state;
54+
const {errorPosition, shouldDisable, isReadonly, price, preset} = this.state;
5455
return (
5556
<ScrollView keyboardShouldPersistTaps="always">
5657
<View flex padding-page>
@@ -182,11 +183,19 @@ export default class TextFieldScreen extends Component {
182183
<Text h3 blue50 marginV-s4>
183184
Colors By State
184185
</Text>
185-
<Button
186-
label={shouldDisable ? 'Enable' : 'Disable'}
187-
onPress={() => this.setState({shouldDisable: !shouldDisable})}
188-
size={Button.sizes.xSmall}
189-
/>
186+
<View row>
187+
<Button
188+
label={isReadonly ? 'Enable' : 'Readonly'}
189+
onPress={() => this.setState({isReadonly: !isReadonly})}
190+
size={Button.sizes.xSmall}
191+
marginR-s4
192+
/>
193+
<Button
194+
label={shouldDisable ? 'Enable' : 'Disable'}
195+
onPress={() => this.setState({shouldDisable: !shouldDisable})}
196+
size={Button.sizes.xSmall}
197+
/>
198+
</View>
190199
</View>
191200

192201
<TextField
@@ -195,14 +204,16 @@ export default class TextFieldScreen extends Component {
195204
default: Colors.$textDefault,
196205
focus: Colors.$textGeneral,
197206
error: Colors.$textDangerLight,
198-
disabled: Colors.$textDisabled
207+
disabled: Colors.$textDisabled,
208+
readonly: Colors.$textNeutral
199209
}}
200210
placeholder="Enter valid email"
201211
validationMessage="Email is invalid"
202212
validate={'email'}
203213
validateOnChange
204214
fieldStyle={styles.withFrame}
205215
editable={!shouldDisable}
216+
readonly={isReadonly}
206217
/>
207218

208219
<View row spread centerV>
@@ -224,6 +235,7 @@ export default class TextFieldScreen extends Component {
224235
preset === 'withUnderline' ? styles.withUnderline : styles.withFrame
225236
}
226237
editable={!shouldDisable}
238+
readonly={isReadonly}
227239
/>
228240

229241
<Text h3 blue50 marginV-s4>

src/incubator/TextField/FieldContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const FieldContext = createContext<FieldContextType>({
88
isValid: true,
99
failingValidatorIndex: undefined,
1010
disabled: false,
11+
readonly: false,
1112
validateField: _.noop,
1213
checkValidity: () => true
1314
});

src/incubator/TextField/Input.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import useImperativeInputHandle from './useImperativeInputHandle';
99

1010
const DEFAULT_INPUT_COLOR: ColorType = {
1111
default: Colors.$textDefault,
12-
disabled: Colors.$textDisabled
12+
disabled: Colors.$textDisabled,
13+
readonly: Colors.$textNeutral
1314
};
1415

1516
const Input = ({
@@ -19,6 +20,7 @@ const Input = ({
1920
forwardedRef,
2021
formatter,
2122
useGestureHandlerInput,
23+
readonly,
2224
...props
2325
}: InputProps & ForwardRefInjectedProps) => {
2426
const inputRef = useImperativeInputHandle(forwardedRef, {onChangeText: props.onChangeText});
@@ -27,6 +29,7 @@ const Input = ({
2729
const inputColor = getColorByState(color, context);
2830
const placeholderTextColor = getColorByState(props.placeholderTextColor, context);
2931
const value = formatter && !context.isFocused ? formatter(props.value) : props.value;
32+
const disabled = props.editable === false || readonly;
3033

3134
const TextInput = useMemo(() => {
3235
if (useGestureHandlerInput) {
@@ -43,13 +46,14 @@ const Input = ({
4346
<TextInput
4447
style={[styles.input, !!inputColor && {color: inputColor}, style, Constants.isWeb && styles.webStyle]}
4548
{...props}
49+
editable={!disabled}
4650
value={value}
4751
placeholder={placeholder}
4852
placeholderTextColor={placeholderTextColor}
4953
// @ts-expect-error
5054
ref={inputRef}
5155
underlineColorAndroid="transparent"
52-
accessibilityState={{disabled: props.editable === false}}
56+
accessibilityState={{disabled}}
5357
/>
5458
);
5559
};

src/incubator/TextField/Label.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import React, {useContext, useMemo} from 'react';
22
import {StyleSheet} from 'react-native';
33
import {Colors} from '../../style';
44
import Text from '../../components/text';
5-
import {LabelProps, ValidationMessagePosition} from './types';
5+
import {ColorType, LabelProps, ValidationMessagePosition} from './types';
66
import {getColorByState} from './Presenter';
77
import FieldContext from './FieldContext';
88

99

10+
const DEFAULT_LABEL_COLOR: ColorType = {
11+
default: Colors.$textDefault,
12+
readonly: Colors.$textNeutral
13+
};
14+
1015
const Label = ({
1116
label,
12-
labelColor = Colors.$textDefault,
17+
labelColor = DEFAULT_LABEL_COLOR,
1318
labelStyle,
1419
labelProps,
1520
validationMessagePosition,

src/incubator/TextField/Presenter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export function getColorByState(color?: ColorType, context?: FieldContextType) {
1414
} else if (_.isPlainObject(color)) {
1515
if (context?.disabled) {
1616
finalColor = color?.disabled;
17+
} else if (context?.readonly) {
18+
finalColor = color?.readonly;
1719
} else if (!context?.isValid) {
1820
finalColor = color?.error;
1921
} else if (context?.isFocused) {

src/incubator/TextField/TextField.driver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class TextFieldDriver extends ComponentDriver {
3131
if (await this.exists()) {
3232
return (await this.getElementProps()).accessibilityState.disabled;
3333
} else {
34-
console.warn(`TextField component with testId:${this.testID}, is not found. So you can't get the content`);
34+
console.warn(`TextField component with testId:${this.testID}, is not found. So you can't get the disabled state`);
3535
return null;
3636
}
3737
}

src/incubator/TextField/__tests__/index.driver.spec.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface TextFieldProps {
1616
validateOnChange?: boolean;
1717
enableErrors?: boolean;
1818
editable?: boolean;
19+
readonly?: boolean;
1920
floatingPlaceholder?: boolean;
2021
showCharCounter?: boolean;
2122
maxLength?: number;
@@ -73,6 +74,22 @@ describe('TextField', () => {
7374
});
7475
});
7576

77+
describe('readonly', () => {
78+
it('should render textField that is not readonly', async () => {
79+
const component = <TestCase/>;
80+
const textFieldDriver = new TextFieldDriver({component, testID: TEXT_FIELD_TEST_ID});
81+
82+
expect(await textFieldDriver.isDisabled()).toBe(false);
83+
});
84+
85+
it('should be readonly', async () => {
86+
const component = <TestCase readonly/>;
87+
const textFieldDriver = new TextFieldDriver({component, testID: TEXT_FIELD_TEST_ID});
88+
89+
expect(await textFieldDriver.isDisabled()).toBe(true);
90+
});
91+
});
92+
7693
describe('placeholder', () => {
7794
it('should render placeholder with correct text', async () => {
7895
const component = <TestCase placeholder={'mock placeholder'}/>;

src/incubator/TextField/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,21 @@ const TextField = (props: InternalTextFieldProps) => {
7373
placeholder,
7474
children,
7575
centered,
76+
readonly = false,
7677
...others
7778
} = usePreset(props);
7879
const {ref: leadingAccessoryRef, measurements: leadingAccessoryMeasurements} = useMeasure();
7980
const {onFocus, onBlur, onChangeText, fieldState, validateField, checkValidity} = useFieldState(others);
8081

8182
const context = useMemo(() => {
82-
return {...fieldState, disabled: others.editable === false, validateField, checkValidity};
83-
}, [fieldState, others.editable, validateField, checkValidity]);
83+
return {
84+
...fieldState,
85+
disabled: others.editable === false,
86+
readonly,
87+
validateField,
88+
checkValidity
89+
};
90+
}, [fieldState, others.editable, readonly, validateField, checkValidity]);
8491

8592
const leadingAccessoryClone = useMemo(() => {
8693
if (leadingAccessory) {
@@ -165,6 +172,7 @@ const TextField = (props: InternalTextFieldProps) => {
165172
placeholderTextColor={hidePlaceholder ? 'transparent' : placeholderTextColor}
166173
value={fieldState.value}
167174
{...others}
175+
readonly={readonly}
168176
style={inputStyle}
169177
onFocus={onFocus}
170178
onBlur={onBlur}

src/incubator/TextField/presets/default.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {Colors, Spacings, Typography} from '../../../style';
44
const colorByState = {
55
focus: Colors.$textPrimary,
66
error: Colors.$textDangerLight,
7-
disabled: Colors.$textDisabled
7+
disabled: Colors.$textDisabled,
8+
readonly: Colors.$textNeutral
89
};
910

1011
const placeholderTextColorByState = {

src/incubator/TextField/textField.api.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
{
1515
"name": "labelColor",
1616
"type": "ColorType",
17-
"description": "Field label color. Either a string or color by state map ({default, focus, error, disabled})"
17+
"description": "Field label color. Either a string or color by state map ({default, focus, error, disabled, readonly})"
1818
},
1919
{"name": "labelStyle", "type": "TextStyle", "description": "Custom style for the field label"},
2020
{"name": "labelProps", "type": "TextProps", "description": "Pass extra props to the label Text element"},
@@ -97,6 +97,11 @@
9797
"name": "useGestureHandlerInput",
9898
"type": "boolean",
9999
"description": "Use react-native-gesture-handler instead of react-native for the base TextInput"
100+
},
101+
{
102+
"name": "readonly",
103+
"type": "boolean",
104+
"description": "A UI preset for read only state"
100105
}
101106
],
102107
"snippet": [

src/incubator/TextField/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type ColorType =
1818
focus?: string;
1919
error?: string;
2020
disabled?: string;
21+
readonly?: string;
2122
};
2223

2324
export enum ValidationMessagePosition {
@@ -51,7 +52,7 @@ export interface LabelProps {
5152
*/
5253
label?: string;
5354
/**
54-
* Field label color. Either a string or color by state map ({default, focus, error, disabled})
55+
* Field label color. Either a string or color by state map ({default, focus, error, disabled, readonly})
5556
*/
5657
labelColor?: ColorType;
5758
/**
@@ -144,6 +145,10 @@ export interface InputProps
144145
* Use react-native-gesture-handler instead of react-native for the base TextInput
145146
*/
146147
useGestureHandlerInput?: boolean;
148+
/**
149+
* A UI preset for read only state
150+
*/
151+
readonly?: boolean;
147152
}
148153

149154
export type TextFieldProps = MarginModifiers &
@@ -239,6 +244,7 @@ export type FieldContextType = {
239244
isValid: boolean;
240245
failingValidatorIndex?: number;
241246
disabled: boolean;
247+
readonly: boolean;
242248
validateField: () => void;
243249
checkValidity: () => boolean;
244250
};

0 commit comments

Comments
 (0)