managarten/apps-archived/memoro/apps/mobile/docs/features/native-menu-solution.md
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

28 KiB

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)

// components/organisms/Memory.tsx
import { Platform } from 'react-native';
import { MenuView } from '@react-native-menu/menu';

export const Memory = ({ memory, onDelete, onShare }) => {
  return (
    <MenuView
      title="Memory Actions"
      actions={[
        {
          id: 'share',
          title: 'Teilen',
          titleColor: '#007AFF',
          image: Platform.select({
            ios: 'square.and.arrow.up',
            android: 'ic_menu_share',
          }),
        },
        {
          id: 'delete',
          title: 'Löschen',
          attributes: {
            destructive: true,
          },
          image: Platform.select({
            ios: 'trash',
            android: 'ic_menu_delete',
          }),
        },
      ]}
      onPressAction={({ nativeEvent }) => {
        switch (nativeEvent.event) {
          case 'share':
            onShare();
            break;
          case 'delete':
            onDelete();
            break;
        }
      }}
    >
      <MemoryContent memory={memory} />
    </MenuView>
  );
};

2. iOS/Android Dropdown Menu

// 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 (
    <MenuView
      title="Einstellungen"
      onPressAction={({ nativeEvent }) => {
        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)
    >
      <Pressable>
        <Icon name="ellipsis-horizontal" />
      </Pressable>
    </MenuView>
  );
};

3. Web - Radix UI Dropdown

// components/menus/HeaderMenu.web.tsx
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import './HeaderMenu.css'; // Radix UI styling

export const HeaderMenu = () => {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button className="IconButton">
          <Icon name="ellipsis-horizontal" />
        </button>
      </DropdownMenu.Trigger>

      <DropdownMenu.Portal>
        <DropdownMenu.Content className="DropdownMenuContent">
          <DropdownMenu.Item
            className="DropdownMenuItem"
            onSelect={() => navigation.navigate('Profile')}
          >
            Profil
          </DropdownMenu.Item>
          <DropdownMenu.Item
            className="DropdownMenuItem"
            onSelect={() => navigation.navigate('Settings')}
          >
            Einstellungen
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  );
};

4. Unified Hook (Cross-Platform Abstraction)

// 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

// 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 <MemoPreviewWeb memo={memo} />;
  }

  // Native: Verwende @react-native-menu/menu
  return (
    <MenuView
      actions={[
        { id: 'edit', title: 'Bearbeiten', image: 'pencil' },
        { id: 'delete', title: 'Löschen', attributes: { destructive: true } },
      ]}
      onPressAction={({ nativeEvent }) => {
        // Handle action
      }}
    >
      <MemoContent memo={memo} />
    </MenuView>
  );
};

Migrations-Plan

Phase 1: Setup & Dependencies (2 Stunden)

1.1 Dependencies bereinigen

# 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):

{
  "plugins": [
    "@react-native-menu/menu"
  ]
}

1.3 Rebuild

# 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:

// VORHER (Zeego)
import * as DropdownMenu from 'zeego/dropdown-menu';

<DropdownMenu.Root>
  <DropdownMenu.Trigger>
    <Button />
  </DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item key="item-1" onSelect={handleAction}>
      <DropdownMenu.ItemTitle>Action</DropdownMenu.ItemTitle>
      <DropdownMenu.ItemIcon ios={{ name: 'star' }} />
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu.Root>

// NACHHER (Native Menu)
import { MenuView } from '@react-native-menu/menu';

<MenuView
  actions={[
    {
      id: 'item-1',
      title: 'Action',
      image: Platform.select({
        ios: 'star',
        android: 'ic_menu_star',
      }),
    },
  ]}
  onPressAction={({ nativeEvent }) => {
    if (nativeEvent.event === 'item-1') handleAction();
  }}
  shouldOpenOnLongPress={false}
>
  <Button />
</MenuView>

Pro Komponente: ~20-30 Minuten


Phase 3: Context Menus Migration (2-3 Stunden)

Komponenten (4 Stück):

  1. components/organisms/Memory.tsx (Kritisch - Kern-Feature)
  2. components/molecules/PromptPreview.tsx
  3. components/molecules/MemoPreview.tsx
  4. components/organisms/PhotoGallery.tsx

Migration Pattern:

// VORHER (Zeego)
import * as ContextMenu from 'zeego/context-menu';

