Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
69 changes: 68 additions & 1 deletion packages/design-system-react-native/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,73 @@ These tokens had no backing CSS custom property, so any usage was already produc

- Any reference to the removed entries will produce a TypeScript error after upgrading.

---

#### TextField and TextFieldSearch: layered props (`inputProps` and root `Box`)

**What changed:**

- **`TextField`** is a root **`Box`** (a styled **`View`**) with an inner **`Input`**. Props that belong on the native text control must be passed in **`inputProps`** (for example `keyboardType`, `secureTextEntry`, `returnKeyType`, `autoCapitalize`, `accessibilityLabel`, `accessibilityState`).
- **`placeholder`**, **`isReadOnly`**, **`onFocus`**, and **`onBlur`** are owned at the **`TextField` / `TextFieldSearch` top level** and forwarded to the inner `Input`. Do not pass them only through **`inputProps`**. The prop **`isReadonly`** was renamed to **`isReadOnly`** (aligned with React / React Native spelling).
- **`placeholderTextColor`** is not supported on the public **`TextField`** API; the inner **`Input`** sets placeholder color from the theme.
- Remaining top-level props on **`TextField`** are **`BoxProps`** (layout and **`View`** props from React Native), except for keys reserved by **`TextField`** (see the exported type **`TextFieldProps`** in **`@metamask/design-system-react-native`**). **`hitSlop`**, **`onPress`**, and other **`Pressable`**-only APIs are not supported on the root; tap-to-focus on the chrome is removed—users focus by tapping the **`Input`** / **`TextInput`**.
- **`TextFieldSearchProps`** extends **`TextFieldProps`**; the same layering applies. **`onPressClearButton`**, **`clearButtonProps`**, **`startAccessory`**, **`endAccessory`**, and **`style`** behavior are unchanged.
- **`ref`** on **`TextField`** / **`TextFieldSearch`** refers to the **root** **`Box`** (**`View`**). Use **`inputRef`** for the inner **`TextInput`** (for example **`focus()`** / **`blur()`**).
- Top-level **`testID`** applies to the **wrapper** **`Box`**. To query the editable **`TextInput`** in E2E tests, use **`inputProps.testID`** (or accessibility / placeholder queries).

**Migration:**

Move inner `TextInput` props from the root into **`inputProps`**. Keep **`placeholder`**, **`onFocus`**, and **`onBlur`** on the component root when you use them.
Comment thread
brianacnguyen marked this conversation as resolved.

Replace **`isReadonly`** with **`isReadOnly`** on **`TextField`**, **`TextFieldSearch`**, and **`Input`** in **`@metamask/design-system-react-native`**. The **`Input`** in **`@metamask/design-system-react`** keeps the **`isReadonly`** prop name.

If you passed **`ref`** expecting the **`TextInput`**, switch imperative usage to **`inputRef`** and use **`ref`** only when you need the outer container (layout / measurement).

```tsx
// Before (0.19.0) — native TextInput props on TextField
<TextField
value={query}
onChangeText={setQuery}
placeholder="Search"
keyboardType="default"
secureTextEntry
onFocus={handleFocus}
/>

// After (0.20.0)
<TextField
value={query}
onChangeText={setQuery}
placeholder="Search"
onFocus={handleFocus}
inputProps={{
keyboardType: 'default',
secureTextEntry: true,
}}
/>
```

If you relied on **`hitSlop`** or a larger tap target on the field chrome, wrap **`TextField`** in your own **`Pressable`** (or enlarge the inner input via **`inputProps`**) at the app level.

Remove **`placeholderTextColor`** from **`TextField`** call sites; rely on theme behavior from **`Input`**.

**Impact:**

