feat: Initial code for menu in header items#4138
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an initial (experimental) nested header-menu configuration for the gamma iOS stack header items, wiring a new menu prop from JS types through Fabric props into native iOS menu construction, and adds a Single Feature Test scenario to exercise basic nested menu rendering.
Changes:
- Added a
menuprop to gamma stack header inline items (including Fabric native props) plus new TypeScript menu shape types. - Implemented iOS-side mapping from JS dictionaries into native menu data objects and a coordinator that builds
UIMenu/UIActiontrees. - Added an iOS-only SFT scenario (
test-stack-header-menu-ios) and registered it in the stack-v5 scenario index.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/fabric/gamma/stack/StackHeaderItemIOSNativeComponent.ts | Adds menu prop to Fabric native component props (UnsafeMixed). |
| src/components/gamma/stack/header/StackHeaderConfig.ios.types.ts | Extends inline header item types to accept an optional menu. |
| src/components/gamma/stack/header/ios/StackHeaderMenu.ios.types.ts | New TS types describing nested menu structures. |
| src/components/gamma/stack/header/ios/StackHeaderItem.ios.types.ts | Extends stack header item props with optional menu. |
| ios/gamma/stack/header/RNSStackHeaderMenuMapper.h | Declares native mapper that parses JS dictionaries into menu data. |
| ios/gamma/stack/header/RNSStackHeaderMenuMapper.mm | Implements parsing/validation of menu dictionaries into native data objects. |
| ios/gamma/stack/header/RNSStackHeaderMenuData.h | Defines native menu/menuItem data models and protocol for menu elements. |
| ios/gamma/stack/header/RNSStackHeaderMenuData.mm | Implements native menu/menuItem data model initializers. |
| ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.h | Declares coordinator that applies native menus to bar button items. |
| ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.mm | Builds UIMenu/UIAction trees and applies them to UIBarButtonItem. |
| ios/gamma/stack/header/RNSStackHeaderItemDataProviding.h | Extends header item data protocol with a menu property. |
| ios/gamma/stack/header/RNSStackHeaderItemComponentView.h | Exposes menu property on the component view implementing the protocol. |
| ios/gamma/stack/header/RNSStackHeaderItemComponentView.mm | Converts Fabric menu prop to native menu data and triggers invalidation. |
| ios/gamma/stack/header/RNSStackHeaderContentFactory.mm | Applies a parsed menu to the produced UIBarButtonItem. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/scenario.md | Adds manual test steps for nested header menu behavior on iOS. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/scenario-description.ts | Registers scenario metadata for the new SFT. |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx | Adds SFT implementation exercising nested menus and dynamic item counts. |
| apps/src/tests/single-feature-tests/stack-v5/index.ts | Registers the new iOS header-menu SFT in the stack-v5 suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export interface NativeProps extends ViewProps { | ||
| placement?: CT.WithDefault<Placement, 'trailing'>; | ||
| label?: string | undefined; | ||
| menu?: CT.UnsafeMixed; |
There was a problem hiding this comment.
If you want typing here you can use util we already have in our repo for tabs appearance, currently in src/fabric/tabs/codegenUtils.ts.
|
|
||
| export interface StackHeaderMenu { | ||
| type: 'menu'; | ||
| title?: string | undefined; |
There was a problem hiding this comment.
Should we unify naming with menu items (title vs label)?
|
|
||
| + (void)applyMenu:(nonnull RNSStackHeaderMenuData *)data toBarButtonItem:(nonnull UIBarButtonItem *)item | ||
| { | ||
| #if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000 |
There was a problem hiding this comment.
maybe we should create a macro for tvOS version, similar to RNS_IPHONE_OS_VERSION_AVAILABLE(v)?
|
|
||
| #pragma mark - Helpers | ||
|
|
||
| + (void)validateMenuKeys:(NSDictionary *)dict |
There was a problem hiding this comment.
I wonder if we should also enforce that all expected keys are present (e.g. type & chidren)
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface RNSStackHeaderMenuCoordinator : NSObject |
There was a problem hiding this comment.
coordinator or applicator or factory?
| } | ||
| } | ||
|
|
||
| return [UIMenu menuWithTitle:data.title ?: @"" children:elements]; |
There was a problem hiding this comment.
is there a difference between @"" and nil here?
|
|
||
| + (nullable UIMenuElement *)buildElementFromData:(id<RNSStackHeaderMenuElement>)element | ||
| { | ||
| if ([element isKindOfClass:[RNSStackHeaderMenuData class]]) { |
There was a problem hiding this comment.
nit: you may consider having a method on the protocol to avoid selecting the implementation based on the class type here
| { | ||
| for (NSString *key in dict) { | ||
| RCTAssert([key isEqualToString:@"type"] || [key isEqualToString:@"title"] || [key isEqualToString:@"children"], | ||
| @"Invalid key \"%@\" found in menu", |
There was a problem hiding this comment.
nit: insert [RNScreens] inside log
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1178
Description
This PR adds some initial configuration for displaying nested header menus.
Changes
menuprop forInlineHeaderItemsmenucan be given type"menu" | "menuItem", atitleand achildrenfor submenusBefore & after - visual documentation
Test plan
Added SFT
test-stack-header-menu-ios. It includes only basic features and will be extended in the future.Checklist