# @react-native-menu/menu
![Supports Android, iOS][support-badge]![Github Action Badge][gha-badge] ![npm][npm-badge]
Android PopupMenu and iOS14+ UIMenu components for react-native.
Falls back to ActionSheet for versions below iOS14.
| Android | iOS 14+ | iOS 13 |
|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| | | |
## Installation
via npm:
```sh
npm install @react-native-menu/menu
```
via yarn:
```sh
yarn add @react-native-menu/menu
```
### Installing on iOS with React Native 0.63 and above
There is an issue(https://github.com/facebook/react-native/issues/29246) causing projects with this module to fail on build on React Native 0.63 and above.
This issue may be fixed in future versions of react native.
As a work around, look for lines in `[YourPrject].xcodeproj` under `LIBRARY_SEARCH_PATHS` with `"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",` and change `swift-5.0` to `swift-5.3`.
## Linking
The package is [automatically linked](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) when building the app. All you need to do is:
```sh
npx pod-install
```
## Usage
```jsx
import { MenuView, MenuComponentRef } from '@react-native-menu/menu';
// ...
const App = () => {
const menuRef = useRef(null);
return (
);
};
```
### Declarative usage
It's also possible to obtain the `action` is a more React-ish, declarative fashion. Refer to the [`react-to-imperative`](https://github.com/vonovak/react-to-imperative?tab=readme-ov-file#why) package, and see an example [here](https://github.com/vonovak/react-navigation-header-buttons/blob/cca6ce6d04d3b106efe7aa62279101db33c7941b/example/src/screens/UsageNativeMenu.tsx#L62).
## Reference
### Props
#### `ref` (Android only)
Ref to the menu component.
| Type | Required |
|------|----------|
| ref | No |
### `title` (iOS only)
The title of the menu.
| Type | Required |
|--------|----------|
| string | Yes |
### `isAnchoredToRight` (Android only)
Boolean determining if menu should anchored to right or left corner of parent view.
| Type | Required |
|---------|----------|
| boolean | No |
### `shouldOpenOnLongPress`
Boolean determining if menu should open after long press or on normal press
| Type | Required |
|---------|----------|
| boolean | No |
### `actions`
Actions to be displayed in the menu.
| Type | Required |
|--------------|----------|
| MenuAction[] | Yes |
### `themeVariant` (iOS only)
String to override theme of the menu. If you want to control theme universally across your app, [see this package](https://github.com/vonovak/react-native-theme-control).
| Type | Required |
|-----------------------|----------|
| enum('light', 'dark') | No |
#### `MenuAction`
Object representing Menu Action.
```ts
export type MenuAction = {
/**
* Identifier of the menu action.
* The value set in this id will be returned when menu is selected.
*/
id?: string;
/**
* The action's title.
*/
title: string;
/**
* (Android only)
* The action's title color.
* @platform Android
*/
titleColor?: number | ColorValue;
/**
* (iOS14+ only)
* An elaborated title that explains the purpose of the action.
* @platform iOS
*/
subtitle?: string;
/**
* The attributes indicating the style of the action.
*/
attributes?: MenuAttributes;
/**
* (iOS14+ only)
* The state of the action.
* @platform iOS
*/
state?: MenuState;
/**
* (Android and iOS13+ only)
* - The action's image.
* - Allows icon name included in project or system (Android) resources drawables and
* in SF Symbol (iOS)
* @example // (iOS)
* image="plus"
* @example // (Android)
* image="ic_menu_add"
*/
image?: string;
/**
* (Android and iOS13+ only)
* - The action's image color.
*/
imageColor?: number | ColorValue;
/**
* (Android and iOS14+ only)
* - Actions to be displayed in the sub menu
* - On Android it does not support nesting next sub menus in sub menu item
*/
subactions?: MenuAction[];
};
```
#### `MenuAttributes`
The attributes indicating the style of the action.
```ts
type MenuAttributes = {
/**
* An attribute indicating the destructive style.
*/
destructive?: boolean;
/**
* An attribute indicating the disabled style.
*/
disabled?: boolean;
/**
* An attribute indicating the hidden style.
*/
hidden?: boolean;
};
```
#### `MenuState`
The state of the action.
```ts
/**
* The state of the action.
* - off: A constant indicating the menu element is in the “off” state.
* - on: A constant indicating the menu element is in the “on” state.
* - mixed: A constant indicating the menu element is in the “mixed” state.
*/
type MenuState = 'off' | 'on' | 'mixed';
```
### `onPressAction`
Callback function that will be called when selecting a menu item.
It will contain id of the given action.
| Type | Required |
|-------------------------|----------|
| ({nativeEvent}) => void | No |
### Events
#### `onCloseMenu`
Callback function that will be called when the menu is dismissed. This event fires at the start of the dismissal, before any animations complete.
| Type | Required |
|------------|----------|
| () => void | No |
#### `onOpenMenu`
Callback function that will be called when the menu is opened. This event fires right before the menu is displayed.
| Type | Required |
|------------|----------|
| () => void | No |
Example usage:
```jsx
{
console.log('Menu was opened');
}}
onCloseMenu={() => {
console.log('Menu was closed');
}}
// ... other props
>
Open Menu
```
## Testing with Jest
In some cases, you might want to mock the package to test your components. You can do this by using the `jest.mock` function.
```ts
import type { MenuComponentProps } from '@react-native-menu/menu';
jest.mock('@react-native-menu/menu', () => ({
MenuView: jest.fn((props: MenuComponentProps) => {
const React = require('react');
class MockMenuView extends React.Component {
render() {
return React.createElement(
'View',
{ testID: props.testID },
// Dynamically mock each action
props.actions.map(action =>
React.createElement('Button', {
key: action.id,
title: action.title,
onPress: () => {
if (action.id && props?.onPressAction) {
props.onPressAction({ nativeEvent: { event: action.id } });
}
},
testID: action.id
})
),
this.props.children
);
}
}
return React.createElement(MockMenuView, props);
})
}));
```
## Contributing
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
## License
MIT
[gha-badge]: https://github.com/react-native-menu/menu/workflows/Build/badge.svg
[npm-badge]: https://img.shields.io/npm/v/@react-native-menu/menu.svg?style=flat-square
[support-badge]: https://img.shields.io/badge/platforms-android%20|%20ios-lightgrey.svg?style=flat-square