From ff2fc4c9244e248d263323b60a547fd4b255bb3c Mon Sep 17 00:00:00 2001 From: Konrad Michalik Date: Tue, 9 Jun 2026 11:59:06 +0200 Subject: [PATCH 1/2] feat: Initial code for menu in header items --- .../single-feature-tests/stack-v5/index.ts | 2 + .../test-stack-header-menu-ios/index.tsx | 98 +++++++++++++++++++ .../scenario-description.ts | 10 ++ .../test-stack-header-menu-ios/scenario.md | 28 ++++++ .../header/RNSStackHeaderContentFactory.mm | 13 +++ .../header/RNSStackHeaderItemComponentView.h | 1 + .../header/RNSStackHeaderItemComponentView.mm | 10 ++ .../header/RNSStackHeaderItemDataProviding.h | 2 + .../header/RNSStackHeaderMenuCoordinator.h | 14 +++ .../header/RNSStackHeaderMenuCoordinator.mm | 42 ++++++++ .../stack/header/RNSStackHeaderMenuData.h | 27 +++++ .../stack/header/RNSStackHeaderMenuData.mm | 26 +++++ .../stack/header/RNSStackHeaderMenuMapper.h | 15 +++ .../stack/header/RNSStackHeaderMenuMapper.mm | 79 +++++++++++++++ .../header/StackHeaderConfig.ios.types.ts | 3 + .../header/ios/StackHeaderItem.ios.types.ts | 2 + .../header/ios/StackHeaderMenu.ios.types.ts | 12 +++ .../StackHeaderItemIOSNativeComponent.ts | 1 + 18 files changed, 385 insertions(+) create mode 100644 apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx create mode 100644 apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/scenario-description.ts create mode 100644 apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/scenario.md create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.h create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuCoordinator.mm create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuData.h create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuData.mm create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuMapper.h create mode 100644 ios/gamma/stack/header/RNSStackHeaderMenuMapper.mm create mode 100644 src/components/gamma/stack/header/ios/StackHeaderMenu.ios.types.ts diff --git a/apps/src/tests/single-feature-tests/stack-v5/index.ts b/apps/src/tests/single-feature-tests/stack-v5/index.ts index 6321f45e2b..c0ae244de4 100644 --- a/apps/src/tests/single-feature-tests/stack-v5/index.ts +++ b/apps/src/tests/single-feature-tests/stack-v5/index.ts @@ -5,6 +5,7 @@ import AnimationAndroid from './test-animation-android'; import TestStackSimpleNav from './test-stack-simple-nav'; import TestStackSubviewsAndroid from './test-stack-subviews-android'; import TestStackSubviewsIOS from './test-stack-subviews-ios'; +import TestStackHeaderMenuIOS from './test-stack-header-menu-ios'; import TestStackBackButton from './test-stack-back-button-android'; import TestStackToolbarMenuCommands from './test-stack-toolbar-menu-commands-android'; import TestStackToolbarMenuShowAsAction from './test-stack-toolbar-menu-show-as-action-android'; @@ -16,6 +17,7 @@ const scenarios = { TestStackSimpleNav, TestStackSubviewsAndroid, TestStackSubviewsIOS, + TestStackHeaderMenuIOS, TestStackBackButton, TestStackToolbarMenuCommands, TestStackToolbarMenuShowAsAction, diff --git a/apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx b/apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx new file mode 100644 index 0000000000..e0f53f1abb --- /dev/null +++ b/apps/src/tests/single-feature-tests/stack-v5/test-stack-header-menu-ios/index.tsx @@ -0,0 +1,98 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { createScenario } from '@apps/tests/shared/helpers'; +import { + StackContainer, + useStackNavigationContext, +} from '@apps/shared/gamma/containers/stack'; +import { StackHeaderConfigProps } from 'react-native-screens/components/gamma/stack/header'; +import { Button, ScrollView } from 'react-native'; +import LongText from '@apps/shared/LongText'; +import { scenarioDescription } from './scenario-description'; +import PressableWithFeedback from '@apps/shared/PressableWithFeedback'; + +const DEFAULT_TRAILING_ITEMS_COUNT = 2; + +export function App() { + return ( + + ); +} + +function buildHeaderConfig(trailingItemsCount: number): StackHeaderConfigProps { + const trailingItems: NonNullable< + StackHeaderConfigProps['ios'] + >['trailingItems'] = Array.from({ length: trailingItemsCount }).map( + (_, i) => ({ + type: 'item', + key: `trailing-${i}`, + label: `Menu ${i}`, + // every second item is custom + ...(i % 2 === 0 && { + render: () => ( + + ), + }), + menu: { + type: 'menu', + children: [ + { type: 'menuItem', title: `Item ${i}.1` }, + { type: 'menuItem', title: `Item ${i}.2` }, + { + type: 'menu', + title: `Submenu ${i}`, + children: [ + { type: 'menuItem', title: `Nested ${i}.1` }, + { type: 'menuItem', title: `Nested ${i}.2` }, + ], + }, + ], + }, + }), + ); + + return { + title: 'Header Menu', + ios: { + trailingItems, + }, + }; +} + +function ConfigScreen() { + const navigation = useStackNavigationContext(); + const [trailingItemsCount, setTrailingItemsCount] = useState( + DEFAULT_TRAILING_ITEMS_COUNT, + ); + + const { setRouteOptions, routeKey } = navigation; + const headerConfig = useMemo( + () => buildHeaderConfig(trailingItemsCount), + [trailingItemsCount], + ); + + useEffect(() => { + setRouteOptions(routeKey, { + headerConfig, + }); + }, [headerConfig, setRouteOptions, routeKey]); + + return ( + +