chore: Align React Native and Expo dependencies with mobile#1165
Conversation
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. Ignoring alerts on:
|
📖 Storybook Preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Metro babel transformer not bumped with RN upgrade
- Replaced the old metro-react-native-babel-transformer 0.76.9 with @react-native/metro-babel-transformer 0.81.5 to align with React Native 0.81.5.
Or push these changes by commenting:
@cursor push 484fc62ea1
Preview (484fc62ea1)
diff --git a/packages/design-system-react-native/package.json b/packages/design-system-react-native/package.json
--- a/packages/design-system-react-native/package.json
+++ b/packages/design-system-react-native/package.json
@@ -75,7 +75,7 @@
"babel-jest": "^29.7.0",
"deepmerge": "^4.2.2",
"jest": "^29.7.0",
- "metro-react-native-babel-transformer": "0.76.9",
+ "@react-native/metro-babel-transformer": "0.81.5",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",You can send follow-ups to the cloud agent here.
| -import NoControlsWarning from './NoControlsWarning'; | ||
| -import PropForm from './PropForm'; | ||
| -import { useArgs } from './hooks'; | ||
| +import NoControlsWarning from './NoControlsWarning.js'; |
There was a problem hiding this comment.
The entire patch adds explicit .js file extensions to every internal import across the compiled dist/ files. Metro bundler (the React Native bundler used by Expo) requires ESM imports to include the full file extension — without them it cannot resolve sibling modules. This is an upstream packaging bug in @storybook/addon-ondevice-controls@10.4.0 that has not yet been released as a fix, so a yarn patch is the correct approach here. See the yarn patch documentation for how this works.
This patch can be removed once @storybook/addon-ondevice-controls releases a version that includes this fix and we upgrade to it. Track upstream progress at the storybook-react-native releases page. When upgrading, check whether the new version ships corrected ESM imports — if it does, remove both this patch file and the patch: protocol prefix from the version specifier in apps/storybook-react-native/package.json.
| '../stories/**/*.stories.@(js|jsx|ts|tsx)', | ||
| ], | ||
| addons: [ | ||
| deviceAddons: [ |
There was a problem hiding this comment.
deviceAddons is the Storybook 10 rename of addons in the React Native config. The previous key name addons no longer works in @storybook/react-native@10 — using it silently results in no addons loading on-device. See the Storybook React Native migration guide for the full list of config key renames in v10.
| "scripts": { | ||
| "android": "yarn prestorybook && expo run:android", | ||
| "ios": "yarn prestorybook && expo run:ios", | ||
| "android": "yarn prestorybook && expo start --android", |
There was a problem hiding this comment.
Scripts are switched from expo run:android/ios to expo start --android/ios to target Expo Go instead of a custom dev client build. expo run: compiles and installs a full native build, which requires Xcode/Android Studio and takes significantly longer. expo start streams the JS bundle to the Expo Go app, enabling faster iteration without native rebuilds. This follows the pattern from expo-template-storybook. The start script also now runs prestorybook to ensure story discovery runs before launching, which the dev-client flow previously did not enforce.
| "react-native": "0.81.5", | ||
| "react-native-gesture-handler": "~2.28.0", | ||
| "react-native-reanimated": "~4.1.1", | ||
| "react-native-worklets": "0.5.1" |
There was a problem hiding this comment.
react-native-worklets is a new required peer of react-native-reanimated@~4.1.1. Reanimated 4 extracted its worklet runtime into a separate package to allow it to be used independently. Without this package, Reanimated 4 will throw at runtime when any animated component is first rendered. It is listed here as a runtime dependency of the Storybook app because BottomSheetDialog and other animated components in the design system depend on it transitively.
| "@react-native-community/slider": "5.0.1", | ||
| "@storybook/addon-ondevice-actions": "10.4.0", | ||
| "@storybook/addon-ondevice-backgrounds": "10.4.0", | ||
| "@storybook/addon-ondevice-controls": "patch:@storybook/addon-ondevice-controls@npm%3A10.4.0#~/.yarn/patches/@storybook-addon-ondevice-controls-npm-10.4.0-dee6ebd17d.patch", |
There was a problem hiding this comment.
The patch: protocol in the version specifier tells Yarn to apply the local patch file at .yarn/patches/@storybook-addon-ondevice-controls-npm-10.4.0-dee6ebd17d.patch on top of the published npm package after installation. All other Storybook on-device packages are pinned to exact versions (no ^ or ~) to prevent unintended minor version drift — Storybook React Native has had breaking config changes between minor releases, so exact pinning ensures the patch remains applicable and the configuration stays consistent across installs.
| import { useTailwind } from '@metamask/design-system-twrnc-preset'; | ||
| import { renderHook } from '@testing-library/react-hooks'; | ||
| import { render } from '@testing-library/react-native'; | ||
| import { renderHook, render } from '@testing-library/react-native'; |
There was a problem hiding this comment.
renderHook was moved into @testing-library/react-native directly in v12+ and the separate @testing-library/react-hooks package is no longer needed or maintained for React Native. Using two separate packages for render and renderHook caused version skew issues in practice. This template is the starting point for all new React Native components created via yarn create-component:react-native, so updating it here ensures every new component scaffold starts with the correct import pattern. See the RTLN v12 migration guide for details.
|
Addressing the Cursor Bugbot comment at #discussion_r3276645252 — this is a false positive on two counts. First, Second, the package was never actually used. The This was a stale devDependency inherited from an older RN setup. Removed in 0f2036c. |
| "@types/prop-types": "^15", | ||
| "@types/react": "^18.2.0", | ||
| "@types/react-dom": "^18.2.0", | ||
| "@types/react": "^19.1.0", |
There was a problem hiding this comment.
These version bumps are required by the monorepo yarn constraints rule (expectConsistentDependenciesAndDevDependencies in yarn.config.cjs), which enforces that every workspace declaring the same dependency must use the same version range. Once react, react-dom, @types/react, and @types/react-dom were bumped to 19.x across the other packages in this PR, yarn constraints would fail here until this workspace matched. Running yarn constraints --fix resolves these automatically.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Unmapped
simultaneousHandlerssilently dropped in gesture migration- Mapped panGestureHandlerProps.simultaneousHandlers to gesture.simultaneousWithExternalGesture in applyPanGestureProps to restore simultaneous gestures on Android.
Or push these changes by commenting:
@cursor push 0d0a73271d
Preview (0d0a73271d)
diff --git a/packages/design-system-react-native/src/components/ActionListItem/ActionListItem.test.tsx b/packages/design-system-react-native/src/components/ActionListItem/ActionListItem.test.tsx
--- a/packages/design-system-react-native/src/components/ActionListItem/ActionListItem.test.tsx
+++ b/packages/design-system-react-native/src/components/ActionListItem/ActionListItem.test.tsx
@@ -1,4 +1,5 @@
-import { fireEvent, render } from '@testing-library/react-native';
+import { useTailwind } from '@metamask/design-system-twrnc-preset';
+import { fireEvent, render, renderHook } from '@testing-library/react-native';
import React from 'react';
import { createRenderer } from '../../test-utils/createRenderer';
@@ -270,6 +271,7 @@
});
it('applies pressed background when not disabled', () => {
+ const tw = renderHook(() => useTailwind()).result.current;
const tree = createRenderer(
<ActionListItem label="Test Label" onPress={mockOnPress} />,
);
@@ -278,11 +280,18 @@
node.props.onPress === mockOnPress &&
typeof node.props.style === 'function',
);
- const styleFn = pressable.props.style as (p: {
+ const styleFn = pressable.props.style as ({
+ pressed,
+ }: {
pressed: boolean;
- }) => unknown;
+ }) => object;
- expect(styleFn({ pressed: true })).toBeDefined();
+ expect(styleFn({ pressed: true })).toStrictEqual(
+ expect.objectContaining(tw`bg-default-pressed`),
+ );
+ expect(styleFn({ pressed: false })).toStrictEqual(
+ expect.not.objectContaining(tw`bg-default-pressed`),
+ );
});
});
diff --git a/packages/design-system-react-native/src/components/BottomSheetDialog/BottomSheetDialog.tsx b/packages/design-system-react-native/src/components/BottomSheetDialog/BottomSheetDialog.tsx
--- a/packages/design-system-react-native/src/components/BottomSheetDialog/BottomSheetDialog.tsx
+++ b/packages/design-system-react-native/src/components/BottomSheetDialog/BottomSheetDialog.tsx
@@ -114,6 +114,17 @@
);
}
+ // Map v1 PanGestureHandler prop to v2 Gesture API:
+ // Forward simultaneousHandlers so pan can coordinate with e.g. ScrollView on Android.
+ if (panGestureHandlerProps.simultaneousHandlers !== undefined) {
+ const handlers = Array.isArray(panGestureHandlerProps.simultaneousHandlers)
+ ? panGestureHandlerProps.simultaneousHandlers
+ : [panGestureHandlerProps.simultaneousHandlers];
+ // Use external gesture composition since handler refs typically come from native components (e.g., ScrollView)
+ // Casting to any to support ref types from RNGH v1 prop surface.
+ gesture.simultaneousWithExternalGesture(...(handlers as any[]));
+ }
+
return gesture;
};
diff --git a/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.test.tsx b/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.test.tsx
--- a/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.test.tsx
+++ b/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.test.tsx
@@ -3,10 +3,11 @@
import { render, renderHook } from '@testing-library/react-native';
import React from 'react';
-import { createRenderer } from '../../../../test-utils/createRenderer';
+import {
+ ButtonPrimary,
+ getButtonPrimaryContainerClassName,
+} from './ButtonPrimary';
-import { ButtonPrimary } from './ButtonPrimary';
-
describe('ButtonPrimary', () => {
let tw: ReturnType<typeof useTailwind>;
@@ -104,93 +105,97 @@
expect(btn).toBeDefined();
});
- it('toggles pressed styles (default)', () => {
- const tree = createRenderer(<ButtonPrimary>Press me</ButtonPrimary>);
+ describe('getButtonPrimaryContainerClassName', () => {
+ const cases = [
+ {
+ pressed: false,
+ isDanger: false,
+ isInverse: false,
+ isLoading: false,
+ expected: 'bg-icon-default',
+ },
+ {
+ pressed: true,
+ isDanger: false,
+ isInverse: false,
+ isLoading: false,
+ expected: 'bg-icon-default-pressed',
+ },
+ {
+ pressed: false,
+ isDanger: true,
+ isInverse: false,
+ isLoading: false,
+ expected: 'bg-error-default',
+ },
+ {
+ pressed: true,
+ isDanger: true,
+ isInverse: false,
+ isLoading: false,
+ expected: 'bg-error-default-pressed',
+ },
+ {
+ pressed: false,
+ isDanger: false,
+ isInverse: true,
+ isLoading: false,
+ expected: 'bg-default',
+ },
+ {
+ pressed: true,
+ isDanger: false,
+ isInverse: true,
+ isLoading: false,
+ expected: 'bg-default-pressed',
+ },
+ {
+ pressed: false,
+ isDanger: true,
+ isInverse: true,
+ isLoading: false,
+ expected: 'bg-default',
+ },
+ {
+ pressed: true,
+ isDanger: true,
+ isInverse: true,
+ isLoading: false,
+ expected: 'bg-default-pressed',
+ },
+ {
+ pressed: false,
+ isDanger: false,
+ isInverse: false,
+ isLoading: true,
+ expected: 'bg-icon-default-pressed',
+ },
+ ];
- // Find the ButtonAnimated component which has the style function
- const buttonAnimated = tree.root.findByProps({
- accessibilityRole: 'button',
+ cases.forEach(({ pressed, isDanger, isInverse, isLoading, expected }) => {
+ it(`returns ${expected} when pressed=${pressed}, isDanger=${isDanger}, isInverse=${isInverse}, isLoading=${isLoading}`, () => {
+ expect(
+ getButtonPrimaryContainerClassName(pressed, {
+ isDanger,
+ isInverse,
+ isLoading,
+ }),
+ ).toBe(expected);
+ });
});
- const styleFn = buttonAnimated.props.style as (p: {
- pressed: boolean;
- }) => unknown[];
- const defaultStyles = flattenStyles(styleFn({ pressed: false }));
- const pressedStyles = flattenStyles(styleFn({ pressed: true }));
-
- expectBackground(defaultStyles, 'bg-icon-default');
- expectBackground(pressedStyles, 'bg-icon-default-pressed');
-
- expect(defaultStyles).toBeDefined();
- expect(pressedStyles).toBeDefined();
- });
-
- it('toggles pressed styles (danger)', () => {
- const tree = createRenderer(<ButtonPrimary isDanger>Danger</ButtonPrimary>);
-
- const buttonAnimated = tree.root.findByProps({
- accessibilityRole: 'button',
+ it('appends custom twClassName', () => {
+ expect(
+ getButtonPrimaryContainerClassName(false, {
+ isDanger: false,
+ isInverse: false,
+ isLoading: false,
+ twClassName: 'rounded-full',
+ }),
+ ).toBe('bg-icon-default rounded-full');
});
- const styleFn = buttonAnimated.props.style as (p: {
- pressed: boolean;
- }) => unknown[];
-
- const defaultStyles = flattenStyles(styleFn({ pressed: false }));
- const pressedStyles = flattenStyles(styleFn({ pressed: true }));
-
- expectBackground(defaultStyles, 'bg-error-default');
- expectBackground(pressedStyles, 'bg-error-default-pressed');
-
- expect(defaultStyles).toBeDefined();
- expect(pressedStyles).toBeDefined();
});
- it('toggles pressed styles (inverse)', () => {
- const tree = createRenderer(
- <ButtonPrimary isInverse>Inverse</ButtonPrimary>,
- );
-
- const buttonAnimated = tree.root.findByProps({
- accessibilityRole: 'button',
- });
- const styleFn = buttonAnimated.props.style as (p: {
- pressed: boolean;
- }) => unknown[];
-
- const defaultStyles = flattenStyles(styleFn({ pressed: false }));
- const pressedStyles = flattenStyles(styleFn({ pressed: true }));
-
- expectBackground(defaultStyles, 'bg-default');
- expectBackground(pressedStyles, 'bg-default-pressed');
-
- expect(defaultStyles).toBeDefined();
- expect(pressedStyles).toBeDefined();
- });
-
- it('toggles pressed styles (inverse+danger)', () => {
- const tree = createRenderer(
- <ButtonPrimary isInverse isDanger>
- Inverse+Danger
- </ButtonPrimary>,
- );
-
- const buttonAnimated = tree.root.findByProps({
- accessibilityRole: 'button',
- });
- const styleFn = buttonAnimated.props.style as (p: {
- pressed: boolean;
- }) => unknown[];
-
- const defaultStyles = flattenStyles(styleFn({ pressed: false }));
- const pressedStyles = flattenStyles(styleFn({ pressed: true }));
-
- expectBackground(defaultStyles, 'bg-default');
- expectBackground(pressedStyles, 'bg-default-pressed');
-
- expect(defaultStyles).toBeDefined();
- expect(pressedStyles).toBeDefined();
- });
-
it('shows spinner + hides content when loading', () => {
const spinnerTW = 'absolute inset-0 flex items-center justify-center';
diff --git a/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx b/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
--- a/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
+++ b/packages/design-system-react-native/src/components/Button/variants/ButtonPrimary/ButtonPrimary.tsx
@@ -8,6 +8,34 @@
// Internal Button variant.
// Consumers should use `Button` with `variant`.
+
+export const getButtonPrimaryContainerClassName = (
+ pressed: boolean,
+ {
+ isDanger,
+ isInverse,
+ isLoading,
+ twClassName = '',
+ }: {
+ isDanger: boolean;
+ isInverse: boolean;
+ isLoading: boolean;
+ twClassName?: string;
+ },
+): string => {
+ const isActive = pressed || isLoading;
+ if (isInverse && isDanger) {
+ return `${isActive ? 'bg-default-pressed' : 'bg-default'} ${twClassName}`.trim();
+ }
+ if (isDanger) {
+ return `${isActive ? 'bg-error-default-pressed' : 'bg-error-default'} ${twClassName}`.trim();
+ }
+ if (isInverse) {
+ return `${isActive ? 'bg-default-pressed' : 'bg-default'} ${twClassName}`.trim();
+ }
+ return `${isActive ? 'bg-icon-default-pressed' : 'bg-icon-default'} ${twClassName}`.trim();
+};
+
export const ButtonPrimary = ({
children,
textProps,
@@ -25,17 +53,12 @@
(pressed: boolean): string => {
const classNameStr =
typeof twClassName === 'function' ? twClassName(pressed) : twClassName;
-
- if (isInverse && isDanger) {
- return `${pressed || isLoading ? 'bg-default-pressed' : 'bg-default'} ${classNameStr}`;
- }
- if (isDanger) {
- return `${pressed || isLoading ? 'bg-error-default-pressed' : 'bg-error-default'} ${classNameStr}`;
- }
- if (isInverse) {
- return `${pressed || isLoading ? 'bg-default-pressed' : 'bg-default'} ${classNameStr}`;
- }
- return `${pressed || isLoading ? 'bg-icon-default-pressed' : 'bg-icon-default'} ${classNameStr}`;
+ return getButtonPrimaryContainerClassName(pressed, {
+ isDanger,
+ isInverse,
+ isLoading,
+ twClassName: classNameStr,
+ });
},
[isInverse, isDanger, isLoading, twClassName],
);
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -423,7 +423,7 @@
languageName: node
linkType: hard
-"@babel/plugin-proposal-class-properties@npm:^7.0.0, @babel/plugin-proposal-class-properties@npm:^7.18.0":
+"@babel/plugin-proposal-class-properties@npm:^7.18.0":
version: 7.18.6
resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6"
dependencies:
@@ -483,7 +483,7 @@
languageName: node
linkType: hard
-"@babel/plugin-proposal-object-rest-spread@npm:^7.0.0, @babel/plugin-proposal-object-rest-spread@npm:^7.20.0":
+"@babel/plugin-proposal-object-rest-spread@npm:^7.20.0":
version: 7.20.7
resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7"
dependencies:
@@ -554,7 +554,7 @@
languageName: node
linkType: hard
-"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13":
+"@babel/plugin-syntax-class-properties@npm:^7.12.13":
version: 7.12.13
resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13"
dependencies:
@@ -609,7 +609,7 @@
languageName: node
linkType: hard
-"@babel/plugin-syntax-flow@npm:^7.0.0, @babel/plugin-syntax-flow@npm:^7.12.1, @babel/plugin-syntax-flow@npm:^7.18.0, @babel/plugin-syntax-flow@npm:^7.27.1":
+"@babel/plugin-syntax-flow@npm:^7.12.1, @babel/plugin-syntax-flow@npm:^7.18.0, @babel/plugin-syntax-flow@npm:^7.27.1":
version: 7.27.1
resolution: "@babel/plugin-syntax-flow@npm:7.27.1"
dependencies:
@@ -664,7 +664,7 @@
languageName: node
linkType: hard
-"@babel/plugin-syntax-jsx@npm:^7.0.0, @babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.28.6, @babel/plugin-syntax-jsx@npm:^7.7.2":
+"@babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.28.6, @babel/plugin-syntax-jsx@npm:^7.7.2":
version: 7.28.6
resolution: "@babel/plugin-syntax-jsx@npm:7.28.6"
dependencies:
@@ -708,7 +708,7 @@
languageName: node
linkType: hard
-"@babel/plugin-syntax-object-rest-spread@npm:^7.0.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3":
+"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3":
version: 7.8.3
resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3"
dependencies:
@@ -823,7 +823,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.25.9":
+"@babel/plugin-transform-block-scoped-functions@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.27.1"
dependencies:
@@ -897,7 +897,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-destructuring@npm:^7.0.0, @babel/plugin-transform-destructuring@npm:^7.20.0, @babel/plugin-transform-destructuring@npm:^7.24.8, @babel/plugin-transform-destructuring@npm:^7.25.9, @babel/plugin-transform-destructuring@npm:^7.28.0":
+"@babel/plugin-transform-destructuring@npm:^7.20.0, @babel/plugin-transform-destructuring@npm:^7.24.8, @babel/plugin-transform-destructuring@npm:^7.25.9, @babel/plugin-transform-destructuring@npm:^7.28.0":
version: 7.28.0
resolution: "@babel/plugin-transform-destructuring@npm:7.28.0"
dependencies:
@@ -977,7 +977,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.20.0, @babel/plugin-transform-flow-strip-types@npm:^7.25.2":
+"@babel/plugin-transform-flow-strip-types@npm:^7.20.0, @babel/plugin-transform-flow-strip-types@npm:^7.25.2":
version: 7.27.1
resolution: "@babel/plugin-transform-flow-strip-types@npm:7.27.1"
dependencies:
@@ -989,7 +989,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-for-of@npm:^7.0.0, @babel/plugin-transform-for-of@npm:^7.24.7, @babel/plugin-transform-for-of@npm:^7.25.9":
+"@babel/plugin-transform-for-of@npm:^7.24.7, @babel/plugin-transform-for-of@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-for-of@npm:7.27.1"
dependencies:
@@ -1047,7 +1047,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.25.9":
+"@babel/plugin-transform-member-expression-literals@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-member-expression-literals@npm:7.27.1"
dependencies:
@@ -1168,7 +1168,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.25.9":
+"@babel/plugin-transform-object-super@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-object-super@npm:7.27.1"
dependencies:
@@ -1239,7 +1239,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.25.9":
+"@babel/plugin-transform-property-literals@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-property-literals@npm:7.27.1"
dependencies:
@@ -1405,7 +1405,7 @@
languageName: node
linkType: hard
-"@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.0.0-0, @babel/plugin-transform-template-literals@npm:^7.25.9":
+"@babel/plugin-transform-template-literals@npm:^7.0.0-0, @babel/plugin-transform-template-literals@npm:^7.25.9":
version: 7.27.1
resolution: "@babel/plugin-transform-template-literals@npm:7.27.1"
dependencies:
@@ -3315,7 +3315,6 @@
deepmerge: "npm:^4.2.2"
fast-text-encoding: "npm:^1.0.6"
jest: "npm:^29.7.0"
- metro-react-native-babel-transformer: "npm:0.76.9"
react: "npm:19.1.0"
react-native: "npm:0.81.5"
react-native-gesture-handler: "npm:~2.28.0"
@@ -6578,13 +6577,6 @@
languageName: node
linkType: hard
-"babel-plugin-syntax-trailing-function-commas@npm:^7.0.0-beta.0":
- version: 7.0.0-beta.0
- resolution: "babel-plugin-syntax-trailing-function-commas@npm:7.0.0-beta.0"
- checksum: 10/e37509156ca945dd9e4b82c66dd74f2d842ad917bd280cb5aa67960942300cd065eeac476d2514bdcdedec071277a358f6d517c31d9f9244d9bbc3619a8ecf8a
- languageName: node
- linkType: hard
-
"babel-plugin-transform-flow-enums@npm:^0.0.2":
version: 0.0.2
resolution: "babel-plugin-transform-flow-enums@npm:0.0.2"
@@ -6658,43 +6650,6 @@
languageName: node
linkType: hard
-"babel-preset-fbjs@npm:^3.4.0":
- version: 3.4.0
- resolution: "babel-preset-fbjs@npm:3.4.0"
- dependencies:
- "@babel/plugin-proposal-class-properties": "npm:^7.0.0"
- "@babel/plugin-proposal-object-rest-spread": "npm:^7.0.0"
- "@babel/plugin-syntax-class-properties": "npm:^7.0.0"
- "@babel/plugin-syntax-flow": "npm:^7.0.0"
- "@babel/plugin-syntax-jsx": "npm:^7.0.0"
- "@babel/plugin-syntax-object-rest-spread": "npm:^7.0.0"
- "@babel/plugin-transform-arrow-functions": "npm:^7.0.0"
- "@babel/plugin-transform-block-scoped-functions": "npm:^7.0.0"
- "@babel/plugin-transform-block-scoping": "npm:^7.0.0"
- "@babel/plugin-transform-classes": "npm:^7.0.0"
- "@babel/plugin-transform-computed-properties": "npm:^7.0.0"
- "@babel/plugin-transform-destructuring": "npm:^7.0.0"
- "@babel/plugin-transform-flow-strip-types": "npm:^7.0.0"
- "@babel/plugin-transform-for-of": "npm:^7.0.0"
- "@babel/plugin-transform-function-name": "npm:^7.0.0"
- "@babel/plugin-transform-literals": "npm:^7.0.0"
- "@babel/plugin-transform-member-expression-literals": "npm:^7.0.0"
- "@babel/plugin-transform-modules-commonjs": "npm:^7.0.0"
- "@babel/plugin-transform-object-super": "npm:^7.0.0"
- "@babel/plugin-transform-parameters": "npm:^7.0.0"
- "@babel/plugin-transform-property-literals": "npm:^7.0.0"
- "@babel/plugin-transform-react-display-name": "npm:^7.0.0"
- "@babel/plugin-transform-react-jsx": "npm:^7.0.0"
- "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0"
- "@babel/plugin-transform-spread": "npm:^7.0.0"
- "@babel/plugin-transform-template-literals": "npm:^7.0.0"
- babel-plugin-syntax-trailing-function-commas: "npm:^7.0.0-beta.0"
- peerDependencies:
- "@babel/core": ^7.0.0
- checksum: 10/1e73ebaaeac805aad15793d06a40a63be096730f58708ec434f08578b5ccba890190cda8fdf1c626ab081a8e1cfd376c9db82eaf78a0fafdbcc2362eb2963804
- languageName: node
- linkType: hard
-
"babel-preset-jest@npm:^29.6.3":
version: 29.6.3
resolution: "babel-preset-jest@npm:29.6.3"
@@ -10015,13 +9970,6 @@
languageName: node
linkType: hard
-"hermes-estree@npm:0.12.0":
- version: 0.12.0
- resolution: "hermes-estree@npm:0.12.0"
- checksum: 10/9aa5e41f4abb522e60ac44950590a08643cdcaa8aac0e4f101b08921880e66778b2f83e08d2c15b94bf87a21d7be1d14f584edf3141b50f79ff091069d92e932
- languageName: node
- linkType: hard
-
"hermes-estree@npm:0.29.1":
version: 0.29.1
resolution: "hermes-estree@npm:0.29.1"
@@ -10043,15 +9991,6 @@
languageName: node
linkType: hard
-"hermes-parser@npm:0.12.0":
- version: 0.12.0
- resolution: "hermes-parser@npm:0.12.0"
- dependencies:
- hermes-estree: "npm:0.12.0"
- checksum: 10/8a8656954956187d60d0c6db9cd71cb3e722949a698d77537870dd4d821a2d3278fbeaa9034a950aa2bb75a4830cbf6df0bdb7210927724dc477c5cd34f9bddd
- languageName: node
- linkType: hard
-
"hermes-parser@npm:0.29.1, hermes-parser@npm:^0.29.1":
version: 0.29.1
resolution: "hermes-parser@npm:0.29.1"
@@ -12296,7 +12235,7 @@
languageName: node
linkType: hard
-"metro-react-native-babel-preset@npm:0.76.9, metro-react-native-babel-preset@npm:^0.76.0":
+"metro-react-native-babel-preset@npm:^0.76.0":
version: 0.76.9
resolution: "metro-react-native-babel-preset@npm:0.76.9"
dependencies:
@@ -12345,21 +12284,6 @@
languageName: node
linkType: hard
-"metro-react-native-babel-transformer@npm:0.76.9":
- version: 0.76.9
- resolution: "metro-react-native-babel-transformer@npm:0.76.9"
- dependencies:
- "@babel/core": "npm:^7.20.0"
- babel-preset-fbjs: "npm:^3.4.0"
- hermes-parser: "npm:0.12.0"
- metro-react-native-babel-preset: "npm:0.76.9"
- nullthrows: "npm:^1.1.1"
- peerDependencies:
- "@babel/core": "*"
- checksum: 10/2e8f7212ef4824bfcb7ae8455d0c7540d3885dda47443bb3889278bbe15057fd7bfda72c36b82b62a209648814fc5c0d7863d7a04ab13371b94395bafea37807
- languageName: node
- linkType: hard
-
"metro-resolver@npm:0.83.3":
version: 0.83.3
resolution: "metro-resolver@npm:0.83.3"You can send follow-ups to the cloud agent here.
a86638c to
0f2036c
Compare
| ); | ||
| expect(getByTestId('action-item')).toBeDefined(); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Removed because the assertion — expect(styleFn({ pressed: true })).toBeDefined() — is a no-op: a style function always returns a defined value regardless of whether the correct class is applied. The test existed solely to hit the pressed=true branch for 100% coverage rather than to verify any observable behavior. Removing it intentionally drops branch coverage on this file; a per-file threshold exception has been added in packages/design-system-react-native/jest.config.js to document this. The pressed visual state is better verified on device via Storybook. See #1182 for the options considered and the path forward when upgrading to RNTL v14.
| import * as ReactTestRenderer from 'react-test-renderer'; | ||
|
|
||
| // Wraps ReactTestRenderer.create in act() as required by React 19. | ||
| export const createRenderer = (element: React.ReactElement) => { |
There was a problem hiding this comment.
React 19 throws a warning when ReactTestRenderer.create() is called outside of act(), so this wrapper centralises the fix rather than duplicating it across every test file that needs the renderer. It is excluded from coverage via /test-utils/ in jest.config.js since it is infrastructure, not component logic. The renderer is used specifically in tests that need to inspect or invoke a Pressable's style function directly — the only way to assert both branches of a pressed-state style in RNTL v13, where no interaction API can hold pressed=true during an assertion. See #1182 for the plan to migrate away from this pattern when RNTL v14 is stable.
| import React from 'react'; | ||
| import * as ReactTestRenderer from 'react-test-renderer'; | ||
|
|
||
| import { createRenderer } from '../../test-utils/createRenderer'; |
There was a problem hiding this comment.
The shared createRenderer utility replaces the inline ReactTestRenderer.act() + create() pattern that was duplicated across 7 test files. React 19 requires ReactTestRenderer.create() to be wrapped in act() — without it a warning is thrown. See src/test-utils/createRenderer.tsx for the implementation and #1182 for the longer-term plan to move away from react-test-renderer when RNTL v14 is stable.
|
Addressing Cursor Bugbot at #discussion_r3277501489 — this is a valid finding. The bugbot correctly identified that Rather than patching The |
| PanGestureHandler, | ||
| PanGestureHandlerGestureEvent, | ||
| } from 'react-native-gesture-handler'; | ||
| import { Gesture, GestureDetector } from 'react-native-gesture-handler'; |
There was a problem hiding this comment.
The import swap from PanGestureHandler / PanGestureHandlerGestureEvent to Gesture / GestureDetector reflects the mandatory RNGH v1 → v2 API migration. PanGestureHandler was a JSX wrapper component; the v2 API uses a GestureDetector wrapper with a gesture object built via the fluent Gesture.Pan() builder. useAnimatedGestureHandler (which handled the event callbacks in Reanimated 3) was removed entirely from Reanimated 4, making this migration necessary.
| const currentYOffset = useSharedValue(screenHeight); | ||
| const topOfDialogYValue = useSharedValue(0); | ||
| const bottomOfDialogYValue = useSharedValue(screenHeight); | ||
| const gestureStartYOffset = useSharedValue(0); |
There was a problem hiding this comment.
gestureStartYOffset replaces the ctx (context) object that useAnimatedGestureHandler provided for sharing state between onStart, onActive, and onEnd callbacks. In Reanimated 3, ctx.startY was the idiomatic way to pass the gesture origin across callbacks on the UI thread. In Reanimated 4, useAnimatedGestureHandler is gone and shared values are the correct replacement — they run on the UI thread the same way, just declared explicitly rather than managed implicitly by the hook.
| Math.abs(velocityY) > | ||
| DEFAULT_BOTTOMSHEETDIALOG_SWIPETHRESHOLD_DURATION; | ||
| const isQuickDismissing = velocityY > 0; | ||
| const gestureHandler = useMemo(() => { |
There was a problem hiding this comment.
The gesture is built inside useMemo rather than with a dedicated hook because RNGH v2 gestures are plain objects — they have no special hook requirements. useMemo prevents recreating the gesture object on every render (which would break gesture state continuity) and correctly re-creates it when isInteractable or the close handler changes. The callback name change from onActive to onUpdate is an RNGH v2 rename; the logic inside is identical to the previous implementation.
| enabled={isInteractable} | ||
| onGestureEvent={gestureHandler} | ||
| > | ||
| <GestureDetector gesture={gestureHandler}> |
There was a problem hiding this comment.
GestureDetector accepts the composed gesture object directly rather than wiring it via onGestureEvent as the old PanGestureHandler did. The enabled flag, previously a JSX prop on PanGestureHandler, is now configured via .enabled(isInteractable) in the gesture builder chain. The consumer-facing panGestureHandlerProps escape hatch has been removed as a breaking change — see MIGRATION.md and issue #1183 for the full context and the plan to replace it with a first-class gestureModifier prop or adopt @gorhom/bottom-sheet.
| PanGestureHandlerProps, | ||
| 'enabled' | 'onGestureEvent' | ||
| >; | ||
|
|
There was a problem hiding this comment.
The PanGestureHandlerProps import and BottomSheetDialogPanGestureHandlerProps type alias are gone because panGestureHandlerProps has been removed from the public API. The old type was semantically wrong in any case — it was the v1 JSX component's props type being used to describe v2 gesture configuration. The prop was audited against the MetaMask Mobile and Extension consumer codebases and found to be unused at any call site. See MIGRATION.md for the breaking change documentation and issue #1183 for the follow-up plan.
| }: { | ||
| children: React.ReactNode; | ||
| }) => { | ||
| GestureDetector: ({ children }: { children: React.ReactNode }) => { |
There was a problem hiding this comment.
The mock stubs only the four methods the component actually calls: enabled (returns the gesture object so method chaining works), onStart, onUpdate, and onEnd. GestureDetector takes the gesture object directly rather than mounting a JSX component with its own props, so there is no component props surface to capture — just the gesture builder API.
Replace getTw() inline helper with beforeAll + module-level tw variable, consistent with ButtonPrimary, ButtonSecondary, ButtonTertiary, and ButtonHero.
📖 Storybook Preview |
| // pressed && !isDisabled branch in getPressableStyle is not unit-testable without | ||
| // react-test-renderer internals (see https://github.com/MetaMask/metamask-design-system/issues/1182). | ||
| // Verified visually via Storybook on device. | ||
| './src/components/ActionListItem/ActionListItem.tsx': { |
There was a problem hiding this comment.
The pressed && !isDisabled branch in ActionListItem's getPressableStyle is only reachable by directly invoking the Pressable style function with { pressed: true }, which requires react-test-renderer internals. The original test that covered this branch used toBeDefined() — an assertion guaranteed to pass regardless of implementation — so removing it loses no meaningful signal. The pressed visual state is verified on device via Storybook instead. See issue #1182 for the options considered and the plan to migrate away from this pattern when RNTL v14 is stable.
| // Add coverage ignore patterns | ||
| coveragePathIgnorePatterns: [ | ||
| 'index.ts', | ||
| '/test-utils/', // shared test utilities |
There was a problem hiding this comment.
src/test-utils/ holds the shared createRenderer utility that wraps ReactTestRenderer.create in act() for React 19. It is test infrastructure, not component logic, so it is excluded from coverage the same way index.ts barrel files and constants files are.
| }, | ||
| transformIgnorePatterns: [ | ||
| 'node_modules/(?!(react-native|@react-native|react-native-reanimated|@react-navigation|react-native-jazzicon|react-native-gesture-handler|react-native-safe-area-context)/)', | ||
| 'node_modules/(?!(react-native|@react-native|react-native-reanimated|react-native-worklets|@react-navigation|react-native-jazzicon|react-native-gesture-handler|react-native-safe-area-context)/)', |
There was a problem hiding this comment.
react-native-worklets is added here because Reanimated 4 extracted its worklet runtime into this separate package. Without it in transformIgnorePatterns, Jest tries to run the raw ESM source of react-native-worklets and throws a SyntaxError — the same reason all the other react-native-* packages are listed here.
…omSheetDialog The gesture handler tests (onStart/onUpdate/onEnd) asserted only toBeDefined() on the handler reference after calling it — a condition guaranteed to pass regardless of implementation. They provided no behavioral verification despite descriptive names implying specific swipe logic was being tested. Removes the gesture handler callbacks describe block and simplifies the Gesture.Pan mock to a minimal stub. Adds per-file coverage threshold for BottomSheetDialog.tsx to document the intentional gap. Gesture behavior is verified via Storybook on device.
📖 Storybook Preview |
| Gesture: { | ||
| Pan: () => { | ||
| const gesture: Record<string, unknown> = {}; | ||
| const noop = () => gesture; |
There was a problem hiding this comment.
The Gesture.Pan mock is now a minimal stub. The previous version captured onStart/onUpdate/onEnd callbacks into a gestureCallbacksRef so tests could invoke them directly, but every assertion in those tests was expect(handlers.onXxx).toBeDefined() — trivially true since the mock itself created those functions. The tests had descriptive names implying behavioral verification (clamping, threshold logic, dismissal) but produced no signal about whether that logic was correct. Removing them is honest: the gesture physics are covered by Storybook on device, and the coverage gap is documented in jest.config.js.
📖 Storybook Preview |
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
|
@SocketSecurity ignore npm/@expo/metro-config@54.0.15 All are devDependencies in apps/storybook-react-native — not shipped in any published package or consumer bundle.
|
📖 Storybook Preview |
📖 Storybook Preview |
…lity (#1186) ## **Description** Custom fonts (Geist, MM Sans, MM Poly) were not rendering in the React Native Storybook app — all text fell back to the system font regardless of the \`fontFamily\` or \`fontWeight\` prop applied via the \`Text\` component. ### Background Prior to [#1165](#1165), the Storybook app used native builds (\`expo run:ios\` / \`expo run:android\`), which compiled and installed a custom native binary. In that workflow, the \`expo-font\` config plugin in \`app.json\` worked correctly — it embedded font files into the native binary at build time by modifying \`Info.plist\` (\`UIAppFonts\`) and the Xcode/Gradle project. [#1165](#1165) aligned Expo and React Native dependencies with MetaMask Mobile and switched the Storybook app scripts back to **Expo Go** (\`expo start --ios\` / \`expo start --android\`). This is what surfaced the font regression. ### Root cause The \`expo-font\` config plugin approach only works when running a locally-built native app. Since the project now runs via **Expo Go** — a pre-built binary distributed by Expo — project-level native plugins are silently ignored. When React Native tried to use \`fontFamily: 'Geist-Medium'\`, iOS/Android found no registered font with that PostScript name and silently fell back to the system font. ### Fix Switch to runtime font loading via the \`useFonts\` hook from \`expo-font\`, which loads fonts as Metro assets at app startup. This works in Expo Go because it uses JavaScript-layer APIs rather than native plugin execution. Font loading is placed in the root entry component (\`index.tsx\`) rather than a Storybook decorator so fonts are guaranteed to be available before any story renders — loading in a per-story decorator can cause timing issues on Android. **Changes:** - Renamed \`.rnstorybook/index.ts\` → \`index.tsx\` and added an \`App\` wrapper component that loads all fonts via \`useFonts\` before rendering \`StorybookUIRoot\` - Updated \`package.json\` \`main\` field accordingly - Removed the now-redundant \`expo-font\` config plugin from \`app.json\` ## **Related issues** Fixes: ## **Manual testing steps** 1. Run \`yarn storybook:ios\` and open the **Text → Font Weight** story — confirm Regular, Medium, and Bold render visibly different weights 2. Open the **Text → Font Family** story — confirm Default (Geist), Accent (MM Sans), and Hero (MM Poly) each render in their respective typeface 3. Run \`yarn storybook:android\` and repeat steps 1–2 ## **Screenshots/Recordings** ### **Before** All three font weights and all three font families rendered identically in the system font (San Francisco on iOS, Roboto on Android). https://github.com/user-attachments/assets/6632dee5-ae08-43b1-a02e-acbfea63910d ### **After** All three font weights and all three font families rendered as expected https://github.com/user-attachments/assets/b26c6d19-8d34-4c6b-b2fe-434a2a07c01f ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
… UI-thread error (#1185) ## **Description** Fixes a runtime crash introduced by the React Native 0.81.5 / Reanimated 4.1.x upgrade (PR #1165). Reanimated 4 enforces strict UI-thread isolation via `react-native-worklets`. Functions called from within a `useAnimatedScrollHandler` callback run on the UI thread and must be compiled as worklets. Previously, `updateScrollYFromEvent` was called from the scroll handler but was a plain JS function — Reanimated 3 was lenient about this boundary, but Reanimated 4 throws a hard error: ``` [Reanimated] Tried to synchronously call a non-worklet function 'updateScrollYFromEvent' on the UI thread. ``` The fix inlines the single-line update directly in the handler callback. Inline arrow functions inside `useAnimatedScrollHandler` are automatically compiled as worklets by the Reanimated Babel plugin — no `'worklet'` directive needed, no Metro cache dependency. The dead `updateScrollYFromEvent` function and its unit test have also been removed (see below). ### Why unit tests didn't catch this The test suite mocks Reanimated entirely: ```ts jest.mock('react-native-reanimated', () => jest.requireActual('react-native-reanimated/mock'), ); ``` This replaces `useAnimatedScrollHandler` with a stub that never executes the callback passed to it. The worklet body — including the call to `updateScrollYFromEvent` — never runs in Jest. The UI-thread isolation error only surfaces at runtime when a real scroll event fires and Reanimated attempts to invoke a non-worklet function across the JS/UI thread boundary. This is a known limitation of testing Reanimated worklets. There is no clean Jest-based solution — the community generally accepts that worklet execution paths require manual testing or integration/E2E tests (e.g. Maestro, Detox, or Storybook-driven automation). Any future changes to components that use `useAnimatedScrollHandler` or other Reanimated worklet APIs should be verified manually on device/simulator, as unit tests cannot exercise this code path. ### Dead code cleanup After inlining, `updateScrollYFromEvent` had zero production callers — its only consumer was its own unit test, which was testing an isolated helper disconnected from the real code path. The function and its test have been removed. Coverage thresholds in `jest.config.js` have been updated to reflect the true testable surface, with a comment explaining that the `onScroll` worklet callback is untestable in Jest. ## **Related issues** Fixes: (regression from #1165) ## **Manual testing steps** 1. Run `yarn storybook:ios` on a device/simulator 2. Navigate to a story that uses `HeaderStandardAnimated` (e.g. `Components/HeaderStandardAnimated/Default`) 3. Scroll the content — confirm no crash and smooth scroll-linked header animation ## **Screenshots/Recordings** ### **Before** ``` [Reanimated] Tried to synchronously call a non-worklet function 'updateScrollYFromEvent' on the UI thread. ``` App crashes on scroll. https://github.com/user-attachments/assets/15184a06-2a31-441f-bce3-19c66d56d828 ### **After** No error. Scroll-linked animation works correctly. https://github.com/user-attachments/assets/27fe2556-5f5c-4b0d-90be-2fb21e2f5188 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches Reanimated UI-thread/worklet scroll handling; behavior can't be exercised in Jest and requires manual verification on device to avoid runtime crashes. > > **Overview** > Fixes a Reanimated 4 runtime crash by inlining the `scrollYValue.value = scrollEvent.contentOffset.y` assignment directly inside `useAnimatedScrollHandler` in `useHeaderStandardAnimated`, avoiding a non-worklet function call on the UI thread. > > Removes the now-dead `updateScrollYFromEvent` helper and its unit test, and relaxes Jest coverage thresholds for `useHeaderStandardAnimated.ts` with a comment noting the `onScroll` worklet callback is untestable under the Reanimated Jest mock. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f0a9c23. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Release 42.0.0 This release curates the changelogs for three published packages into consumer-facing Keep a Changelog entries. It adds the `FlashFilled` icon and `SelectButtonSize` across platforms, adds the `TextField` component to React web, and ships two React Native breaking changes: the `panGestureHandlerProps` removal (part of the `react-native-gesture-handler` v2 migration) and the removal of the variant-based title API from `HeaderBase`/`BottomSheetHeader`. ### 📦 Package Versions - `@metamask/design-system-shared`: **0.20.0** (was 0.19.0) - `@metamask/design-system-react`: **0.24.0** (was 0.23.1) - `@metamask/design-system-react-native`: **0.27.0** (was 0.26.0) > `@metamask/design-tokens`, `@metamask/design-system-tailwind-preset`, and `@metamask/design-system-twrnc-preset` are unchanged in this release. ### 🔄 Shared Type Updates (0.20.0) #### Component Type Additions (#1191, #1177, #1170) **What Changed:** - Added `FlashFilled` to the `IconName` const so the filled lightning bolt is available on both platforms. - Added `SelectButtonSize` so `SelectButton` exposes a semantic size type shared across platforms. - Added `TextFieldPropsShared` for the cross-platform text field input contract. **Impact:** - Additive only — no breaking changes to the shared package. - Continues the ADR-0003/0004 const-object + centralized-types pattern. ### 🌐 React Web Updates (0.24.0) #### Added - Added `TextField` for labeled text entry with optional helper and validation text, exposing `TextFieldSize` and `TextFieldType` (#1170) - Added `FlashFilled` icon (filled lightning bolt) to `IconName` (#1191) ### 📱 React Native Updates (0.27.0) #### Added - Added `FlashFilled` icon (filled lightning bolt) to `IconName` (#1191) - Added `SelectButtonSize` so `SelectButton` exposes a semantic size type (#1177) #### Changed - **BREAKING:** Removed `panGestureHandlerProps` from `BottomSheet` and `BottomSheetDialog` following the migration to the `react-native-gesture-handler` v2 `GestureDetector`/`Gesture.Pan()` API (#1165) - Migration: [From version 0.26.0 to 0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270) - **BREAKING:** Removed the variant-based title API from `HeaderBase` and `BottomSheetHeader` — `variant`, `HeaderBaseVariant`, `BottomSheetHeaderVariant`, and `HeaderBase`'s `titleTestID` (#1103) - String titles now render with a centered `HeadingSm` treatment; pass custom `children` for bespoke title layouts and use `textProps.testID` in place of `titleTestID` - Migration: [From version 0.26.0 to 0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270) - Reduced the default `SegmentGroup` segment spacing from `gap-3` to `gap-1` for tighter grouped segments (#1194) #### Fixed - Fixed a `HeaderStandardAnimated` runtime crash under React Native Reanimated 4 by inlining the scroll-handler worklet (#1185) - Fixed React Native Web rendering for `BottomSheet`, `BottomSheetOverlay`, `Icon`, and the animated `ButtonAnimated` and `Spinner` components (#1187) ###⚠️ Breaking Changes #### Removed `panGestureHandlerProps` from `BottomSheet` / `BottomSheetDialog` (React Native Only) **What Changed:** - Removed the `panGestureHandlerProps` prop. The components migrated from the deprecated RNGH v1 `PanGestureHandler` JSX component to the v2 `GestureDetector` + `Gesture.Pan()` API. - `simultaneousHandlers` (the only real-world use case) was never wired up under the old shim, so no working behavior is lost. **Migration:** ```tsx // Before (0.26.0) <BottomSheet goBack={goBack} panGestureHandlerProps={{ simultaneousHandlers: scrollViewRef }} > {children} </BottomSheet> // After (0.27.0) <BottomSheet goBack={goBack}>{children}</BottomSheet> ``` #### Removed variant-based title API from `HeaderBase` / `BottomSheetHeader` (React Native Only) **What Changed:** - Removed `variant`, `HeaderBaseVariant`, `BottomSheetHeaderVariant`, and `HeaderBase`'s `titleTestID`. - String titles now render with a centered `HeadingSm` treatment; custom layouts use `children`, and `titleTestID` is replaced by `textProps.testID`. **Migration:** ```tsx // Before (0.26.0) import { HeaderBase, HeaderBaseVariant } from '@metamask/design-system-react-native'; <HeaderBase variant={HeaderBaseVariant.Display} titleTestID="title"> Account details </HeaderBase> // After (0.27.0) import { HeaderBase } from '@metamask/design-system-react-native'; <HeaderBase textProps={{ testID: 'title' }}>Account details</HeaderBase> ``` See migration guide for complete instructions: - [React Native Migration Guide — 0.26.0 to 0.27.0](./packages/design-system-react-native/MIGRATION.md#from-version-0260-to-0270) ### 🔗 Consumer note: MetaMask Mobile MetaMask Mobile currently consumes `@metamask/design-system-react-native@0.20.0` and applies a local yarn patch — `.yarn/patches/@metamask-design-system-react-native-npm-0.20.0-2ae4d6f1dd.patch` — to migrate `BottomSheetDialog` from `PanGestureHandler` to `GestureDetector` for its React Native Reanimated 4 upgrade ([MetaMask/metamask-mobile#29470](MetaMask/metamask-mobile#29470)). This release **upstreams that exact migration natively** (#1165). Once mobile bumps to `0.27.0`, it can **drop that yarn patch** — the package now uses the RNGH v2 `GestureDetector`/`Gesture.Pan()` API on its own. Compatibility note: the package keeps `react-native-reanimated` at `peerDependencies: >=3.17.0` (unchanged). It was validated against mobile's current Reanimated 3.19 and is forward-compatible with the incoming Reanimated 4.1.x (the #1185 worklet fix works on both), so no peer-dependency bump is required. ### ✅ Checklist - [x] Changelogs updated with human-readable descriptions - [x] Changelog validation passed (`yarn changelog:validate`) - [x] Version bumps follow semantic versioning - [x] design-system-shared: minor (0.19.0 → 0.20.0) - additive type exports - [x] design-system-react: minor (0.23.1 → 0.24.0) - new `TextField` component + icon - [x] design-system-react-native: minor (0.26.0 → 0.27.0) - pre-1.0 minor with breaking changes - [x] Breaking changes documented with migration guidance - [x] Migration guides updated with before/after examples (if breaking changes) - [x] PR references included in changelog entries ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've reviewed the [Release Workflow](./.cursor/rules/release-workflow.md) cursor rule - [ ] All tests pass (`yarn build && yarn test && yarn lint`) - [x] Changelog validation passes (`yarn changelog:validate`) ## **Pre-merge reviewer checklist** - [ ] I've reviewed the [Reviewing Release PRs](./docs/reviewing-release-prs.md) guide - [ ] Package versions follow semantic versioning - [ ] Changelog entries are consumer-facing (not commit message regurgitation) - [ ] Breaking changes are documented in MIGRATION.md with examples - [ ] All unreleased changes are accounted for in changelogs <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > No runtime code in the diff, but the published React Native 0.27.0 changelog documents breaking BottomSheet and Header API changes that require consumer migrations. > > **Overview** > **Release 42.0.0** bumps the monorepo to **42.0.0** and publishes **@metamask/design-system-shared@0.20.0**, **@metamask/design-system-react@0.24.0**, and **@metamask/design-system-react-native@0.27.0** with finalized Keep a Changelog entries and compare links. > > The diff is mostly **release packaging**: version fields in root and package `package.json` files, new changelog sections for those versions, and doc updates. **React Native** docs drop **`panGestureHandlerProps`** from `BottomSheet` / `BottomSheetDialog` READMEs; **`MIGRATION.md`** adds a **0.26.0 → 0.27.0** section (bottom-sheet gesture prop removal, header title API) and moves **BannerBase** guidance under **0.24.0 → 0.25.0**. > > What consumers get in this release (documented in changelogs, not new code in this PR): shared **`FlashFilled`**, **`SelectButtonSize`**, **`TextFieldPropsShared`**; web **`TextField`** and **`FlashFilled`**; native **`FlashFilled`**, **`SelectButtonSize`**, two **breaking** API removals (`panGestureHandlerProps`, header **`variant`** / **`titleTestID`**), tighter **`SegmentGroup`** spacing, Reanimated 4 / RN Web fixes. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6710621. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Description
Aligns the React Native packages and Storybook app with the dependency baseline introduced in MetaMask Mobile #29195 — the React Native 0.76.9 → 0.81.5 / React 18 → 19 / Expo 52 → 54 upgrade that mobile recently merged.
As part of this upgrade, the Storybook app scripts have been switched from native builds (
expo run:ios/expo run:android) back to Expo Go (expo start --ios/expo start --android), which no longer require a full native build and are significantly faster to set up for local development.Related issues
Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-744
Dependency changes
apps/storybook-react-native— production dependenciesreact18.3.119.1.019.1.0✅react-native0.76.90.81.50.81.5✅expo~52.0.49~54.0.3454.0.33✅expo-asset~11.0.5~12.0.12~12.0.12✅expo-font~13.0.4~14.0.11~14.0.11✅expo-status-bar~2.0.0~3.0.9react-native-gesture-handler~2.25.0~2.28.0~2.28.0✅react-native-reanimated~3.17.2~4.1.13.19.0(mobile still compatible)react-native-worklets0.5.1apps/storybook-react-native— dev dependencies@react-native-async-storage/async-storage^1.23.12.2.02.2.0✅@react-native-community/datetimepicker^8.5.1^8.5.1^8.5.1✅@react-native-community/slider^4.5.65.0.15.0.1✅@storybook/react-native^1010.4.0@storybook/addon-ondevice-actions^1010.4.0@storybook/addon-ondevice-backgrounds^1010.4.0@storybook/addon-ondevice-controls^1010.4.0(patched)@storybook/addon-ondevice-notes^1010.4.0storybook^10.3.110.3.6@types/react^18.2.0^19.1.0^19.1.0✅@types/react-dom^18.2.0^19.1.0react-dom18.3.119.1.0react-native-safe-area-context^5.4.0~5.6.0~5.6.0✅react-native-svg~15.11.215.12.115.12.1✅react-native-svg-transformer^1.5.0^1.5.3packages/design-system-react-native— dev dependenciesreact18.3.119.1.019.1.0✅react-native0.76.90.81.50.81.5✅react-test-renderer^18.3.119.1.019.1.0✅@react-native/babel-preset0.76.90.81.50.76.9@react-native/typescript-config0.76.90.81.50.76.9@types/react^18.2.0^19.1.0^19.1.0✅@types/react-test-renderer^18^19.1.0@testing-library/react-hooks^8.0.1@storybook/react-native^1010.4.0react-native-gesture-handler~2.25.0~2.28.0~2.28.0✅react-native-reanimated~3.17.2~4.1.13.19.0react-native-safe-area-context^5.4.0~5.6.0~5.6.0✅react-native-svg~15.11.215.12.115.12.1✅react-native-svg-transformer^1.5.0^1.5.3react-native-worklets0.5.1packages/design-system-shared— dev dependenciesreact18.3.119.1.019.1.0✅@types/react^18.2.0^19.1.0^19.1.0✅packages/design-system-twrnc-preset— dev dependenciesreact18.3.119.1.019.1.0✅react-test-renderer^18.3.119.1.019.1.0✅@types/react^19.1.0^19.1.0✅@types/react-test-renderer^18^19.1.0apps/storybook-react— dev dependenciesreact18.3.119.1.0react-dom18.3.119.1.0@types/react^18.2.0^19.1.0@types/react-dom^18.2.0^19.1.0storybook^10.3.110.3.6Additional changes
BottomSheetDialogfrom the deprecated RNGH v1PanGestureHandler/useAnimatedGestureHandlerAPI to the RNGH v2GestureDetector/Gesture.Pan()API@testing-library/react-hooks(removed) torenderHookfrom@testing-library/react-nativereact-native-workletsto JesttransformIgnorePatterns@storybook/addon-ondevice-controls@10.4.0(upstream bug fix)expo run:ios/android(native builds) toexpo start --ios/android(Expo Go) — previously required a full native build; now works out of the box with the upgraded Expo 54 baselineManual testing steps
yarn storybook:ios— confirm Expo Go launches and stories renderyarn storybook:android— confirm Expo Go launches and stories renderyarn test— confirm all tests passyarn build— confirm all packages build without errorsyarn lint— confirm no lint errorsScreenshots/Recordings
Before
Storybook running from native build because ExpoGo was broken in those versions of RN/Storybook
After
Storybook running from ExpoGo in iOS and Android
Screen.Recording.2026-05-20.at.1.01.52.PM.mov
Preview builds
Testing MMDS
BottomSheetwith reanimated library updates in mobileScreen.Recording.2026-05-21.at.1.16.52.PM.mov
Extension running as expected
Screen.Recording.2026-05-21.at.1.41.38.PM.mov
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Moderate risk due to broad React/React Native/Expo and Storybook version bumps plus a migration of
BottomSheetDialoggesture handling to the RNGH v2 API, which can affect runtime interactions across devices.Overview
Aligns the monorepo’s Storybook + React Native toolchain with a newer Mobile baseline by upgrading Expo 54, React 19, React Native 0.81 and related RN libs (including adding
react-native-workletsand updating Jest transforms).Migrates
BottomSheetDialogfrom deprecated RNGH v1PanGestureHandler/useAnimatedGestureHandlerto RNGH v2GestureDetector/Gesture.Pan(), and removes thepanGestureHandlerPropspass-through from bothBottomSheet/BottomSheetDialog(with accompanying story/test updates and migration docs).Updates tests for React 19 by consolidating hook testing into
@testing-library/react-native’srenderHook, adding a sharedcreateRenderer()helper to wrapreact-test-rendererinact(), and adjusting a few component typings/prop forwarding for updated React types.Fixes Storybook RN breakage by switching config to
deviceAddons, changing Storybook RN scripts toexpo start(Expo Go), pinning Storybook packages, and applying a Yarn patch to@storybook/addon-ondevice-controlsto add explicit.jsextensions in built imports.Reviewed by Cursor Bugbot for commit 9a54c91. Bugbot is set up for automated code reviews on this repo. Configure here.