<ContextMenu.Root>
  <ContextMenu.Trigger>
    <MemoryCard />
  </ContextMenu.Trigger>
  <ContextMenu.Content>
    <ContextMenu.Item key="share" onSelect={handleShare}>
      <ContextMenu.ItemTitle>Teilen</ContextMenu.ItemTitle>
    </ContextMenu.Item>
  </ContextMenu.Content>
</ContextMenu.Root>

// NACHHER (Native Menu - Long Press by default)
import { MenuView } from '@react-native-menu/menu';

<MenuView
  actions={[
    {
      id: 'share',
      title: 'Teilen',
      image: 'square.and.arrow.up', // SF Symbol
    },
  ]}
  onPressAction={({ nativeEvent }) => {
    if (nativeEvent.event === 'share') handleShare();
  }}
  // shouldOpenOnLongPress ist true by default für Context Menus
>
  <MemoryCard />
</MenuView>

Pro Komponente: ~30-40 Minuten


Phase 4: Web Support (Optional - 2-3 Stunden)

Wenn Web-Support wichtig ist, erstelle .web.tsx Varianten:

4.1 Platform Extensions Setup

// components/menus/HeaderMenu.tsx (Native)
export { HeaderMenu } from './HeaderMenu.native';

// components/menus/HeaderMenu.native.tsx
import { MenuView } from '@react-native-menu/menu';
// Native implementation

// components/menus/HeaderMenu.web.tsx
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
// Radix UI implementation

4.2 Shared Styling für Web

/* styles/menus.css */
.DropdownMenuContent {
  min-width: 220px;
  background-color: white;
  border-radius: 6px;
  padding: 5px;
  box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35);
}

.DropdownMenuItem {
  padding: 8px;
  border-radius: 3px;
  cursor: pointer;
}

.DropdownMenuItem:hover {
  background-color: #f5f5f5;
}

Nur implementieren wenn Web-Support benötigt wird!


Phase 5: Testing & Optimierung (2-3 Stunden)

5.1 Test-Checkliste

iOS Testing:

  • Context Menus (Long Press)
  • Dropdown Menus (Tap)
  • SF Symbols Icons
  • Destructive Actions (rote Farbe)
  • Nested Submenus
  • Events (onOpenMenu, onCloseMenu)

Android Testing:

  • Context Menus (Long Press)
  • Dropdown Menus (Tap)
  • Material Icons
  • Destructive Actions
  • Performance

Web Testing (falls implementiert):

  • Radix UI Dropdowns
  • Radix UI Context Menus
  • Keyboard Navigation
  • Accessibility (ARIA)

5.2 Edge Cases

  • Mehrere Menus gleichzeitig geöffnet
  • Schnelles Tippen/Schließen
  • Screen Rotation während Menu offen
  • Dark Mode Support

Vorteile dieser Lösung

Technische Vorteile

  1. SDK 54 Kompatibel: Library wurde auf RN 0.81 getestet
  2. Native Performance: Direkte UIMenu/PopupMenu APIs
  3. Bereits installiert: Keine neuen Dependencies
  4. Aktiv maintained: Updates vor 14 Tagen
  5. Expo-Integration: Offizieller Config Plugin

UX Vorteile

  1. Native Look & Feel: 100% native iOS/Android Menus
  2. System-Integration: SF Symbols, Material Icons
  3. Accessibility: Native Accessibility Support
  4. Gestures: Native Long Press, Tap Gestures

Developer Experience

  1. Simple API: Weniger Boilerplate als Zeego
  2. TypeScript Support: Vollständig typisiert
  3. Debugging: Native Debugging mit Xcode/Android Studio
  4. Testing: Jest Mocks vorhanden

Nachteile & Lösungen

Kein Unified API wie Zeego

Problem: Verschiedene APIs für iOS/Android vs Web

Lösung 1 (Empfohlen): Platform-specific files

HeaderMenu.tsx         → Export default
HeaderMenu.native.tsx  → Native implementation
HeaderMenu.web.tsx     → Radix UI implementation

Lösung 2: Custom Hook (siehe useNativeMenu oben)

Lösung 3: Wrapper-Komponente

// components/ui/NativeMenu.tsx
export const NativeMenu = Platform.OS === 'web'
  ? RadixDropdownMenu
  : ReactNativeMenu;

Web Support erfordert extra Arbeit

Problem: Web nicht out-of-the-box supported

