Skip to content

feat(mobile): document react-navigation modal pattern using ModalContext#756

Open
adrienzheng-cb wants to merge 2 commits into
masterfrom
adrien/modal-content
Open

feat(mobile): document react-navigation modal pattern using ModalContext#756
adrienzheng-cb wants to merge 2 commits into
masterfrom
adrien/modal-content

Conversation

@adrienzheng-cb

@adrienzheng-cb adrienzheng-cb commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What changed? Why?

Background

This PR addresses the mobile Modal refresh goal of enabling Modal sub-components (ModalHeader, ModalBody, ModalFooter) to be used inside react-navigation modal screens — where react-navigation owns the overlay, transition animation, and swipe-to-dismiss gesture, making the existing Modal component inappropriate.

Decision process: why no new component

The initial approach explored introducing a new component (variously named ModalContent, NavigationModal, and ModalLayout) to serve as a layout wrapper for navigation screens. After iterating on the design, we decided against it for the following reasons:

  1. Modal doesn't own its children's structure. Modal is a pure overlay mechanism — it renders whatever children are passed to it verbatim. Introducing a companion layout component (ModalLayout) that does dictate structure would be philosophically inconsistent.

  2. The wrapper adds very little value. When examined closely, any such component would reduce to a thin ModalContext.Provider wrapper around a VStack. The animation and SafeAreaView layers inside Modal are overlay-specific and can't be cleanly shared. Making them conditional would leak Modal's concerns into the layout component.

  3. @coinbase/cds-common is accessible to consumers. The ModalContext that sub-components depend on lives in @coinbase/cds-common, which consumers typically install alongside @coinbase/cds-mobile. The package exposes a ./* wildcard export, so @coinbase/cds-common/overlays/ModalContext is a valid public import path — no re-export needed.

  4. Consistent with web precedent. On web, FullscreenModal and FullscreenModalLayout are composed independently — the overlay component doesn't force a specific chrome structure on consumers.

What changed

  • Docs (apps/docs/docs/components/overlay/Modal/_mobileExamples.mdx): Added a "Using with react-navigation" section documenting the recommended pattern: wrap screen content with ModalContext.Provider + VStack, compose ModalHeader / ModalBody / ModalFooter directly. No Modal wrapper needed.

  • test-expo story (packages/mobile/src/overlays/__stories__/ModalNavigation.stories.tsx): New ModalNavigation story that demonstrates the pattern as a real react-navigation modal screen (presented with presentation: "modal"). Deep link: testexpo:///DebugModalNavigation.

  • test-expo routes (apps/expo-app/src/routes.ts): Added ModalNavigation route with presentation: 'modal' as const.

  • PlaygroundRoute type (apps/expo-app/src/playground/PlaygroundRoute.ts): Added optional options and hideFromList fields to support per-route navigation options.

  • Playground (apps/expo-app/src/playground/Playground.tsx): Updated to pass route.options to Stack.Screen and filter hidden routes from the list.

UI changes

Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-06-12.at.14.30.33.mov

Testing

How has it been tested?

  • Manual - iOS (Simulator)

Testing instructions

  1. Run yarn nx run expo-app:start
  2. Navigate to ModalNavigation in the examples list, or deep link via testexpo:///DebugModalNavigation
  3. The screen should present as a full-screen modal sliding up from the bottom with a CDS header, scrollable body, and footer actions
  4. The close button and "Cancel" button should dismiss the modal via navigation.goBack()

Change management

type=routine
risk=low
impact=sev5

automerge=false

Made with Cursor

Adds a recommended pattern for using Modal sub-components (ModalHeader,
ModalBody, ModalFooter) inside react-navigation modal screens, without
introducing a new public component.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cb-heimdall

cb-heimdall commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants