Skip to content

chore: Align React Native and Expo dependencies with mobile#1165

Merged
georgewrmarshall merged 26 commits into
mainfrom
codex/align-rn-expo-dependencies
May 21, 2026
Merged

chore: Align React Native and Expo dependencies with mobile#1165
georgewrmarshall merged 26 commits into
mainfrom
codex/align-rn-expo-dependencies

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 dependencies

Package Before After Mobile (MetaMask/metamask-mobile#29195)
react 18.3.1 19.1.0 19.1.0
react-native 0.76.9 0.81.5 0.81.5
expo ~52.0.49 ~54.0.34 54.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.9
react-native-gesture-handler ~2.25.0 ~2.28.0 ~2.28.0
react-native-reanimated ~3.17.2 ~4.1.1 3.19.0 (mobile still compatible)
react-native-worklets 0.5.1

apps/storybook-react-native — dev dependencies

Package Before After Mobile (MetaMask/metamask-mobile#29195)
@react-native-async-storage/async-storage ^1.23.1 2.2.0 2.2.0
@react-native-community/datetimepicker ^8.5.1 ^8.5.1 ^8.5.1
@react-native-community/slider ^4.5.6 5.0.1 5.0.1
@storybook/react-native ^10 10.4.0
@storybook/addon-ondevice-actions ^10 10.4.0
@storybook/addon-ondevice-backgrounds ^10 10.4.0
@storybook/addon-ondevice-controls ^10 10.4.0 (patched)
@storybook/addon-ondevice-notes ^10 10.4.0
storybook ^10.3.1 10.3.6
@types/react ^18.2.0 ^19.1.0 ^19.1.0
@types/react-dom ^18.2.0 ^19.1.0
react-dom 18.3.1 19.1.0
react-native-safe-area-context ^5.4.0 ~5.6.0 ~5.6.0
react-native-svg ~15.11.2 15.12.1 15.12.1
react-native-svg-transformer ^1.5.0 ^1.5.3

packages/design-system-react-native — dev dependencies

Package Before After Mobile (MetaMask/metamask-mobile#29195)
react 18.3.1 19.1.0 19.1.0
react-native 0.76.9 0.81.5 0.81.5
react-test-renderer ^18.3.1 19.1.0 19.1.0
@react-native/babel-preset 0.76.9 0.81.5 0.76.9
@react-native/typescript-config 0.76.9 0.81.5 0.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 removed
@storybook/react-native ^10 10.4.0
react-native-gesture-handler ~2.25.0 ~2.28.0 ~2.28.0
react-native-reanimated ~3.17.2 ~4.1.1 3.19.0
react-native-safe-area-context ^5.4.0 ~5.6.0 ~5.6.0
react-native-svg ~15.11.2 15.12.1 15.12.1
react-native-svg-transformer ^1.5.0 ^1.5.3
react-native-worklets 0.5.1

packages/design-system-shared — dev dependencies

Package Before After Mobile (MetaMask/metamask-mobile#29195)
react 18.3.1 19.1.0 19.1.0
@types/react ^18.2.0 ^19.1.0 ^19.1.0

packages/design-system-twrnc-preset — dev dependencies

Package Before After Mobile (MetaMask/metamask-mobile#29195)
react 18.3.1 19.1.0 19.1.0
react-test-renderer ^18.3.1 19.1.0 19.1.0
@types/react ^19.1.0 ^19.1.0
@types/react-test-renderer ^18 ^19.1.0

apps/storybook-react — dev dependencies

Package Before After
react 18.3.1 19.1.0
react-dom 18.3.1 19.1.0
@types/react ^18.2.0 ^19.1.0
@types/react-dom ^18.2.0 ^19.1.0
storybook ^10.3.1 10.3.6

Additional changes

  • Migrated BottomSheetDialog from the deprecated RNGH v1 PanGestureHandler/useAnimatedGestureHandler API to the RNGH v2 GestureDetector/Gesture.Pan() API
  • Migrated hook tests from @testing-library/react-hooks (removed) to renderHook from @testing-library/react-native
  • Added react-native-worklets to Jest transformIgnorePatterns
  • Added a yarn patch for @storybook/addon-ondevice-controls@10.4.0 (upstream bug fix)
  • Switched Storybook scripts from expo run:ios/android (native builds) to expo start --ios/android (Expo Go) — previously required a full native build; now works out of the box with the upgraded Expo 54 baseline
  • Pinned Storybook packages to exact versions for reproducibility

Manual testing steps

  1. Run yarn storybook:ios — confirm Expo Go launches and stories render
  2. Run yarn storybook:android — confirm Expo Go launches and stories render
  3. Run yarn test — confirm all tests pass
  4. Run yarn build — confirm all packages build without errors
  5. Run yarn lint — confirm no lint errors

Screenshots/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 BottomSheet with reanimated library updates in mobile

Screen.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

  • I've followed MetaMask Contributor Docs
  • I've completed the PR template to the best of my ability
  • I've included tests if applicable
  • I've documented my code using JSDoc format if applicable
  • I've applied the right labels on the PR (see labeling guidelines). 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.

Note

Medium Risk
Moderate risk due to broad React/React Native/Expo and Storybook version bumps plus a migration of BottomSheetDialog gesture 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-worklets and updating Jest transforms).

Migrates BottomSheetDialog from deprecated RNGH v1 PanGestureHandler/useAnimatedGestureHandler to RNGH v2 GestureDetector/Gesture.Pan(), and removes the panGestureHandlerProps pass-through from both BottomSheet/BottomSheetDialog (with accompanying story/test updates and migration docs).

Updates tests for React 19 by consolidating hook testing into @testing-library/react-native’s renderHook, adding a shared createRenderer() helper to wrap react-test-renderer in act(), 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 to expo start (Expo Go), pinning Storybook packages, and applying a Yarn patch to @storybook/addon-ondevice-controls to add explicit .js extensions in built imports.

Reviewed by Cursor Bugbot for commit 9a54c91. Bugbot is set up for automated code reviews on this repo. Configure here.

@socket-security

socket-security Bot commented May 7, 2026

Copy link
Copy Markdown

@socket-security

socket-security Bot commented May 7, 2026

Copy link
Copy Markdown

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:

  • @ungap/structured-clone@1.3.0
  • @expo/schema-utils@0.1.8
  • lan-network@0.2.1
  • accepts@2.0.0
  • yaml@2.8.4
  • expo-status-bar@3.0.9
  • expo-keep-awake@15.0.8
  • @react-native/debugger-frontend@0.81.5
  • @storybook/addon-ondevice-notes@10.4.0
  • @storybook/react-native@10.4.0
  • tar@7.5.13
  • @expo/cli@54.0.24
  • @expo/metro-config@54.0.15
  • react-devtools-core@6.1.5
  • @expo/devcert@1.2.1
  • @expo/plist@0.4.8

View full report

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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.

Comment thread packages/design-system-react-native/package.json
@georgewrmarshall georgewrmarshall changed the title [codex] Align React Native and Expo dependencies chore: Align React Native and Expo dependencies May 20, 2026
@georgewrmarshall georgewrmarshall self-assigned this May 20, 2026
@georgewrmarshall georgewrmarshall changed the title chore: Align React Native and Expo dependencies chore: Align React Native and Expo dependencies with mobile May 20, 2026
-import NoControlsWarning from './NoControlsWarning';
-import PropForm from './PropForm';
-import { useArgs } from './hooks';
+import NoControlsWarning from './NoControlsWarning.js';

@georgewrmarshall georgewrmarshall May 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: [

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

Addressing the Cursor Bugbot comment at #discussion_r3276645252 — this is a false positive on two counts.

First, metro-react-native-babel-transformer does not follow React Native's versioning scheme. The latest published version is 0.77.0 — there is no 0.81.x. The version numbers are independent of the RN release cadence, so 0.76.9 staying at 0.76.9 is not a mismatch.

Second, the package was never actually used. The react-native jest preset sets transform to babel-jest directly (not this transformer), and the package's own jest config also overrides transform with babel-jest. Neither execution path ever loads metro-react-native-babel-transformer at runtime.

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",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 simultaneousHandlers silently dropped in gesture migration
    • Mapped panGestureHandlerProps.simultaneousHandlers to gesture.simultaneousWithExternalGesture in applyPanGestureProps to restore simultaneous gestures on Android.

Create PR

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.

);
expect(getByTestId('action-item')).toBeDefined();
});

@georgewrmarshall georgewrmarshall May 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

Addressing Cursor Bugbot at #discussion_r3277501489 — this is a valid finding. The bugbot correctly identified that simultaneousHandlers was silently dropped.

Rather than patching applyPanGestureProps to add gesture.simultaneousWith(), we took the more decisive fix: panGestureHandlerProps has been removed entirely in e76b9d2. The prop was a compatibility shim from the RNGH v1 PanGestureHandler JSX API and was not used anywhere in the MetaMask Mobile or Extension consumer codebases. Since simultaneousHandlers was already broken and the entire prop surface was unverified against the v2 API, removal is cleaner than an incomplete fix.

The MIGRATION.md documents the breaking change and notes that first-class simultaneous gesture handling will be addressed as a follow-up if needed.

PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(() => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'
>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }) => {

@georgewrmarshall georgewrmarshall May 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@github-actions

Copy link
Copy Markdown
Contributor

📖 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': {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)/)',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

Gesture: {
Pan: () => {
const gesture: Record<string, unknown> = {};
const noop = () => gesture;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/design-system-react": "0.23.1-preview.047f96c",
  "@metamask-previews/design-system-react-native": "0.26.0-preview.047f96c",
  "@metamask-previews/design-system-shared": "0.19.0-preview.047f96c",
  "@metamask-previews/design-system-tailwind-preset": "0.8.0-preview.047f96c",
  "@metamask-previews/design-system-twrnc-preset": "0.4.2-preview.047f96c",
  "@metamask-previews/design-tokens": "8.4.0-preview.047f96c"
}

@georgewrmarshall

georgewrmarshall commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

@SocketSecurity ignore npm/@expo/metro-config@54.0.15
@SocketSecurity ignore npm/@expo/cli@54.0.24
@SocketSecurity ignore npm/@expo/devcert@1.2.1
@SocketSecurity ignore npm/@expo/plist@0.4.8
@SocketSecurity ignore npm/@expo/schema-utils@0.1.8
@SocketSecurity ignore npm/expo-keep-awake@15.0.8
@SocketSecurity ignore npm/expo-status-bar@3.0.9
@SocketSecurity ignore npm/lan-network@0.2.1
@SocketSecurity ignore npm/react-devtools-core@6.1.5
@SocketSecurity ignore npm/@react-native/debugger-frontend@0.81.5
@SocketSecurity ignore npm/@ungap/structured-clone@1.3.0
@SocketSecurity ignore npm/accepts@2.0.0
@SocketSecurity ignore npm/tar@7.5.13
@SocketSecurity ignore npm/yaml@2.8.4
@SocketSecurity ignore npm/@storybook/addon-ondevice-notes@10.4.0
@SocketSecurity ignore npm/@storybook/react-native@10.4.0

All are devDependencies in apps/storybook-react-native — not shipped in any published package or consumer bundle.

  • @expo/* and expo-* packages (metro-config, cli, devcert, plist, schema-utils, keep-awake, status-bar): Official Expo team packages (https://github.com/expo/expo), part of the Expo 54 development toolchain.
  • lan-network@0.2.1: Transitive dep of Metro bundler, used for local network interface detection in the Expo dev server.
  • react-devtools-core@6.1.5: Official Meta/React DevTools package (https://github.com/facebook/react), required by the React Native debugger.
  • @react-native/debugger-frontend@0.81.5: Official Meta package for the React Native debugger UI, versioned with RN 0.81.5.
  • @ungap/structured-clone@1.3.0: Widely-used polyfill for structuredClone (https://github.com/ungap/structured-clone), transitive dep of Metro bundler.
  • accepts@2.0.0: Standard HTTP content-negotiation utility (https://github.com/jshttp/accepts), transitive dep of the Express-based dev server in Metro.
  • tar@7.5.13: Standard archive utility (https://github.com/isaacs/node-tar), used by npm/yarn tooling.
  • yaml@2.8.4: Standard YAML parser (https://github.com/eemeli/yaml), transitive dep of various Expo config tools.
  • @storybook/addon-ondevice-notes@10.4.0: Official Storybook React Native package (https://github.com/storybookjs/react-native). The obfuscation signal is a false positive common to bundled npm packages — Storybook minifies its dist output in a way that pattern-matches as obfuscated. Source is open and auditable.
  • @storybook/react-native@10.4.0: Official Storybook React Native core package (https://github.com/storybookjs/react-native). The AI anomaly signal is low-confidence (62%) and typical of larger framework packages with complex module graphs.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall marked this pull request as ready for review May 21, 2026 20:42
@georgewrmarshall georgewrmarshall requested a review from a team as a code owner May 21, 2026 20:42
@georgewrmarshall georgewrmarshall enabled auto-merge (squash) May 21, 2026 22:31
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit 57860fa into main May 21, 2026
78 of 81 checks passed
@georgewrmarshall georgewrmarshall deleted the codex/align-rn-expo-dependencies branch May 21, 2026 22:47
georgewrmarshall added a commit that referenced this pull request May 26, 2026
…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.
georgewrmarshall added a commit that referenced this pull request May 26, 2026
… 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 -->
@georgewrmarshall georgewrmarshall mentioned this pull request May 29, 2026
18 tasks
georgewrmarshall added a commit that referenced this pull request May 29, 2026
## 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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants