Skip to content

shottah/expo-focus-menu

Repository files navigation

expo-focus-menu

npm version License: MIT Test

Native iOS context menus with haptic feedback, SF Symbols, and interactive emoji reactions for React Native. Provides an elegant focus menu UI component for Expo and React Native apps.

Features

  • πŸ“± Native iOS Context Menus - Uses UIContextMenuInteraction for authentic iOS experience
  • 🎯 Focus Menu UI - Long press or tap to reveal contextual actions
  • πŸ’« Haptic Feedback - Configurable haptic response on menu activation
  • 🎨 SF Symbols Support - Use any SF Symbol as menu item icons
  • πŸ˜€ Emoji Reactions - Interactive emoji picker for quick reactions
  • 🎭 Customizable Triggers - Long press or tap activation modes
  • πŸ“¦ Submenus - Nested menu items for complex hierarchies
  • πŸ”΄ Destructive Actions - Native styling for dangerous operations
  • β™Ώ Accessibility - Full VoiceOver and accessibility support

Installation

For Expo managed projects

expo install expo-focus-menu

For bare React Native projects

First ensure you have installed and configured the expo package.

npm install expo-focus-menu
# or
yarn add expo-focus-menu

iOS Setup

Run pod install after installation:

cd ios && pod install

Note: This module currently only supports iOS. Android support displays a fallback view.

Quick Start

import { ExpoFocusMenuView } from 'expo-focus-menu';

function MyComponent() {
  const menuItems = [
    { id: 'share', title: 'Share', icon: 'square.and.arrow.up' },
    { id: 'copy', title: 'Copy', icon: 'doc.on.doc' },
    { id: 'delete', title: 'Delete', icon: 'trash', destructive: true }
  ];

  return (
    <ExpoFocusMenuView
      items={menuItems}
      onItemPress={(itemId) => console.log('Selected:', itemId)}
    >
      <Text>Long press me!</Text>
    </ExpoFocusMenuView>
  );
}

Advanced Usage

With Emoji Reactions

<ExpoFocusMenuView
  items={menuItems}
  reactions={['πŸ‘', '❀️', 'πŸ˜‚', 'πŸ”₯', 'πŸ’―']}
  onReactionPress={({ emoji, selected }) => {
    console.log(`Emoji ${emoji} was ${selected ? 'selected' : 'deselected'}`);
  }}
  onItemPress={(itemId) => console.log('Menu item:', itemId)}
>
  <View style={styles.card}>
    <Text>React to this content!</Text>
  </View>
</ExpoFocusMenuView>

With Submenus

const menuItems = [
  { id: 'edit', title: 'Edit', icon: 'pencil' },
  {
    id: 'share',
    title: 'Share',
    icon: 'square.and.arrow.up',
    children: [
      { id: 'twitter', title: 'Twitter', icon: 'bird' },
      { id: 'facebook', title: 'Facebook', icon: 'f.circle' },
      { id: 'email', title: 'Email', icon: 'envelope' }
    ]
  },
  { id: 'delete', title: 'Delete', icon: 'trash', destructive: true }
];

Custom Configuration

<ExpoFocusMenuView
  items={menuItems}
  hapticFeedback={true}        // Enable haptic feedback
  onItemPress={handleItemPress}
  onMenuShow={() => console.log('Menu opened')}
  onMenuDismiss={() => console.log('Menu closed')}
>
  <YourContent />
</ExpoFocusMenuView>

API Reference

ExpoFocusMenuView Props

Prop Type Description Default
items FocusMenuItem[] Array of menu items to display Required
onItemPress (itemId: string) => void Callback when menu item is selected Required
children ReactNode Content to wrap with menu Required
hapticFeedback boolean Enable haptic feedback false
reactions string[] Emoji reactions to display (none if omitted) -
onReactionPress (data: {emoji: string, selected: boolean}) => void Reaction selection callback -
onMenuShow () => void Menu shown callback -
onMenuDismiss () => void Menu dismissed callback -

FocusMenuItem Interface

interface FocusMenuItem {
  id: string;                    // Unique identifier
  title: string;                 // Display title
  subtitle?: string;             // Optional subtitle (iOS 15+)
  icon?: string;                 // SF Symbol name
  image?: string;                // Custom image URL or base64
  destructive?: boolean;         // Style as destructive action
  disabled?: boolean;            // Disable this item
  children?: FocusMenuItem[];    // Nested submenu items
}

Compatibility

Platform Support

Platform Status Notes
iOS 13+ βœ… Fully supported Native UIContextMenuInteraction
iOS 14+ βœ… Enhanced UIMenu with advanced features
iOS 15+ βœ… Enhanced Subtitles support
Android ⚠️ Fallback Displays children without menu
Web ⚠️ Fallback Displays children without menu

Version Compatibility

expo-focus-menu Expo SDK React Native iOS Android Node
0.1.x 54+ 0.81+ 15.0+ API 24+ (SDK 36) 20.0+

Dependency Requirements

Dependency Version Required
expo * Yes (peer)
react * Yes (peer)
react-native * Yes (peer)
ExpoModulesCore Auto-linked Yes (iOS)

Build Configuration

Configuration iOS Android
Swift Version 5.9 -
Compile SDK - 36
Min SDK iOS 15.0, tvOS 15.0 API 24
Target SDK - 36

Examples

Check the example directory for a complete working example with various use cases.

# Run the example app
cd example
npm install
npm run ios

Testing

# Run all tests
npm test

# Run iOS specific tests
npm run test:ios

# Run with coverage
npm test -- --coverage

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

Roadmap

  • Android support with native implementation
  • Web context menu support
  • Custom menu animations
  • Menu item badges
  • Dynamic menu updates
  • Custom preview views
  • Menu section headers

License

MIT Β© shottah

Acknowledgments

  • Built with Expo Modules API
  • Inspired by iOS native context menu interactions
  • Emoji picker design inspired by popular messaging apps

Support

About

Expo Module package that replicates iOS iMessage Long Press functionality

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •