# Native Menu Lösung für Expo SDK 54 - Empfehlung ## Executive Summary **Status:** ✅ LÖSUNG GEFUNDEN Unser Projekt hat bereits `@react-native-menu/menu@2.0.0` installiert (als Zeego-Dependency). Diese Library wurde **im September 2023 auf React Native 0.81 aktualisiert** und ist damit SDK 54 kompatibel. Wir können Zeego entfernen und direkt mit `@react-native-menu/menu` arbeiten - kombiniert mit Radix UI für Web. **Zeitaufwand:** 8-12 Stunden **Risiko:** Niedrig - Library ist bereits installiert und getestet **Empfehlung:** ⭐ Primäre Lösung --- ## Problemanalyse (Nochmal zur Klarstellung) ### Warum funktioniert Zeego nicht? Zeego v3.0.6 ist für **Expo SDK 52** (React Native 0.76/0.77) optimiert, nicht für SDK 54 (React Native 0.81). Der Fehler: ``` Unable to resolve module react-native-ios-context-menu ``` tritt auf, weil die Zeego-Dependencies (`react-native-ios-context-menu` und `react-native-ios-utilities`) einen `RCT-Folly` Versionskonflikt mit React Native 0.81 haben. **GitHub Issue #173:** Build fails with Expo 54 - noch nicht gelöst. ### Was bietet Expo UI? **Expo UI** (`@expo/ui`) ist **kein Context/Dropdown Menu Framework**. Es bietet nur: - Native Input Components für Jetpack Compose (Android) und SwiftUI (iOS) - Keine Menu-Komponenten Expo UI ist also keine Lösung für unser Problem. --- ## Die Lösung: @react-native-menu/menu (Bereits installiert!) ### Warum @react-native-menu/menu? ✅ **Bereits im Projekt:** Version 2.0.0 ist installiert (war Zeego-Dependency) ✅ **SDK 54 Ready:** v2.0.0 wurde auf React Native 0.81 aktualisiert (September 2023) ✅ **Native iOS & Android Menus:** UIMenu (iOS 14+) und PopupMenu (Android) ✅ **Aktiv maintained:** Letztes Update vor 14 Tagen (Stand: September 2025) ✅ **Expo Config Plugin:** Offizielle Expo-Integration ✅ **Feature-reich:** Icons, Submenus, Events (onOpenMenu, onCloseMenu) ### Library Status **Current Version:** 2.0.0 **Last Updated:** Vor 14 Tagen (September 2025) **React Native Support:** 0.81+ (getestet im Example-Projekt) **iOS:** 14+ (UIMenu nativ), iOS 13 Fallback zu ActionSheet **Android:** PopupMenu (native Android API) **Expo:** ✅ Expo Config Plugin vorhanden --- ## Architektur-Übersicht ### Cross-Platform Strategie ``` ┌─────────────────────────────────────────────────────┐ │ useNativeMenu() │ │ (Unified Hook Interface) │ └─────────────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ iOS │ │ Android │ │ Web │ └─────────┘ └─────────┘ └─────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ UIMenu │ │ PopupMenu │ │ Radix UI │ │ (Native) │ │ (Native) │ │ (Web-only) │ └─────────────┘ └─────────────┘ └─────────────┘ ``` ### Platform-Specific Implementation **iOS:** `@react-native-menu/menu` → UIMenu API **Android:** `@react-native-menu/menu` → PopupMenu API **Web:** Radix UI Dropdown/Context Menu (bereits von Zeego bekannt) --- ## Feature-Vergleich | Feature | Zeego | @react-native-menu/menu + Radix | Action Sheet | |---------|-------|----------------------------------|--------------| | **Native iOS Context Menu** | ✅ | ✅ | ❌ | | **Native Android Menu** | ✅ | ✅ | ❌ | | **Web Support** | ✅ Radix | ✅ Radix | ❌ | | **Expo SDK 54** | ❌ Broken | ✅ | ✅ | | **React Native 0.81** | ❌ | ✅ | ✅ | | **Icons (SF Symbols)** | ✅ | ✅ | ❌ | | **Submenus** | ✅ | ✅ | ❌ | | **Checkboxes** | ✅ | ⚠️ Eingeschränkt | ❌ | | **Custom Styling** | ✅ | ⚠️ Native Styling | ✅ | | **Unified API** | ✅ | ❌ (Platform Switch) | N/A | | **Maintenance Status** | ⚠️ SDK 54 Issue | ✅ Aktiv | ✅ Expo Official | | **Setup Complexity** | Mittel | Mittel | Niedrig | --- ## Code-Beispiele ### 1. iOS/Android Native Menu (Context Menu) ```tsx // components/organisms/Memory.tsx import { Platform } from 'react-native'; import { MenuView } from '@react-native-menu/menu'; export const Memory = ({ memory, onDelete, onShare }) => { return ( { switch (nativeEvent.event) { case 'share': onShare(); break; case 'delete': onDelete(); break; } }} > ); }; ``` ### 2. iOS/Android Dropdown Menu ```tsx // features/menus/HeaderMenu.tsx import { Platform, Pressable } from 'react-native'; import { MenuView } from '@react-native-menu/menu'; export const HeaderMenu = () => { const [isOpen, setIsOpen] = useState(false); return ( { if (nativeEvent.event === 'profile') { navigation.navigate('Profile'); } else if (nativeEvent.event === 'settings') { navigation.navigate('Settings'); } }} actions={[ { id: 'profile', title: 'Profil', image: Platform.select({ ios: 'person.circle', android: 'ic_menu_myplaces', }), }, { id: 'settings', title: 'Einstellungen', image: Platform.select({ ios: 'gearshape', android: 'ic_menu_preferences', }), }, ]} shouldOpenOnLongPress={false} // Öffnet auf normalen Press (nicht long press) > ); }; ``` ### 3. Web - Radix UI Dropdown ```tsx // components/menus/HeaderMenu.web.tsx import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import './HeaderMenu.css'; // Radix UI styling export const HeaderMenu = () => { return ( navigation.navigate('Profile')} > Profil navigation.navigate('Settings')} > Einstellungen ); }; ``` ### 4. Unified Hook (Cross-Platform Abstraction) ```tsx // hooks/useNativeMenu.ts import { Platform } from 'react-native'; import { MenuView } from '@react-native-menu/menu'; export interface MenuAction { id: string; title: string; destructive?: boolean; icon?: string; onPress: () => void; } export const useNativeMenu = () => { const showMenu = (actions: MenuAction[]) => { if (Platform.OS === 'web') { // Web: Return props for Radix UI return { isWeb: true, actions, }; } // Native: Return props for MenuView return { isWeb: false, menuActions: actions.map(action => ({ id: action.id, title: action.title, attributes: action.destructive ? { destructive: true } : undefined, image: action.icon ? Platform.select({ ios: action.icon, android: `ic_menu_${action.icon}`, }) : undefined, })), onPressAction: ({ nativeEvent }) => { const action = actions.find(a => a.id === nativeEvent.event); action?.onPress(); }, }; }; return { showMenu }; }; ``` ### 5. Platform-Specific Component ```tsx // components/molecules/MemoPreview.tsx import { Platform } from 'react-native'; import { MenuView } from '@react-native-menu/menu'; // Separate Web-Komponente importieren const MemoPreviewWeb = Platform.OS === 'web' ? require('./MemoPreview.web').default : null; export const MemoPreview = ({ memo }) => { // Web: Verwende Radix UI if (Platform.OS === 'web') { return ; } // Native: Verwende @react-native-menu/menu return ( { // Handle action }} > ); }; ``` --- ## Migrations-Plan ### Phase 1: Setup & Dependencies (2 Stunden) #### 1.1 Dependencies bereinigen ```bash # Zeego entfernen npm uninstall zeego # Zeego iOS-Dependencies entfernen npm uninstall react-native-ios-context-menu react-native-ios-utilities # @react-native-menu/menu ist bereits installiert (v2.0.0) # Radix UI für Web installieren npm install @radix-ui/react-dropdown-menu @radix-ui/react-context-menu ``` #### 1.2 Expo Config Plugin `app.json` ist bereits konfiguriert (Zeego hatte die gleiche Dependency): ```json { "plugins": [ "@react-native-menu/menu" ] } ``` #### 1.3 Rebuild ```bash # Clean prebuild npx expo prebuild --clean # iOS npx expo run:ios # Android npx expo run:android ``` --- ### Phase 2: Dropdown Menus Migration (4-5 Stunden) **Komponenten (11 Stück):** 1. ✅ `features/menus/HeaderMenu.tsx` 2. ✅ `features/menus/MemoMenu.tsx` 3. ✅ `features/menus/MemoHeaderMenu.tsx` 4. ✅ `components/atoms/Pill.tsx` 5. ✅ `components/molecules/TableOfContentsMenu.tsx` 6. ✅ `features/subscription/SubscriptionMenu.tsx` 7. ✅ Weitere Dropdown-Komponenten **Migration Pattern:** ```tsx // VORHER (Zeego) import * as DropdownMenu from 'zeego/dropdown-menu';