Lösung:

  • Wenn Web wichtig: Phase 4 implementieren (~2-3h)
  • Wenn Web nicht wichtig: Weglassen

Realität: Viele Expo-Apps fokussieren sich auf Mobile

Mehr Code als mit Zeego

Problem: ~20-30% mehr Code durch Platform Switches

Lösung:

  • Abstraction Layers (Hooks, Wrapper)
  • Code-Generierung für repetitive Patterns
  • Akzeptieren des Trade-offs für native UX

Checkbox-Support eingeschränkt

Problem: Native Menus haben limitierte Checkbox-Unterstützung

Alternative:

  • Separate Settings-Screens
  • Toggle-Buttons statt Menu Checkboxes
  • Custom Modals für komplexe Selections

Migration Timeline

Aggressive Timeline (3 Tage)

Tag 1 (4h):

  • Dependencies setup
  • Rebuild & Testing
  • 5-6 Dropdown Menus migrieren

Tag 2 (4h):

  • Restliche Dropdown Menus
  • Alle Context Menus
  • Basis-Testing

Tag 3 (4h):

  • Comprehensive Testing
  • Bug Fixes
  • Performance-Optimierung

Komfortable Timeline (5 Tage)

Tag 1-2: Dependencies + Dropdown Menus Tag 3: Context Menus Tag 4: Web Support (optional) Tag 5: Testing + Optimierung

Enterprise Timeline (2 Wochen)

Woche 1: Migration + Basic Testing Woche 2: Web Support + Comprehensive Testing + QA


Code-Organisation Best Practices

1. Zentralisierte Menu Actions

// config/menuActions.ts
export const MEMO_ACTIONS = {
  EDIT: {
    id: 'edit',
    title: 'Bearbeiten',
    icon: {
      ios: 'pencil',
      android: 'ic_menu_edit',
    },
  },
  DELETE: {
    id: 'delete',
    title: 'Löschen',
    destructive: true,
    icon: {
      ios: 'trash',
      android: 'ic_menu_delete',
    },
  },
  SHARE: {
    id: 'share',
    title: 'Teilen',
    icon: {
      ios: 'square.and.arrow.up',
      android: 'ic_menu_share',
    },
  },
};

2. Menu Action Builder

// utils/menuBuilder.ts
import { Platform } from 'react-native';

export const buildMenuAction = (action: MenuActionConfig) => ({
  id: action.id,
  title: action.title,
  attributes: action.destructive ? { destructive: true } : undefined,
  image: action.icon ? Platform.select({
    ios: action.icon.ios,
    android: action.icon.android,
  }) : undefined,
  titleColor: action.color,
});

3. Reusable Menu Component

// components/ui/NativeMenu.tsx
interface NativeMenuProps {
  actions: MenuActionConfig[];
  onAction: (actionId: string) => void;
  children: React.ReactNode;
  isContextMenu?: boolean;
}

export const NativeMenu: FC<NativeMenuProps> = ({
  actions,
  onAction,
  children,
  isContextMenu = false,
}) => {
  const menuActions = actions.map(buildMenuAction);

  return (
    <MenuView
      actions={menuActions}
      onPressAction={({ nativeEvent }) => onAction(nativeEvent.event)}
      shouldOpenOnLongPress={isContextMenu}
    >
      {children}
    </MenuView>
  );
};

4. Verwendung

// components/organisms/Memory.tsx
import { NativeMenu } from '~/components/ui/NativeMenu';
import { MEMO_ACTIONS } from '~/config/menuActions';

export const Memory = ({ memo }) => {
  const handleAction = (actionId: string) => {
    switch (actionId) {
      case 'edit':
        editMemo(memo);
        break;
      case 'delete':
        deleteMemo(memo);
        break;
      case 'share':
        shareMemo(memo);
        break;
    }
  };

  return (
    <NativeMenu
      actions={[MEMO_ACTIONS.EDIT, MEMO_ACTIONS.DELETE, MEMO_ACTIONS.SHARE]}
      onAction={handleAction}
      isContextMenu
    >
      <MemoCard memo={memo} />
    </NativeMenu>
  );
};

Technische Details

@react-native-menu/menu Features

iOS (UIMenu API)

