Skip to content

Commit 08deb1c

Browse files
authored
textField - a11y label improvements, testID fix. (#3947)
* fix: extract testID from usePreset instead of using props.testID in TextField * Integrated back a11y logic added in the previous PR and reverted * fix: move testID to accessible container and update driver to target input sub-element
1 parent efc5522 commit 08deb1c

File tree

2 files changed

+50
-15
lines changed

2 files changed

+50
-15
lines changed

packages/react-native-ui-lib/src/components/textField/TextField.driver.new.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {ViewDriver} from '../view/View.driver.new';
1010

1111
export const TextFieldDriver = (props: ComponentProps, options?: ComponentDriverOptions) => {
1212
const driver = usePressableDriver(useComponentDriver(props, options));
13+
const inputDriver = useComponentDriver({renderTree: props.renderTree, testID: `${props.testID}.input`}, options);
1314

1415
const floatingPlaceholderDriver = TextDriver({
1516
renderTree: props.renderTree,
@@ -44,35 +45,37 @@ export const TextFieldDriver = (props: ComponentProps, options?: ComponentDriver
4445
testID: `${props.testID}.clearButton.container`
4546
});
4647

48+
const getInputElement = () => inputDriver.queryElement() ?? driver.getElement();
49+
4750
const getValue = (): string | undefined => {
48-
return driver.getElement().props.value ?? driver.getElement().props.defaultValue;
51+
return getInputElement().props.value ?? getInputElement().props.defaultValue;
4952
};
5053

5154
const changeText = (text: string): void => {
52-
fireEvent.changeText(driver.getElement(), text);
55+
fireEvent.changeText(getInputElement(), text);
5356
};
5457

5558
const focus = (): void => {
56-
fireEvent(driver.getElement(), 'focus');
59+
fireEvent(getInputElement(), 'focus');
5760
};
5861

5962
const blur = (): void => {
60-
fireEvent(driver.getElement(), 'blur');
63+
fireEvent(getInputElement(), 'blur');
6164
};
6265

6366
const isEnabled = (): boolean => {
64-
return !driver.getElement().props.accessibilityState?.disabled;
67+
return !getInputElement().props.accessibilityState?.disabled;
6568
};
6669

6770
const getPlaceholder = () => {
6871
const exists = (): boolean => {
69-
const hasPlaceholder = !!driver.getElement().props.placeholder;
72+
const hasPlaceholder = !!getInputElement().props.placeholder;
7073
const hasText = !!getValue();
7174
return hasPlaceholder && (!hasText || (hasText && floatingPlaceholderDriver.exists()));
7275
};
7376
const getText = (): string | undefined => {
7477
if (exists()) {
75-
return driver.getElement().props.placeholder;
78+
return getInputElement().props.placeholder;
7679
}
7780
};
7881

packages/react-native-ui-lib/src/components/textField/index.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ const TextField = (props: InternalTextFieldProps) => {
9292
readonly = false,
9393
showMandatoryIndication,
9494
clearButtonStyle,
95+
testID,
96+
accessibilityLabel: accessibilityLabelProp,
9597
...others
9698
} = usePreset(props);
9799

@@ -138,9 +140,38 @@ const TextField = (props: InternalTextFieldProps) => {
138140
[typographyStyle, colorStyle, others.style, centeredTextStyle, hasValue]);
139141
const dummyPlaceholderStyle = useMemo(() => [inputStyle, styles.dummyPlaceholder], [inputStyle]);
140142

143+
const defaultAccessibilityLabel = useMemo(() => {
144+
const parts: string[] = [];
145+
146+
if (label) {
147+
parts.push(label);
148+
}
149+
150+
if (context.isMandatory) {
151+
parts.push('required');
152+
}
153+
154+
parts.push('textField');
155+
156+
if (helperText) {
157+
parts.push(helperText);
158+
} else if (placeholder) {
159+
parts.push(placeholder);
160+
}
161+
162+
if (showCharCounter && others.maxLength) {
163+
parts.push(`you can enter up to ${others.maxLength} characters`);
164+
}
165+
166+
return parts.join(', ');
167+
168+
}, [label, context.isMandatory, helperText, placeholder, showCharCounter, others.maxLength]);
169+
170+
const accessibilityLabel = accessibilityLabelProp ?? defaultAccessibilityLabel;
171+
141172
return (
142173
<FieldContext.Provider value={context}>
143-
<View {...containerProps} style={[margins, positionStyle, containerStyle, centeredContainerStyle]}>
174+
<View {...containerProps} testID={testID} accessible accessibilityLabel={accessibilityLabel} style={[margins, positionStyle, containerStyle, centeredContainerStyle]}>
144175
<View row spread style={centeredContainerStyle}>
145176
<Label
146177
label={label}
@@ -149,7 +180,7 @@ const TextField = (props: InternalTextFieldProps) => {
149180
labelProps={labelProps}
150181
floatingPlaceholder={floatingPlaceholder}
151182
validationMessagePosition={validationMessagePosition}
152-
testID={`${props.testID}.label`}
183+
testID={`${testID}.label`}
153184
showMandatoryIndication={showMandatoryIndication}
154185
enableErrors={enableErrors}
155186
/>
@@ -160,7 +191,7 @@ const TextField = (props: InternalTextFieldProps) => {
160191
validationMessage={others.validationMessage}
161192
validationMessageStyle={_validationMessageStyle}
162193
retainValidationSpace={retainValidationSpace && retainTopMessageSpace}
163-
testID={`${props.testID}.validationMessage`}
194+
testID={`${testID}.validationMessage`}
164195
/>
165196
)}
166197
{topTrailingAccessory && <View>{topTrailingAccessory}</View>}
@@ -189,7 +220,7 @@ const TextField = (props: InternalTextFieldProps) => {
189220
floatOnFocus={floatOnFocus}
190221
validationMessagePosition={validationMessagePosition}
191222
extraOffset={leadingAccessoryMeasurements?.width}
192-
testID={`${props.testID}.floatingPlaceholder`}
223+
testID={`${testID}.floatingPlaceholder`}
193224
showMandatoryIndication={showMandatoryIndication}
194225
/>
195226
)}
@@ -198,6 +229,7 @@ const TextField = (props: InternalTextFieldProps) => {
198229
placeholderTextColor={hidePlaceholder ? 'transparent' : placeholderTextColor}
199230
value={fieldState.value}
200231
{...others}
232+
testID={`${testID}.input`}
201233
readonly={readonly}
202234
style={inputStyle}
203235
onFocus={onFocus}
@@ -212,7 +244,7 @@ const TextField = (props: InternalTextFieldProps) => {
212244
{showClearButton && (
213245
<ClearButton
214246
onClear={onClear}
215-
testID={`${props.testID}.clearButton`}
247+
testID={`${testID}.clearButton`}
216248
onChangeText={onChangeText}
217249
clearButtonStyle={clearButtonStyle}
218250
/>
@@ -230,11 +262,11 @@ const TextField = (props: InternalTextFieldProps) => {
230262
validationIcon={validationIcon}
231263
validationMessageStyle={_validationMessageStyle}
232264
retainValidationSpace={retainValidationSpace}
233-
testID={`${props.testID}.validationMessage`}
265+
testID={`${testID}.validationMessage`}
234266
/>
235267
)}
236268
{helperText && (
237-
<Text $textNeutralHeavy subtext marginT-s1 testID={`${props.testID}.helperText`}>
269+
<Text $textNeutralHeavy subtext marginT-s1 testID={`${testID}.helperText`}>
238270
{helperText}
239271
</Text>
240272
)}
@@ -245,7 +277,7 @@ const TextField = (props: InternalTextFieldProps) => {
245277
<CharCounter
246278
maxLength={others.maxLength}
247279
charCounterStyle={charCounterStyle}
248-
testID={`${props.testID}.charCounter`}
280+
testID={`${testID}.charCounter`}
249281
/>
250282
)}
251283
</View>

0 commit comments

Comments
 (0)