Skip to content

Commit 0050bfb

Browse files
committed
feat: api unification for status handling
1 parent 470f9cc commit 0050bfb

7 files changed

Lines changed: 54 additions & 98 deletions

File tree

example/src/Examples/TextFieldExample.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,6 @@ const TextFieldDemo = ({ variant }: TextFieldDemoProps) => {
8282
const setModifier = (key: keyof DemoModifiers, text: string) =>
8383
setModifiers((prev) => ({ ...prev, [key]: text }));
8484

85-
const status =
86-
controls.error || controls.disabled
87-
? [
88-
...(controls.error ? (['error'] as const) : []),
89-
...(controls.disabled ? (['disabled'] as const) : []),
90-
]
91-
: undefined;
92-
9385
const LeadingIcon = React.useCallback(
9486
(props: TextFieldAccessoryProps) => (
9587
<TextField.Icon {...props} icon="magnify" />
@@ -143,7 +135,8 @@ const TextFieldDemo = ({ variant }: TextFieldDemoProps) => {
143135
label={modifiers.label || undefined}
144136
placeholder={modifiers.placeholder || undefined}
145137
supportingText={modifiers.helperText || undefined}
146-
status={status}
138+
error={controls.error}
139+
editable={!controls.disabled}
147140
value={value}
148141
onChangeText={setValue}
149142
multiline={controls.multiline}

src/components/TextField/TextField.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ import type { InternalTheme, ThemeProp } from '../../types';
2323

2424
export type TextFieldVariant = 'filled' | 'outlined';
2525

26-
export type TextFieldStatus = 'error' | 'disabled' | ('error' | 'disabled')[];
27-
2826
export interface TextFieldAccessoryProps {
2927
style: StyleProp<ViewStyle>;
3028
multiline: boolean;
31-
editable: boolean;
32-
status?: TextFieldStatus;
29+
disabled: boolean;
30+
error: boolean;
3331
}
3432

3533
export type TextFieldSharedApi = {
@@ -123,10 +121,9 @@ export interface TextFieldProps extends TextInputProps {
123121
*/
124122
variant?: TextFieldVariant;
125123
/**
126-
* A style modifier for different input states. Accepts an array so both
127-
* `'error'` and `'disabled'` can be active simultaneously.
124+
* When `true`, the field uses error styling and validation semantics (`aria-invalid`).
128125
*/
129-
status?: TextFieldStatus;
126+
error?: boolean;
130127
/**
131128
* The label text to display above the input.
132129
*/
@@ -137,7 +134,7 @@ export interface TextFieldProps extends TextInputProps {
137134
labelProps?: TextProps;
138135
/**
139136
* Supporting text to display below the input (Material Design 3). When
140-
* `status` is `error`, this text is styled as an error message.
137+
* `error` is `true`, this text is styled as an error message.
141138
*/
142139
supportingText?: string;
143140
/**
@@ -213,10 +210,10 @@ export interface TextFieldProps extends TextInputProps {
213210
* <TextField.Icon {...props} icon="magnify" />
214211
* );
215212
*
216-
* const ClearAccessory = ({ style, editable }) => (
213+
* const ClearAccessory = ({ style, disabled }) => (
217214
* <Pressable
218215
* style={style}
219-
* disabled={!editable}
216+
* disabled={disabled}
220217
* onPress={() => setText('')}
221218
* accessibilityRole="button"
222219
* accessibilityLabel="Clear text"
@@ -245,7 +242,7 @@ function TextField(props: TextFieldProps) {
245242
/* eslint-disable @typescript-eslint/no-unused-vars -- peel TextField-only props before TextInput spread */
246243
const {
247244
ref,
248-
status,
245+
error,
249246
label,
250247
supportingText,
251248
supportingTextProps,
@@ -345,8 +342,8 @@ function TextField(props: TextFieldProps) {
345342
{!!LeadingAccessory && (
346343
<LeadingAccessory
347344
style={$leadingAccessoryStyles}
348-
status={status}
349-
editable={!disabled}
345+
error={hasError}
346+
disabled={disabled}
350347
multiline={!!textInputProps.multiline}
351348
/>
352349
)}
@@ -363,7 +360,6 @@ function TextField(props: TextFieldProps) {
363360
aria-disabled={disabled}
364361
aria-invalid={hasError}
365362
ref={input}
366-
editable={!disabled}
367363
onFocus={onFocusHandler}
368364
onBlur={onBlurHandler}
369365
selectionColor={$selectionColor}
@@ -384,8 +380,8 @@ function TextField(props: TextFieldProps) {
384380
{TrailingAccessory ? (
385381
<TrailingAccessory
386382
style={$trailingAccessoryStyles}
387-
status={status}
388-
editable={!disabled}
383+
error={hasError}
384+
disabled={disabled}
389385
multiline={!!textInputProps.multiline}
390386
/>
391387
) : hasError ? (

src/components/TextField/TextFieldIcon.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { IconSource } from '../Icon';
77
import { ACCESSORY_SIZE } from './constants';
88
import { $iconStyle, $iconWrapperStyle } from './styles';
99
import type { TextFieldAccessoryProps } from './TextField';
10-
import { getIconColor, parseStatus } from './utils';
10+
import { getIconColor } from './utils';
1111
import IconButton from '../IconButton/IconButton';
1212

1313
export interface TextFieldIconProps extends TextFieldAccessoryProps {
@@ -74,26 +74,24 @@ const TextFieldIcon = ({
7474
color,
7575
size,
7676
style,
77-
status,
78-
editable,
77+
error,
78+
disabled,
7979
accessibility,
8080
theme: themeOverride,
8181
onPress,
8282
}: TextFieldIconProps) => {
8383
const theme = useInternalTheme(themeOverride);
8484

85-
const { hasError, disabled } = parseStatus(status);
86-
8785
const iconSize = size ?? ACCESSORY_SIZE;
8886

8987
const iconColor = getIconColor({
9088
theme,
9189
color,
92-
hasError,
90+
hasError: error,
9391
disabled,
9492
});
9593

96-
const onPressHandler = editable ? onPress : undefined;
94+
const onPressHandler = disabled ? undefined : onPress;
9795

9896
return (
9997
<View style={[$iconWrapperStyle, style]}>
@@ -103,7 +101,6 @@ const TextFieldIcon = ({
103101
size={iconSize}
104102
style={$iconStyle}
105103
onPress={onPressHandler}
106-
disabled={!editable}
107104
{...accessibility}
108105
/>
109106
</View>

src/components/TextField/logic.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import type {
3030
TextFieldProps,
3131
TextFieldSharedApi,
3232
} from './TextField';
33-
import { getAccentColors, parseStatus } from './utils';
33+
import { getAccentColors } from './utils';
3434
import { useInternalTheme } from '../../core/theming';
3535

3636
export const useTextField = (props: TextFieldProps): TextFieldHookReturn => {
@@ -59,9 +59,9 @@ export const useTextField = (props: TextFieldProps): TextFieldHookReturn => {
5959
// =======================
6060

6161
const { isRTL } = I18nManager.getConstants();
62-
const { hasError, disabled: isDisabled } = parseStatus(props.status);
63-
const disabled = props.editable === false || isDisabled;
62+
const disabled = props.editable === false;
6463
const isFloating = isFocused || !!props.value;
64+
const hasError = !!props.error;
6565
const hasAccessory = isRTL ? !!props.EndAccessory : !!props.StartAccessory;
6666
const hasPrefix = !!props.prefix && isFloating;
6767
const hasSuffix = !!props.suffix && isFloating;

src/components/TextField/utils.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,10 @@ import {
1818
import type {
1919
TextFieldProps,
2020
TextFieldSharedApi,
21-
TextFieldStatus,
2221
SharedTextFieldStyleData,
2322
} from './TextField';
2423
import type { InternalTheme } from '../../types';
2524

26-
export const parseStatus = (
27-
status: TextFieldStatus | undefined
28-
): { hasError: boolean; disabled: boolean } => {
29-
if (!status) return { hasError: false, disabled: false };
30-
31-
const list = typeof status === 'string' ? [status] : status;
32-
33-
return {
34-
hasError: list.includes('error'),
35-
disabled: list.includes('disabled'),
36-
};
37-
};
38-
3925
export const getAccentColors = ({
4026
theme,
4127
hasError,

0 commit comments

Comments
 (0)