<MenuView
  title="Title"
  actions={[
    {
      id: 'action-id',
      title: 'Action Title',
      titleColor: '#007AFF',
      subtitle: 'Optional subtitle',
      image: 'sf.symbol.name', // SF Symbol
      imageColor: '#FF3B30',
      attributes: {
        destructive: boolean,
        disabled: boolean,
        hidden: boolean,
      },
      state: 'on' | 'off' | 'mixed', // Checkbox state
    },
  ]}
  onPressAction={({ nativeEvent }) => {
    // nativeEvent.event = action id
  }}
  onOpenMenu={() => console.log('Opened')}
  onCloseMenu={() => console.log('Closed')}
  shouldOpenOnLongPress={true} // Context menu
  isAnchoredToRight={false}
  themeVariant="dark" | "light"
>
  <YourComponent />
</MenuView>

Android (PopupMenu API)

<MenuView
  title="Title"
  actions={[
    {
      id: 'action-id',
      title: 'Action Title',
      image: 'ic_menu_icon', // Material Icon
      // Android unterstützt weniger Optionen
    },
  ]}
  onPressAction={({ nativeEvent }) => {
    // Handle action
  }}
>
  <YourComponent />
</MenuView>

Nested Menus (iOS & Android)

<MenuView
  actions={[
    {
      id: 'parent',
      title: 'Parent Menu',
      subactions: [
        { id: 'child-1', title: 'Child 1' },
        { id: 'child-2', title: 'Child 2' },
      ],
    },
  ]}
  onPressAction={({ nativeEvent }) => {
    // Handles parent and child actions
  }}
>
  <YourComponent />
</MenuView>

SF Symbols Guide (iOS)

// Common SF Symbols
const SF_SYMBOLS = {
  // Actions
  edit: 'pencil',
  delete: 'trash',
  share: 'square.and.arrow.up',
  copy: 'doc.on.doc',
  download: 'arrow.down.circle',
  upload: 'arrow.up.circle',

  // Objects
  photo: 'photo',
  video: 'video',
  document: 'doc',
  folder: 'folder',

  // UI
  settings: 'gearshape',
  profile: 'person.circle',
  search: 'magnifyingglass',
  filter: 'line.3.horizontal.decrease.circle',

  // Status
  check: 'checkmark',
  close: 'xmark',
  warning: 'exclamationmark.triangle',
  info: 'info.circle',
};

Material Icons Guide (Android)

// Common Material Icons
const MATERIAL_ICONS = {
  // Actions
  edit: 'ic_menu_edit',
  delete: 'ic_menu_delete',
  share: 'ic_menu_share',
  copy: 'ic_menu_copy_holo_dark',

  // Objects
  camera: 'ic_menu_camera',
  gallery: 'ic_menu_gallery',

  // UI
  preferences: 'ic_menu_preferences',
  myplaces: 'ic_menu_myplaces', // Profile
  search: 'ic_menu_search',

  // Status
  add: 'ic_menu_add',
  close: 'ic_menu_close_clear_cancel',
};

Alternative Lösungen (Falls @react-native-menu/menu nicht funktioniert)

Option B: Zeego Fix abwarten + Workaround

Wenn du Zeego wirklich bevorzugst:

  1. GitHub Issue monitoren: Issue #173 beobachten
  2. Temporärer Workaround: Downgrade auf Expo SDK 52
  3. Zeitaufwand: Unbekannt (kann Wochen dauern)

Nicht empfohlen: Blockiert SDK 54 Features

Option C: react-native-ios-context-menu direkt (iOS only)

Nur für iOS-exklusive Apps:

import { ContextMenuView } from 'react-native-ios-context-menu';

<ContextMenuView
  menuConfig={{
    menuTitle: 'Title',
    menuItems: [
      {
        actionKey: 'action1',
        actionTitle: 'Action 1',
        icon: { iconType: 'SYSTEM', iconValue: 'trash' },
      },
    ],
  }}
  onPressMenuItem={({ nativeEvent }) => {
    // Handle
  }}
>
  <YourComponent />
</ContextMenuView>

Nachteile:

  • Nur iOS
  • In Maintenance Mode (Autor macht kein OSS mehr)
  • Keine Android/Web Unterstützung

Option D: Custom Implementation

Nur als letzter Ausweg:

  • 20-30 Stunden Aufwand
  • Kein natives Look-and-Feel
  • Hoher Wartungsaufwand

Testing-Strategie

Unit Tests

// __tests__/NativeMenu.test.tsx
import { render, fireEvent } from '@testing-library/react-native';
import { NativeMenu } from '../components/ui/NativeMenu';

