From 4af3f5837efa776203db2bcf988792b7778aafc2 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:18:30 -0300 Subject: [PATCH 1/6] fix: support expo router modal wrappers --- .changeset/sharp-cars-approve.md | 5 + examples/expo-multichain/app/_layout.tsx | 4 +- jest.config.ts | 14 ++- packages/appkit/jest.config.ts | 6 +- .../__tests__/hooks/useAppKitTheme.test.tsx | 24 ++-- .../src/__tests__/modal/w3m-modal.test.tsx | 113 ++++++++++++++++++ packages/appkit/src/index.ts | 7 +- packages/appkit/src/modal/w3m-modal/index.tsx | 42 ++++--- packages/common/jest.config.ts | 6 +- packages/core/jest.config.ts | 6 +- packages/ui/jest.config.ts | 6 +- 11 files changed, 198 insertions(+), 35 deletions(-) create mode 100644 .changeset/sharp-cars-approve.md create mode 100644 packages/appkit/src/__tests__/modal/w3m-modal.test.tsx diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md new file mode 100644 index 000000000..3b6bde112 --- /dev/null +++ b/.changeset/sharp-cars-approve.md @@ -0,0 +1,5 @@ +--- +'@reown/appkit-react-native': patch +--- + +feat: add modalWrapper prop for custom modal window wrappers diff --git a/examples/expo-multichain/app/_layout.tsx b/examples/expo-multichain/app/_layout.tsx index 6119a864a..96ddf435e 100644 --- a/examples/expo-multichain/app/_layout.tsx +++ b/examples/expo-multichain/app/_layout.tsx @@ -96,7 +96,9 @@ export default function RootLayout() { - {/* This is a workaround for the Android modal issue. https://github.com/expo/expo/issues/32991#issuecomment-2489620459 */} + {/* Mount AppKit once in the root layout to avoid Android Expo Router modal layering issues. + If your app already uses react-native-screens and transparentModal still hides the modal, + pass modalWrapper={FullWindowOverlay} here. */} diff --git a/jest.config.ts b/jest.config.ts index 98a897090..5d12cd6dd 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,3 +1,5 @@ +const path = require('path'); + module.exports = { preset: 'react-native', modulePathIgnorePatterns: ['/node_modules', '/lib/'], @@ -8,6 +10,16 @@ module.exports = { '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { configFile: './babel.config.js' }] }, moduleNameMapper: { - '^@shared-jest-setup$': '/jest-shared-setup.ts' + '^@shared-jest-setup$': '/jest-shared-setup.ts', + '^@reown/appkit-react-native$': path.join(__dirname, 'packages/appkit/src/index.ts'), + '^@reown/appkit-bitcoin-react-native$': path.join(__dirname, 'packages/bitcoin/src/index.tsx'), + '^@reown/appkit-coinbase-react-native$': path.join(__dirname, 'packages/coinbase/src/index.ts'), + '^@reown/appkit-common-react-native$': path.join(__dirname, 'packages/common/src/index.ts'), + '^@reown/appkit-core-react-native$': path.join(__dirname, 'packages/core/src/index.ts'), + '^@reown/appkit-ethers-react-native$': path.join(__dirname, 'packages/ethers/src/index.tsx'), + '^@reown/appkit-react-native-cli$': path.join(__dirname, 'packages/cli/src/index.ts'), + '^@reown/appkit-solana-react-native$': path.join(__dirname, 'packages/solana/src/index.ts'), + '^@reown/appkit-ui-react-native$': path.join(__dirname, 'packages/ui/src/index.ts'), + '^@reown/appkit-wagmi-react-native$': path.join(__dirname, 'packages/wagmi/src/index.tsx') } }; diff --git a/packages/appkit/jest.config.ts b/packages/appkit/jest.config.ts index f2fb133e4..93551e068 100644 --- a/packages/appkit/jest.config.ts +++ b/packages/appkit/jest.config.ts @@ -1,8 +1,10 @@ +const rootConfig = require('../../jest.config'); + const appkitConfig = { - ...require('../../jest.config'), + ...rootConfig, setupFilesAfterEnv: ['./jest-setup.ts'], - // Override the moduleNameMapper to use the correct path from the package moduleNameMapper: { + ...rootConfig.moduleNameMapper, '^@shared-jest-setup$': '../../jest-shared-setup.ts' } }; diff --git a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx index cfc5ea8dd..37a912d6d 100644 --- a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx +++ b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx @@ -18,16 +18,20 @@ jest.mock('valtio', () => ({ })); // Mock ThemeController -jest.mock('@reown/appkit-core-react-native', () => ({ - ThemeController: { - state: { - themeMode: 'light', - themeVariables: {} - }, - setDefaultThemeMode: jest.fn(), - setThemeVariables: jest.fn() - } -})); +jest.mock( + '@reown/appkit-core-react-native', + () => ({ + ThemeController: { + state: { + themeMode: 'light', + themeVariables: {} + }, + setDefaultThemeMode: jest.fn(), + setThemeVariables: jest.fn() + } + }), + { virtual: true } +); describe('useAppKitTheme', () => { const mockAppKit = {} as AppKit; diff --git a/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx new file mode 100644 index 000000000..6c340aa6e --- /dev/null +++ b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; +import { View } from 'react-native'; + +const mockClose = jest.fn(); +const mockPrefetch = jest.fn().mockResolvedValue(undefined); +const mockSendEvent = jest.fn(); + +jest.mock('valtio', () => ({ + useSnapshot: jest.fn((state: Record) => state) +})); + +jest.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })) +})); + +jest.mock('../../AppKitContext', () => ({ + useInternalAppKit: jest.fn(() => ({ + close: mockClose + })) +})); + +jest.mock('../../partials/w3m-header', () => ({ + Header: () => 'Header' +})); + +jest.mock('../../partials/w3m-snackbar', () => ({ + Snackbar: () => 'Snackbar' +})); + +jest.mock('../../modal/w3m-router', () => ({ + AppKitRouter: () => 'Router' +})); + +jest.mock( + '@reown/appkit-ui-react-native', + () => { + const { View: MockView } = require('react-native'); + + return { + Card: ({ children, ...props }: any) => ( + + {children} + + ), + Modal: ({ children, testID }: any) => {children}, + ThemeProvider: ({ children }: any) => <>{children} + }; + }, + { virtual: true } +); + +jest.mock( + '@reown/appkit-core-react-native', + () => ({ + ApiController: { + prefetch: mockPrefetch + }, + EventsController: { + sendEvent: mockSendEvent + }, + ModalController: { + state: { + open: true + } + }, + OptionsController: { + state: { + projectId: 'project-id' + } + }, + RouterController: { + state: { + history: ['Connect'] + }, + goBack: jest.fn() + }, + ThemeController: { + state: { + themeMode: 'light', + themeVariables: {} + }, + setSystemThemeMode: jest.fn() + } + }), + { virtual: true } +); + +const { AppKit } = require('../../modal/w3m-modal'); + +describe('AppKit modal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the modal without a wrapper', () => { + const { getByTestId, queryByTestId } = render(); + + expect(getByTestId('w3m-modal')).toBeTruthy(); + expect(queryByTestId('modal-wrapper')).toBeNull(); + }); + + it('wraps the modal when modalWrapper is provided', () => { + function ModalWrapper({ children }: { children: React.ReactNode }) { + return {children}; + } + + const { getByTestId } = render(); + + expect(getByTestId('modal-wrapper')).toBeTruthy(); + expect(getByTestId('w3m-modal')).toBeTruthy(); + }); +}); diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index cef464c0b..57ad6f20f 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -14,7 +14,12 @@ export { NetworkButton as NetworkButton, type NetworkButtonProps as NetworkButtonProps } from './modal/w3m-network-button'; -export { AppKit } from './modal/w3m-modal'; +export { + AppKit, + type AppKitProps, + type AppKitModalWrapperComponent, + type AppKitModalWrapperProps +} from './modal/w3m-modal'; /********** Types **********/ export type * from '@reown/appkit-core-react-native'; diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 4ea75fa09..d5ead915b 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, type ComponentType, type ReactNode } from 'react'; import { useColorScheme } from 'react-native'; import { Card, Modal, ThemeProvider } from '@reown/appkit-ui-react-native'; import { @@ -18,7 +18,17 @@ import { Snackbar } from '../../partials/w3m-snackbar'; import { useInternalAppKit } from '../../AppKitContext'; import styles from './styles'; -export function AppKit() { +export interface AppKitModalWrapperProps { + children: ReactNode; +} + +export type AppKitModalWrapperComponent = ComponentType; + +export interface AppKitProps { + modalWrapper?: AppKitModalWrapperComponent; +} + +export function AppKit({ modalWrapper: ModalWrapper }: AppKitProps) { const theme = useColorScheme(); const { bottom, top } = useSafeAreaInsets(); const { close } = useInternalAppKit(); @@ -53,20 +63,24 @@ export function AppKit() { } }, [projectId, prefetch]); + const modal = ( + + +
+ + + + + ); + return ( - - -
- - - - + {ModalWrapper ? {modal} : modal} ); } diff --git a/packages/common/jest.config.ts b/packages/common/jest.config.ts index 1704bb3e8..daca28c61 100644 --- a/packages/common/jest.config.ts +++ b/packages/common/jest.config.ts @@ -1,8 +1,10 @@ +const rootConfig = require('../../jest.config'); + const commonConfig = { - ...require('../../jest.config'), + ...rootConfig, setupFilesAfterEnv: ['./jest-setup.ts'], - // Override the moduleNameMapper to use the correct path from the package moduleNameMapper: { + ...rootConfig.moduleNameMapper, '^@shared-jest-setup$': '../../jest-shared-setup.ts' } }; diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 21e9e0b9f..2666b21e3 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -1,8 +1,10 @@ +const rootConfig = require('../../jest.config'); + const coreConfig = { - ...require('../../jest.config'), + ...rootConfig, setupFilesAfterEnv: ['./jest-setup.ts'], - // Override the moduleNameMapper to use the correct path from the package moduleNameMapper: { + ...rootConfig.moduleNameMapper, '^@shared-jest-setup$': '../../jest-shared-setup.ts' } }; diff --git a/packages/ui/jest.config.ts b/packages/ui/jest.config.ts index 2892ee1d4..aeface904 100644 --- a/packages/ui/jest.config.ts +++ b/packages/ui/jest.config.ts @@ -1,8 +1,10 @@ +const rootConfig = require('../../jest.config'); + const uiConfig = { - ...require('../../jest.config'), + ...rootConfig, setupFilesAfterEnv: ['./jest-setup.ts'], - // Override the moduleNameMapper to use the correct path from the package moduleNameMapper: { + ...rootConfig.moduleNameMapper, '^@shared-jest-setup$': '../../jest-shared-setup.ts' } }; From 4d9e9a59f0c8c40cb4a57f2ef6ec4eabe809596b Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:29:50 -0300 Subject: [PATCH 2/6] chore: align release bumps for appkit adapters --- .changeset/sharp-cars-approve.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md index 3b6bde112..3abccfeba 100644 --- a/.changeset/sharp-cars-approve.md +++ b/.changeset/sharp-cars-approve.md @@ -1,5 +1,7 @@ --- '@reown/appkit-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wagmi-react-native': patch --- feat: add modalWrapper prop for custom modal window wrappers From e4a938d124c17fdc30c52f9755cbe1ea6178bf06 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:31:13 -0300 Subject: [PATCH 3/6] chore: bump all packages in changeset --- .changeset/sharp-cars-approve.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md index 3abccfeba..a9eda01f3 100644 --- a/.changeset/sharp-cars-approve.md +++ b/.changeset/sharp-cars-approve.md @@ -1,6 +1,13 @@ --- -'@reown/appkit-react-native': patch +'@reown/appkit-bitcoin-react-native': patch +'@reown/appkit-coinbase-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-core-react-native': patch '@reown/appkit-ethers-react-native': patch +'@reown/appkit-react-native': patch +'@reown/appkit-react-native-cli': patch +'@reown/appkit-solana-react-native': patch +'@reown/appkit-ui-react-native': patch '@reown/appkit-wagmi-react-native': patch --- From f98c49f9894d685b15744ac01e38503c51ff2937 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:39:30 -0300 Subject: [PATCH 4/6] fix: wrap modal content inside RN modal --- .changeset/sharp-cars-approve.md | 2 +- examples/expo-multichain/app/_layout.tsx | 2 +- .../src/__tests__/modal/w3m-modal.test.tsx | 37 +++++++++--- packages/appkit/src/index.ts | 4 +- packages/appkit/src/modal/w3m-modal/index.tsx | 37 ++++++------ .../ui/src/components/wui-modal/index.tsx | 59 +++++++++++++++---- packages/ui/src/index.ts | 7 ++- 7 files changed, 101 insertions(+), 47 deletions(-) diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md index a9eda01f3..0531e1f96 100644 --- a/.changeset/sharp-cars-approve.md +++ b/.changeset/sharp-cars-approve.md @@ -11,4 +11,4 @@ '@reown/appkit-wagmi-react-native': patch --- -feat: add modalWrapper prop for custom modal window wrappers +feat: add modalContentWrapper prop for custom modal content wrappers diff --git a/examples/expo-multichain/app/_layout.tsx b/examples/expo-multichain/app/_layout.tsx index 96ddf435e..95d4e5183 100644 --- a/examples/expo-multichain/app/_layout.tsx +++ b/examples/expo-multichain/app/_layout.tsx @@ -98,7 +98,7 @@ export default function RootLayout() { {/* Mount AppKit once in the root layout to avoid Android Expo Router modal layering issues. If your app already uses react-native-screens and transparentModal still hides the modal, - pass modalWrapper={FullWindowOverlay} here. */} + pass modalContentWrapper={FullWindowOverlay} here. */} diff --git a/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx index 6c340aa6e..02e153d19 100644 --- a/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx +++ b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx @@ -5,6 +5,9 @@ import { View } from 'react-native'; const mockClose = jest.fn(); const mockPrefetch = jest.fn().mockResolvedValue(undefined); const mockSendEvent = jest.fn(); +const modalState = { + open: true +}; jest.mock('valtio', () => ({ useSnapshot: jest.fn((state: Record) => state) @@ -43,7 +46,15 @@ jest.mock( {children} ), - Modal: ({ children, testID }: any) => {children}, + Modal: ({ children, testID, visible, contentWrapper: ContentWrapper }: any) => { + if (!visible) { + return null; + } + + const content = ContentWrapper ? {children} : children; + + return {content}; + }, ThemeProvider: ({ children }: any) => <>{children} }; }, @@ -60,9 +71,7 @@ jest.mock( sendEvent: mockSendEvent }, ModalController: { - state: { - open: true - } + state: modalState }, OptionsController: { state: { @@ -91,6 +100,7 @@ const { AppKit } = require('../../modal/w3m-modal'); describe('AppKit modal', () => { beforeEach(() => { jest.clearAllMocks(); + modalState.open = true; }); it('renders the modal without a wrapper', () => { @@ -100,14 +110,23 @@ describe('AppKit modal', () => { expect(queryByTestId('modal-wrapper')).toBeNull(); }); - it('wraps the modal when modalWrapper is provided', () => { - function ModalWrapper({ children }: { children: React.ReactNode }) { - return {children}; + it('wraps modal content when modalContentWrapper is provided', () => { + function ModalContentWrapper({ children }: { children: React.ReactNode }) { + return {children}; } - const { getByTestId } = render(); + const { getByTestId } = render(); - expect(getByTestId('modal-wrapper')).toBeTruthy(); + expect(getByTestId('modal-content-wrapper')).toBeTruthy(); expect(getByTestId('w3m-modal')).toBeTruthy(); }); + + it('does not render modal content when closed', () => { + modalState.open = false; + + const { queryByTestId } = render(); + + expect(queryByTestId('w3m-modal')).toBeNull(); + expect(queryByTestId('mock-card')).toBeNull(); + }); }); diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 57ad6f20f..2b1bf9aaf 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -17,8 +17,8 @@ export { export { AppKit, type AppKitProps, - type AppKitModalWrapperComponent, - type AppKitModalWrapperProps + type AppKitModalContentWrapperComponent, + type AppKitModalContentWrapperProps } from './modal/w3m-modal'; /********** Types **********/ diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index d5ead915b..ff5bd8a01 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -18,17 +18,17 @@ import { Snackbar } from '../../partials/w3m-snackbar'; import { useInternalAppKit } from '../../AppKitContext'; import styles from './styles'; -export interface AppKitModalWrapperProps { +export interface AppKitModalContentWrapperProps { children: ReactNode; } -export type AppKitModalWrapperComponent = ComponentType; +export type AppKitModalContentWrapperComponent = ComponentType; export interface AppKitProps { - modalWrapper?: AppKitModalWrapperComponent; + modalContentWrapper?: AppKitModalContentWrapperComponent; } -export function AppKit({ modalWrapper: ModalWrapper }: AppKitProps) { +export function AppKit({ modalContentWrapper }: AppKitProps) { const theme = useColorScheme(); const { bottom, top } = useSafeAreaInsets(); const { close } = useInternalAppKit(); @@ -63,24 +63,21 @@ export function AppKit({ modalWrapper: ModalWrapper }: AppKitProps) { } }, [projectId, prefetch]); - const modal = ( - - -
- - - - - ); - return ( - {ModalWrapper ? {modal} : modal} + + +
+ + + + ); } diff --git a/packages/ui/src/components/wui-modal/index.tsx b/packages/ui/src/components/wui-modal/index.tsx index d814cfce2..5cba40d6e 100644 --- a/packages/ui/src/components/wui-modal/index.tsx +++ b/packages/ui/src/components/wui-modal/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, type ComponentType, type ReactNode } from 'react'; import { useWindowDimensions, Modal as RNModal, @@ -14,13 +14,27 @@ export type ModalProps = Pick< RNModalProps, 'visible' | 'onDismiss' | 'testID' | 'onRequestClose' > & { - children: React.ReactNode; + children: ReactNode; onBackdropPress?: () => void; + contentWrapper?: ModalContentWrapperComponent; }; +export interface ModalContentWrapperProps { + children: ReactNode; +} + +export type ModalContentWrapperComponent = ComponentType; + const AnimatedPressable = Animated.createAnimatedComponent(Pressable); -export function Modal({ visible, onBackdropPress, onRequestClose, testID, children }: ModalProps) { +export function Modal({ + visible, + onBackdropPress, + onRequestClose, + testID, + children, + contentWrapper: ContentWrapper +}: ModalProps) { const { height } = useWindowDimensions(); const Theme = useTheme(); const backdropOpacity = useRef(new Animated.Value(0)).current; @@ -120,16 +134,35 @@ export function Modal({ visible, onBackdropPress, onRequestClose, testID, childr onRequestClose={onRequestClose} testID={testID} > - {showBackdrop ? ( - - ) : null} - - {children} - - + {ContentWrapper ? ( + + <> + {showBackdrop ? ( + + ) : null} + + {children} + + + + + ) : ( + <> + {showBackdrop ? ( + + ) : null} + + {children} + + + + )} ); } diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f2e8efa4a..4de0945d8 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -50,7 +50,12 @@ export { ListTransaction, type ListTransactionProps } from './composites/wui-lis export { ListWallet, type ListWalletProps } from './composites/wui-list-wallet'; export { Logo, type LogoProps } from './composites/wui-logo'; export { LogoSelect, type LogoSelectProps } from './composites/wui-logo-select'; -export { Modal, type ModalProps } from './components/wui-modal'; +export { + Modal, + type ModalContentWrapperComponent, + type ModalContentWrapperProps, + type ModalProps +} from './components/wui-modal'; export { NetworkButton, type NetworkButtonProps } from './composites/wui-network-button'; export { NetworkImage, type NetworkImageProps } from './composites/wui-network-image'; export { NumericKeyboard, type NumericKeyboardProps } from './composites/wui-numeric-keyboard'; From dbe5a80225478926f5fa2531b409ca4c95e39a3c Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:45:23 -0300 Subject: [PATCH 5/6] chore: code improvements --- .changeset/sharp-cars-approve.md | 1 - .../ui/src/components/wui-modal/index.tsx | 45 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md index 0531e1f96..a280c1443 100644 --- a/.changeset/sharp-cars-approve.md +++ b/.changeset/sharp-cars-approve.md @@ -5,7 +5,6 @@ '@reown/appkit-core-react-native': patch '@reown/appkit-ethers-react-native': patch '@reown/appkit-react-native': patch -'@reown/appkit-react-native-cli': patch '@reown/appkit-solana-react-native': patch '@reown/appkit-ui-react-native': patch '@reown/appkit-wagmi-react-native': patch diff --git a/packages/ui/src/components/wui-modal/index.tsx b/packages/ui/src/components/wui-modal/index.tsx index 5cba40d6e..3ee44228b 100644 --- a/packages/ui/src/components/wui-modal/index.tsx +++ b/packages/ui/src/components/wui-modal/index.tsx @@ -125,6 +125,21 @@ export function Modal({ } }, [modalVisible, translateY, backdropOpacity, height]); + const modalContent = ( + <> + {showBackdrop ? ( + + ) : null} + + {children} + + + + ); + return ( - {ContentWrapper ? ( - - <> - {showBackdrop ? ( - - ) : null} - - {children} - - - - - ) : ( - <> - {showBackdrop ? ( - - ) : null} - - {children} - - - - )} + {ContentWrapper ? {modalContent} : modalContent} ); } From f6b43cc3e0c81aea51fbecc1534d507c463db3af Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:23:53 -0300 Subject: [PATCH 6/6] chore: minor changes --- .changeset/sharp-cars-approve.md | 2 +- packages/appkit/src/__tests__/modal/w3m-modal.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/sharp-cars-approve.md b/.changeset/sharp-cars-approve.md index a280c1443..5df25ad17 100644 --- a/.changeset/sharp-cars-approve.md +++ b/.changeset/sharp-cars-approve.md @@ -10,4 +10,4 @@ '@reown/appkit-wagmi-react-native': patch --- -feat: add modalContentWrapper prop for custom modal content wrappers +fix: add modalContentWrapper prop to work around Expo Router modal layering issues diff --git a/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx index 02e153d19..1adac9d97 100644 --- a/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx +++ b/packages/appkit/src/__tests__/modal/w3m-modal.test.tsx @@ -107,7 +107,7 @@ describe('AppKit modal', () => { const { getByTestId, queryByTestId } = render(); expect(getByTestId('w3m-modal')).toBeTruthy(); - expect(queryByTestId('modal-wrapper')).toBeNull(); + expect(queryByTestId('modal-content-wrapper')).toBeNull(); }); it('wraps modal content when modalContentWrapper is provided', () => {