Skip to content

feat: Handle onPress in iOS Menu#4148

Open
kmichalikk wants to merge 2 commits into
@kmichalikk/stack-v5-header-menufrom
@kmichalikk/stack-v5-header-menu-onpress
Open

feat: Handle onPress in iOS Menu#4148
kmichalikk wants to merge 2 commits into
@kmichalikk/stack-v5-header-menufrom
@kmichalikk/stack-v5-header-menu-onpress

Conversation

@kmichalikk

@kmichalikk kmichalikk commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Closes https://github.com/software-mansion/react-native-screens-labs/issues/1521

Description

This PR adds support for onPress in iOS Stack v5 header.

Changes

  • added required menuElementId for all menuElements, currently used for onPress in menu items
  • added optional onPress for menu item - a callback defined per menu item

Before & after - visual documentation

N/A

Test plan

Use test-stack-header-menu-ios, follow the scenario.md

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

Copilot AI 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.

Pull request overview

Adds end-to-end support for per-menu-item onPress callbacks in the experimental iOS Stack v5 header menu system by introducing stable menuElementId routing from native (UIAction) back to JS.

Changes:

  • Introduces menuElementId (required) on all iOS header menu elements and adds optional JS-side onPress on menu items.
  • Wires native menu selection to Fabric event emission (onPressMenuItem) and dispatches to the correct JS menu item handler via ID lookup.
  • Updates the single-feature test app + scenario instructions to exercise menu presses and show Toast feedback.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/fabric/gamma/stack/StackHeaderConfigIOSNativeComponent.ts Adds onPressMenuItem direct event + payload type for Fabric codegen.
src/components/gamma/stack/header/utils.ts Adds recursive lookup helpers to resolve a menu element by menuElementId.
src/components/gamma/stack/header/StackHeaderConfig.ios.types.ts Introduces SupportsMenuIOS and reuses it across header item types.
src/components/gamma/stack/header/StackHeaderConfig.ios.tsx Subscribes to native onPressMenuItem and invokes the matched JS menu item onPress.
src/components/gamma/stack/header/ios/StackHeaderMenu.ios.types.ts Makes menuElementId required and adds optional onPress on menu items.
ios/gamma/stack/header/RNSStackHeaderMenuMapper.mm Parses/validates menuElementId and constructs menu/menuItem data with IDs.
ios/gamma/stack/header/RNSStackHeaderMenuEventsDelegate.h Defines delegate protocol for native menu element press events.
ios/gamma/stack/header/RNSStackHeaderMenuData.h Adds menuElementId requirement to the menu element protocol and updates initializers.
ios/gamma/stack/header/RNSStackHeaderMenuData.mm Stores/synthesizes menuElementId on menu/menuItem data objects.
ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.h Extends coordinator API to accept a menu events delegate.
ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.mm Builds UIAction handlers that notify delegate with pressed element ID.
ios/gamma/stack/header/RNSStackHeaderContentFactory.h Threads menu events delegate into bar button item creation.
ios/gamma/stack/header/RNSStackHeaderContentFactory.mm Applies menus using the coordinator + delegate for event forwarding.
ios/gamma/stack/header/RNSStackHeaderConfigEventEmitter.h Adds an ObjC wrapper to emit Fabric onPressMenuItem events.
ios/gamma/stack/header/RNSStackHeaderConfigEventEmitter.mm Implements onPressMenuItem emission to the Fabric event emitter.
ios/gamma/stack/header/RNSStackHeaderConfigComponentView.mm Implements the menu events delegate and forwards presses to JS via the event emitter.
apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/scenario.md Extends manual scenario to verify menu item presses (Toast expectations).
apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx Updates test app to attach menuElementIds and show Toasts on menu item presses.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/gamma/stack/header/utils.ts Outdated
Comment thread ios/gamma/stack/header/RNSStackHeaderConfigEventEmitter.mm
Comment thread ios/gamma/stack/header/RNSStackHeaderMenuMapper.mm
Comment thread ios/gamma/stack/header/RNSStackHeaderMenuMapper.mm
@@ -1,9 +1,12 @@
export interface StackHeaderMenuItem {
menuElementId: string;

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.

  1. do we need menuElement prefix if we're already in menu element?
  2. should we unify naming with regular items (key vs id)?

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.

same applies to the content of PressMenuItemEvent

menuElementId: string;
type: 'menuItem';
title?: string | undefined;
onPress?: () => void;

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.

Suggested change
onPress?: () => void;
onPress?: () => void | undefined;

largeSubtitle?: string | undefined;
largeTitleEnabled?: CT.WithDefault<boolean, false>;

onPressMenuItem?: CT.DirectEventHandler<PressMenuItemEvent> | undefined;

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.

Suggested change
onPressMenuItem?: CT.DirectEventHandler<PressMenuItemEvent> | undefined;
onMenuItemPress?: CT.DirectEventHandler<MenuItemPressEvent> | undefined;

nit, up to you


export interface StackHeaderInlineItemIOS extends StackHeaderBaseItemIOS {
type: 'item';
export interface SupportsMenuIOS {

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.

tbh, do we even need this base type & do we need it exported? I'm not convinced about the name here.

...(leadingItems ?? []).filter(it => it && it.type === 'item'),
...(trailingItems ?? []).filter(it => it && it.type === 'item'),
);
const menu = findMenuElementByIdInItems(

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.

Suggested change
const menu = findMenuElementByIdInItems(
const menuElement = findMenuElementByIdInItems(

nit

continue;
}

const menu = findMenuElementById(item.menu, menuElementId);

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.

Suggested change
const menu = findMenuElementById(item.menu, menuElementId);
const menuElement = findMenuElementById(item.menu, menuElementId);

- (BOOL)emitOnPressMenuItem:(NSString *)menuElementId
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onPressMenuItem({.menuElementId = menuElementId.cString});

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.

e.g. in tabs we use RCTStringFromNSString, maybe we should do so here as well

{.selectedScreenKey = RCTStringFromNSString(payload.selectedScreenKey),

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.

4 participants