Skip to content

Commit

Permalink
feat(android): add show method to MenuView component (#954)
Browse files Browse the repository at this point in the history
* feat(android): add show method to MenuView component

* refactor(types): re-export MenuComponentRef type

* docs: update README with ref usage example
  • Loading branch information
batuhanoztrk authored Oct 31, 2024
1 parent 02592ae commit 205c4bb
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 28 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ npx pod-install
## Usage

```jsx
import { MenuView } from '@react-native-menu/menu';
import { MenuView, MenuComponentRef } from '@react-native-menu/menu';

// ...

const App = () => {
const menuRef = useRef<MenuComponentRef>(null);
return (
<View style={styles.container}>
<Button
title="Show Menu with ref (Android only)"
onPress={() => menuRef.current?.show()}
/>
<MenuView
ref={menuRef}
title="Menu Title"
onPressAction={({ nativeEvent }) => {
console.warn(JSON.stringify(nativeEvent));
Expand Down Expand Up @@ -131,6 +137,14 @@ It's also possible to obtain the `action` is a more React-ish, declarative fashi
### Props
#### `ref` (Android only)
Ref to the menu component.
| Type | Required |
|------|----------|
| ref | No |
### `title` (iOS only)
The title of the menu.
Expand Down
4 changes: 4 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
})
}

fun show(){
prepareMenu()
}

override fun setHitSlopRect(rect: Rect?) {
super.setHitSlopRect(rect)
mHitSlopRect = rect
Expand Down
11 changes: 11 additions & 0 deletions android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,18 @@ abstract class MenuViewManagerBase: ReactClippingViewManager<MenuView>() {
view.setBackfaceVisibilityDependantOpacity()
}

override fun getCommandsMap(): MutableMap<String, Int>? {
return MapBuilder.of("show", COMMAND_SHOW)
}

override fun receiveCommand(root: MenuView, commandId: Int, args: ReadableArray?) {
when (commandId) {
COMMAND_SHOW -> root.show()
}
}

companion object {
val COMMAND_SHOW = 1
val SPACING_TYPES = arrayOf(
Spacing.ALL,
Spacing.LEFT,
Expand Down
12 changes: 10 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import * as React from 'react';

Check failure on line 1 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint (20)

Definition for rule 'prettier/prettier' was not found
import { Platform, StyleSheet, Text, View } from 'react-native';
import { MenuView } from '@react-native-menu/menu';
import { Button, Platform, StyleSheet, Text, View } from 'react-native';
import { MenuView, MenuComponentRef } from '@react-native-menu/menu';
import { useRef } from 'react';

export const App = () => {
const [themeVariant] = React.useState<string | undefined>('light');
const menuRef = useRef<MenuComponentRef>(null);

return (
<View style={styles.container}>
<Button
title="Show Menu with ref (Android only)"
onPress={() => menuRef.current?.show()}
/>
<MenuView
ref={menuRef}
title="Menu Title"
onPressAction={({ nativeEvent }) => {
console.warn(JSON.stringify(nativeEvent));
Expand Down Expand Up @@ -178,6 +185,7 @@ const styles = StyleSheet.create({
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 10,
},
button: {
height: 100,
Expand Down
36 changes: 33 additions & 3 deletions src/UIMenuView.android.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
import { HostComponent, requireNativeComponent } from 'react-native';
import type { NativeMenuComponentProps } from './types';
import {

Check failure on line 1 in src/UIMenuView.android.tsx

View workflow job for this annotation

GitHub Actions / lint (20)

Definition for rule 'prettier/prettier' was not found
findNodeHandle,
HostComponent,
requireNativeComponent,
UIManager,
} from 'react-native';
import type { MenuComponentRef, NativeMenuComponentProps } from './types';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';

const MenuComponent = requireNativeComponent(
const NativeMenuComponent = requireNativeComponent(
'MenuView'
) as HostComponent<NativeMenuComponentProps>;

const MenuComponent = forwardRef<MenuComponentRef, NativeMenuComponentProps>(
(props, ref) => {
const nativeRef = useRef(null);

useImperativeHandle(
ref,
() => ({
show: () => {
if (nativeRef.current) {
const node = findNodeHandle(nativeRef.current);
const command =
UIManager.getViewManagerConfig('MenuView').Commands.show;

UIManager.dispatchViewManagerCommand(node, command, undefined);
}
},
}),
[nativeRef]
);

return <NativeMenuComponent {...props} ref={nativeRef} />;
}
);

export default MenuComponent;
50 changes: 28 additions & 22 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { forwardRef, useMemo } from 'react';
import { processColor } from 'react-native';

import UIMenuView from './UIMenuView';
Expand All @@ -7,6 +7,7 @@ import type {
MenuAction,
ProcessedMenuAction,
NativeActionEvent,
MenuComponentRef,
} from './types';
import { objectHash } from './utils';

Expand All @@ -21,26 +22,31 @@ function processAction(action: MenuAction): ProcessedMenuAction {

const defaultHitslop = { top: 0, left: 0, bottom: 0, right: 0 };

const MenuView: React.FC<MenuComponentProps> = ({
actions,
hitSlop = defaultHitslop,
...props
}) => {
const processedActions = actions.map<ProcessedMenuAction>((action) =>
processAction(action)
);
const hash = useMemo(() => {
return objectHash(processedActions);
}, [processedActions]);
return (
<UIMenuView
{...props}
hitSlop={hitSlop}
actions={processedActions}
actionsHash={hash}
/>
);
};
const MenuView = forwardRef<MenuComponentRef, MenuComponentProps>(
({ actions, hitSlop = defaultHitslop, ...props }, ref) => {
const processedActions = actions.map<ProcessedMenuAction>((action) =>
processAction(action)
);
const hash = useMemo(() => {
return objectHash(processedActions);
}, [processedActions]);

return (
<UIMenuView
{...props}
hitSlop={hitSlop}
actions={processedActions}
actionsHash={hash}
ref={ref}
/>
);
}
);

export { MenuView };
export type { MenuComponentProps, MenuAction, NativeActionEvent };
export type {
MenuComponentProps,
MenuComponentRef,
MenuAction,
NativeActionEvent,
};
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ type MenuComponentPropsBase = {
export type MenuComponentProps =
React.PropsWithChildren<MenuComponentPropsBase>;

export type MenuComponentRef = {
show: () => void;
};

export type ProcessedMenuAction = Omit<
MenuAction,
'imageColor' | 'titleColor' | 'subactions'
Expand All @@ -180,4 +184,5 @@ export type NativeMenuComponentProps = {
title?: string;
hitSlop?: MenuComponentProps['hitSlop'];
testID?: string;
ref?: React.ForwardedRef<MenuComponentRef>;
};

0 comments on commit 205c4bb

Please sign in to comment.