jest.mock('@react-native-menu/menu', () => ({
  MenuView: 'MenuView',
}));

describe('NativeMenu', () => {
  it('renders children', () => {
    const { getByText } = render(
      <NativeMenu actions={[]} onAction={jest.fn()}>
        <Text>Test</Text>
      </NativeMenu>
    );
    expect(getByText('Test')).toBeTruthy();
  });

  it('calls onAction with correct id', () => {
    const onAction = jest.fn();
    const { getByTestId } = render(
      <NativeMenu
        actions={[{ id: 'test', title: 'Test' }]}
        onAction={onAction}
      >
        <View testID="trigger" />
      </NativeMenu>
    );

    // Simulate menu press
    fireEvent(getByTestId('trigger'), 'onPressAction', {
      nativeEvent: { event: 'test' },
    });

    expect(onAction).toHaveBeenCalledWith('test');
  });
});

E2E Tests (Detox)

// e2e/menus.test.ts
describe('Native Menus', () => {
  it('should open context menu on long press', async () => {
    await element(by.id('memory-card')).longPress();
    await expect(element(by.text('Löschen'))).toBeVisible();
  });

  it('should execute action on tap', async () => {
    await element(by.id('memory-card')).longPress();
    await element(by.text('Löschen')).tap();
    await expect(element(by.id('memory-card'))).not.toBeVisible();
  });
});

Rollback-Plan

Falls Probleme auftreten:

1. Git Branch Strategy

# Vor Migration
git checkout -b migration/native-menus
git push -u origin migration/native-menus

# Bei Problemen
git checkout main
git branch -D migration/native-menus

2. Package Lock

# package.json vor Migration sichern
cp package.json package.json.backup
cp package-lock.json package-lock.json.backup

# Rollback
mv package.json.backup package.json
mv package-lock.json.backup package-lock.json
npm install

3. Zeego Restore

# Falls Rollback nötig
npm install zeego@3.0.6
npx expo prebuild --clean

Performance-Optimierung

1. Action Memoization

const memoActions = useMemo(() =>
  actions.map(buildMenuAction),
  [actions]
);

<MenuView actions={memoActions} />

2. Handler Optimization

const handleAction = useCallback((actionId: string) => {
  switch (actionId) {
    case 'delete':
      deleteHandler();
      break;
    // ...
  }
}, [deleteHandler, /* deps */]);

3. Lazy Loading (Web)

// Radix UI nur auf Web laden
const DropdownMenu = Platform.OS === 'web'
  ? lazy(() => import('@radix-ui/react-dropdown-menu'))
  : null;

FAQ

Q: Funktioniert @react-native-menu/menu mit Expo Go?

A: Nein, erfordert Custom Development Build (wie Zeego auch).

Q: Brauche ich Web Support?

A: Nur wenn deine App im Browser laufen soll. Viele Expo-Apps sind Mobile-only.

Q: Wie ist die Performance vs Zeego?

A: Identisch - beide nutzen die gleichen nativen APIs.

Q: Was ist mit Dark Mode?

A: Native Menus respektieren automatisch das System-Theme.

Q: Kann ich Custom Styling verwenden?

A: Nein - native Menus nutzen System-Styling. Für Custom UI: eigene Modals bauen.

Q: Unterstützt es Animationen?

A: Ja - native System-Animationen (nicht konfigurierbar).


Zusammenfassung & Empfehlung

Empfohlene Lösung: @react-native-menu/menu

Warum:

  • Bereits installiert (v2.0.0)
  • SDK 54 kompatibel (RN 0.81 getestet)
  • Native iOS & Android Menus
  • Aktiv maintained
  • Niedriges Risiko

Zeitaufwand: 8-12 Stunden Risiko: Niedrig UX: Native (best-in-class)

Migrations-Reihenfolge

  1. Sofort: Dependencies setup (30 min)
  2. Phase 1: Dropdown Menus (4-5h)
  3. Phase 2: Context Menus (2-3h)
  4. Phase 3: Testing (2-3h)
  5. Optional: Web Support (2-3h)

Nächste Schritte

  1. Team-Entscheidung einholen
  2. Git Branch erstellen
  3. Phase 1 starten (Dependencies)
  4. Iterativ migrieren
  5. Testen auf allen Plattformen

Dokument erstellt: 30. September 2025 Autor: Claude Code Analyse Status: Production Ready Review: Empfohlen für Team-Review


Ressourcen