- Any **`TextField`** or **`TextFieldSearch`** usage that spread or passed **`TextInput`** props on the root must move those keys into **`inputProps`**, except for the props **`TextField`** owns (**`value`**, **`onChangeText`**, **`placeholder`**, **`isReadOnly`**, **`onFocus`**, **`onBlur`**, **`isDisabled`**, **`autoFocus`**, **`isError`**, accessories, **`inputElement`**, **`inputRef`**, **`testID`**, **`style`**, **`twClassName`**) and valid **`BoxProps`** / **`View`** props you pass at the top level.
- Call sites that passed **`Pressable`**-only props (**`hitSlop`**, root **`onPress`**, root **`disabled`**) must be updated: the root is no longer a **`Pressable`**.
- Type-only consumers can extend or intersect **`TextFieldProps`** from **`@metamask/design-system-react-native`** for typed wrappers or form helpers. Derive the inner input prop bag with **`TextFieldProps['inputProps']`** when needed.

#### Input: theme `placeholderTextColor` always wins

**What changed:**

**`Input`** used to pass **`placeholderTextColor`** on the native **`TextInput`** **before** **`{...props}`**, so a **`placeholderTextColor`** included in **`props`** could override the theme-derived color. **`Input`** now passes **`placeholderTextColor`** **after** **`{...props}`**, so the **theme token for placeholder text is always applied** and **is not overridden** by caller props.

**Impact:**

- Passing **`placeholderTextColor`** on **`Input`** has **no effect** on the rendered placeholder tint; remove dead props if you had any.
- **`TextField`** already omits **`placeholderTextColor`** from its public API and forwards inner **`Input`** behavior only.

### From version 0.18.0 to 0.19.0

#### HeaderRoot: `titleAccessory` no longer renders without `title`
Expand Down Expand Up @@ -2307,7 +2374,7 @@ These props work identically in both versions — no migration needed:
| `onBlur` | `(e) => void` | Blur handler (skipped when disabled) |
| `isError` | `boolean` | Error border state |
| `isDisabled` | `boolean` | Disabled state (opacity + no interaction) |
| `isReadonly` | `boolean` | Read-only state |
| `isReadOnly` | `boolean` | Read-only state |
| `autoFocus` | `boolean` | Auto-focus on mount |
| `startAccessory` | `ReactNode` | Content before the input |
| `endAccessory` | `ReactNode` | Content after the input |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const meta: Meta<InputProps> = {
isDisabled: {
control: 'boolean',
},
isReadonly: {
isReadOnly: {
control: 'boolean',
},
isStateStylesDisabled: {
Expand Down Expand Up @@ -87,11 +87,11 @@ export const IsDisabled: Story = {
),
};

export const IsReadonly: Story = {
export const IsReadOnly: Story = {
render: () => (
<View style={{ gap: 16 }}>
<ControlledInput placeholder="Editable" value="" />
<Input placeholder="Readonly" value="Read-only value" isReadonly />
<Input placeholder="Readonly" value="Read-only value" isReadOnly />
</View>
),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Input = forwardRef<TextInput, InputProps>(
textVariant = TextVariant.BodyMd,
isStateStylesDisabled,
isDisabled = false,
isReadonly = false,
isReadOnly = false,
value,
placeholder,
twClassName,
Expand Down Expand Up @@ -121,12 +121,12 @@ export const Input = forwardRef<TextInput, InputProps>(
return (
<TextInput
ref={ref}
placeholderTextColor={placeholderTextColor}
{...props}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
value={value}
style={resolvedStyle}
editable={!isDisabled && !isReadonly}
editable={!isDisabled && !isReadOnly}
autoFocus={autoFocus}
onBlur={onBlurHandler}
onFocus={onFocusHandler}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type InputProps = Omit<
*
* @default false
*/
isReadonly?: boolean;
isReadOnly?: boolean;
/**
* Optional boolean to disable state styles.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Optional boolean to disable Input.
| --------- | -------- | ------- |
| `boolean` | No | `false` |

### `isReadonly`
### `isReadOnly`

Optional boolean to show readonly input.

Expand Down
Loading
Loading