@quyvt.dev/react-native-native-menu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quy Vo
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @quyvt.dev/react-native-native-menu
2
+
3
+ Fabric-only native system menus for React Native. iOS uses UIKit `UIMenu`; Android intentionally leaves fallback UI to the consuming app.
4
+
5
+ ## Requirements
6
+
7
+ - React Native with the New Architecture enabled
8
+ - iOS with CocoaPods
9
+ - iOS 15.1 or newer when using the current React Native scaffold defaults
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install @quyvt.dev/react-native-native-menu
15
+ cd ios && pod install
16
+ ```
17
+
18
+ Rebuild the native application after installation.
19
+
20
+ ## Usage
21
+
22
+ Render your visual trigger, then place `NativeMenuButton` over it on iOS. Android receives a no-op component, so the app owns its fallback interaction.
23
+
24
+ ```tsx
25
+ import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
26
+ import {
27
+ NativeMenuButton,
28
+ type NativeMenuItem,
29
+ } from '@quyvt.dev/react-native-native-menu';
30
+
31
+ const items: NativeMenuItem[] = [
32
+ { id: 'preview', label: 'Preview' },
33
+ { id: 'disabled', label: 'Unavailable', disabled: true },
34
+ { id: 'delete', label: 'Delete', destructive: true },
35
+ ];
36
+
37
+ function MenuTrigger() {
38
+ const handleSelect = (id: string) => {
39
+ console.log(id);
40
+ };
41
+
42
+ return (
43
+ <View style={styles.trigger}>
44
+ <Text>•••</Text>
45
+ {Platform.OS === 'ios' ? (
46
+ <NativeMenuButton
47
+ accessibilityLabel="More options"
48
+ items={items}
49
+ style={StyleSheet.absoluteFill}
50
+ onSelect={handleSelect}
51
+ />
52
+ ) : (
53
+ <Pressable
54
+ accessibilityLabel="More options"
55
+ style={StyleSheet.absoluteFill}
56
+ onPress={() => openAndroidMenu(items, handleSelect)}
57
+ />
58
+ )}
59
+ </View>
60
+ );
61
+ }
62
+
63
+ const styles = StyleSheet.create({
64
+ trigger: { width: 48, height: 48 },
65
+ });
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `NativeMenuButton`
71
+
72
+ A transparent native touch surface. Give it non-zero dimensions and position it over your app-owned visual trigger.
73
+
74
+ ### `NativeMenuButtonProps`
75
+
76
+ | Prop | Type | Description |
77
+ | --- | --- | --- |
78
+ | `accessibilityLabel` | `string?` | Accessibility label applied to the native iOS button. |
79
+ | `items` | `ReadonlyArray<NativeMenuItem>` | Ordered native menu actions. |
80
+ | `onSelect` | `(id: string) => void` | Called immediately with the selected item ID. |
81
+ | `style` | `StyleProp<ViewStyle>?` | Layout for the transparent touch surface. |
82
+
83
+ ### `NativeMenuItem`
84
+
85
+ | Field | Type | Description |
86
+ | --- | --- | --- |
87
+ | `id` | `string` | Stable value delivered to `onSelect`. |
88
+ | `label` | `string` | Native action title. |
89
+ | `destructive` | `boolean?` | Applies UIKit destructive styling. |
90
+ | `disabled` | `boolean?` | Prevents selection. |
91
+
92
+ ## Platform behavior
93
+
94
+ - **iOS:** Uses a Fabric component backed by `UIButton`, `UIAction`, and `UIMenu`. Item order is preserved and prop updates rebuild the native menu.
95
+ - **System appearance:** UIKit supplies the native animation and material. Liquid Glass appears automatically on operating systems and SDKs that provide it; this package does not emulate that effect.
96
+ - **Version 0.1.0 theme:** The native view uses dark interface style by default.
97
+ - **Android:** The exported component renders `null`. The consuming app must render its own trigger and fallback menu.
98
+
99
+ ## Troubleshooting
100
+
101
+ - Ensure the React Native New Architecture is enabled; legacy architecture view managers are not included.
102
+ - Run `pod install` in the app's `ios` directory after adding or upgrading the package.
103
+ - Rebuild the native app instead of relying on JavaScript reloads after installation.
104
+ - Give `NativeMenuButton` non-zero width and height, usually with `StyleSheet.absoluteFill` over a sized trigger.
105
+ - If the view is not registered, clean the iOS build and reinstall pods so Codegen can regenerate the Fabric component provider.
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,78 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <react/renderer/components/VTNativeMenuButtonSpec/ComponentDescriptors.h>
3
+ #import <react/renderer/components/VTNativeMenuButtonSpec/EventEmitters.h>
4
+ #import <react/renderer/components/VTNativeMenuButtonSpec/Props.h>
5
+ #import <react/renderer/components/VTNativeMenuButtonSpec/RCTComponentViewHelpers.h>
6
+
7
+ using namespace facebook::react;
8
+
9
+ @interface VTNativeMenuButtonView : UIView
10
+ @property (nonatomic, copy) NSArray *items;
11
+ @property (nonatomic, copy, nullable) NSString *menuAccessibilityLabel;
12
+ @property (nonatomic, copy, nullable) void (^onSelect)(NSString *itemId);
13
+ @end
14
+
15
+ @interface VTNativeMenuButton : RCTViewComponentView <RCTVTNativeMenuButtonViewProtocol>
16
+ @end
17
+
18
+ @implementation VTNativeMenuButton {
19
+ VTNativeMenuButtonView *_menuButtonView;
20
+ }
21
+
22
+ + (ComponentDescriptorProvider)componentDescriptorProvider
23
+ {
24
+ return concreteComponentDescriptorProvider<VTNativeMenuButtonComponentDescriptor>();
25
+ }
26
+
27
+ - (instancetype)initWithFrame:(CGRect)frame
28
+ {
29
+ if (self = [super initWithFrame:frame]) {
30
+ static const auto defaultProps = std::make_shared<const VTNativeMenuButtonProps>();
31
+ _props = defaultProps;
32
+
33
+ _menuButtonView = [[VTNativeMenuButtonView alloc] initWithFrame:frame];
34
+
35
+ __weak VTNativeMenuButton *weakSelf = self;
36
+ _menuButtonView.onSelect = ^(NSString *itemId) {
37
+ VTNativeMenuButton *strongSelf = weakSelf;
38
+ if (!strongSelf || !strongSelf->_eventEmitter) {
39
+ return;
40
+ }
41
+
42
+ auto eventEmitter = std::static_pointer_cast<const VTNativeMenuButtonEventEmitter>(
43
+ strongSelf->_eventEmitter);
44
+ eventEmitter->onSelect(VTNativeMenuButtonEventEmitter::OnSelect{
45
+ .id = std::string(itemId.UTF8String),
46
+ });
47
+ };
48
+
49
+ self.contentView = _menuButtonView;
50
+ }
51
+
52
+ return self;
53
+ }
54
+
55
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
56
+ {
57
+ const auto &newMenuProps = *std::static_pointer_cast<const VTNativeMenuButtonProps>(props);
58
+ NSMutableArray<NSDictionary *> *items =
59
+ [NSMutableArray arrayWithCapacity:newMenuProps.items.size()];
60
+
61
+ for (const auto &item : newMenuProps.items) {
62
+ [items addObject:@{
63
+ @"destructive" : @(item.destructive),
64
+ @"disabled" : @(item.disabled),
65
+ @"id" : [NSString stringWithUTF8String:item.id.c_str()],
66
+ @"label" : [NSString stringWithUTF8String:item.label.c_str()],
67
+ }];
68
+ }
69
+
70
+ _menuButtonView.items = items;
71
+ _menuButtonView.menuAccessibilityLabel = newMenuProps.menuAccessibilityLabel.empty()
72
+ ? nil
73
+ : [NSString stringWithUTF8String:newMenuProps.menuAccessibilityLabel.c_str()];
74
+
75
+ [super updateProps:props oldProps:oldProps];
76
+ }
77
+
78
+ @end
@@ -0,0 +1,64 @@
1
+ import UIKit
2
+
3
+ @objc(VTNativeMenuButtonView)
4
+ final class VTNativeMenuButtonView: UIView {
5
+ private let button = UIButton(type: .system)
6
+
7
+ @objc var items: NSArray = [] {
8
+ didSet { updateMenu() }
9
+ }
10
+
11
+ @objc var menuAccessibilityLabel: NSString? {
12
+ didSet { button.accessibilityLabel = menuAccessibilityLabel as String? }
13
+ }
14
+
15
+ @objc var onSelect: ((NSString) -> Void)?
16
+
17
+ override init(frame: CGRect) {
18
+ super.init(frame: frame)
19
+
20
+ overrideUserInterfaceStyle = .dark
21
+ button.backgroundColor = .clear
22
+ button.overrideUserInterfaceStyle = .dark
23
+ button.showsMenuAsPrimaryAction = true
24
+ addSubview(button)
25
+ updateMenu()
26
+ }
27
+
28
+ required init?(coder: NSCoder) {
29
+ fatalError("init(coder:) has not been implemented")
30
+ }
31
+
32
+ override func layoutSubviews() {
33
+ super.layoutSubviews()
34
+ button.frame = bounds
35
+ }
36
+
37
+ private func updateMenu() {
38
+ let actions = items.compactMap { value -> UIAction? in
39
+ guard
40
+ let item = value as? NSDictionary,
41
+ let id = item["id"] as? String,
42
+ let label = item["label"] as? String
43
+ else {
44
+ return nil
45
+ }
46
+
47
+ var attributes: UIMenuElement.Attributes = []
48
+
49
+ if item["destructive"] as? Bool == true {
50
+ attributes.insert(.destructive)
51
+ }
52
+
53
+ if item["disabled"] as? Bool == true {
54
+ attributes.insert(.disabled)
55
+ }
56
+
57
+ return UIAction(title: label, attributes: attributes) { [weak self] _ in
58
+ self?.onSelect?(id as NSString)
59
+ }
60
+ }
61
+
62
+ button.menu = UIMenu(children: actions)
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ import NativeMenuButtonComponent from './NativeMenuButtonNativeComponent';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ export const getNativeMenuItemId = event => event.nativeEvent.id;
6
+ const NativeMenuButton = ({
7
+ accessibilityLabel,
8
+ items,
9
+ onSelect,
10
+ style
11
+ }) => /*#__PURE__*/_jsx(NativeMenuButtonComponent, {
12
+ items: items,
13
+ menuAccessibilityLabel: accessibilityLabel,
14
+ style: style,
15
+ onSelect: event => onSelect(getNativeMenuItemId(event))
16
+ });
17
+ export default NativeMenuButton;
18
+ //# sourceMappingURL=NativeMenuButton.ios.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeMenuButtonComponent","jsx","_jsx","getNativeMenuItemId","event","nativeEvent","id","NativeMenuButton","accessibilityLabel","items","onSelect","style","menuAccessibilityLabel"],"sourceRoot":"../../src","sources":["NativeMenuButton.ios.tsx"],"mappings":";;AAEA,OAAOA,yBAAyB,MAAM,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAY1E,OAAO,MAAMC,mBAAmB,GAAIC,KAA4B,IAC9DA,KAAK,CAACC,WAAW,CAACC,EAAE;AAEtB,MAAMC,gBAAgB,GAAGA,CAAC;EACxBC,kBAAkB;EAClBC,KAAK;EACLC,QAAQ;EACRC;AACqB,CAAC,kBACtBT,IAAA,CAACF,yBAAyB;EACxBS,KAAK,EAAEA,KAAM;EACbG,sBAAsB,EAAEJ,kBAAmB;EAC3CG,KAAK,EAAEA,KAAM;EACbD,QAAQ,EAAGN,KAA4B,IACrCM,QAAQ,CAACP,mBAAmB,CAACC,KAAK,CAAC;AACpC,CACF,CACF;AAED,eAAeG,gBAAgB","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ const NativeMenuButton = _props => null;
4
+ export default NativeMenuButton;
5
+ //# sourceMappingURL=NativeMenuButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeMenuButton","_props"],"sourceRoot":"../../src","sources":["NativeMenuButton.tsx"],"mappings":";;AAEA,MAAMA,gBAAgB,GAAIC,MAA6B,IAAK,IAAI;AAEhE,eAAeD,gBAAgB","ignoreList":[]}
@@ -0,0 +1,19 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
3
+
4
+ export type NativeMenuItem = Readonly<{
5
+ destructive?: boolean;
6
+ disabled?: boolean;
7
+ id: string;
8
+ label: string;
9
+ }>;
10
+
11
+ export interface NativeProps extends ViewProps {
12
+ items: ReadonlyArray<NativeMenuItem>;
13
+ menuAccessibilityLabel?: string;
14
+ onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{ id: string }>>;
15
+ }
16
+
17
+ export default codegenNativeComponent<NativeProps>(
18
+ 'VTNativeMenuButton'
19
+ ) as HostComponent<NativeProps>;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export { default as NativeMenuButton } from './NativeMenuButton';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["default","NativeMenuButton"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,gBAAgB,QAAQ,oBAAoB","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,5 @@
1
+ import type { NativeMenuButtonProps } from './NativeMenuButton.ios.js';
2
+ declare const NativeMenuButton: (_props: NativeMenuButtonProps) => null;
3
+ export default NativeMenuButton;
4
+ export type { NativeMenuButtonProps };
5
+ //# sourceMappingURL=NativeMenuButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeMenuButton.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAAwB,CAAC;AAEpE,QAAA,MAAM,gBAAgB,GAAI,QAAQ,qBAAqB,SAAS,CAAC;AAEjE,eAAe,gBAAgB,CAAC;AAChC,YAAY,EAAE,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
2
+ import type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
3
+ type NativeMenuSelectEvent = NativeSyntheticEvent<{
4
+ id: string;
5
+ }>;
6
+ export type NativeMenuButtonProps = {
7
+ accessibilityLabel?: string;
8
+ items: ReadonlyArray<NativeMenuItem>;
9
+ onSelect: (id: string) => void;
10
+ style?: StyleProp<ViewStyle>;
11
+ };
12
+ export declare const getNativeMenuItemId: (event: NativeMenuSelectEvent) => string;
13
+ declare const NativeMenuButton: ({ accessibilityLabel, items, onSelect, style, }: NativeMenuButtonProps) => import("react").JSX.Element;
14
+ export default NativeMenuButton;
15
+ //# sourceMappingURL=NativeMenuButton.ios.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeMenuButton.ios.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButton.ios.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,KAAK,qBAAqB,GAAG,oBAAoB,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,qBAAqB,WAC1C,CAAC;AAEvB,QAAA,MAAM,gBAAgB,GAAI,iDAKvB,qBAAqB,gCASvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
2
+ export type NativeMenuItem = Readonly<{
3
+ destructive?: boolean;
4
+ disabled?: boolean;
5
+ id: string;
6
+ label: string;
7
+ }>;
8
+ export interface NativeProps extends ViewProps {
9
+ items: ReadonlyArray<NativeMenuItem>;
10
+ menuAccessibilityLabel?: string;
11
+ onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{
12
+ id: string;
13
+ }>>;
14
+ }
15
+ declare const _default: HostComponent<NativeProps>;
16
+ export default _default;
17
+ //# sourceMappingURL=NativeMenuButtonNativeComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeMenuButtonNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/NativeMenuButtonNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,YAAY,CAAC,oBAAoB,CAAC,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACxE;wBAII,aAAa,CAAC,WAAW,CAAC;AAF/B,wBAEgC"}
@@ -0,0 +1,4 @@
1
+ export { default as NativeMenuButton } from './NativeMenuButton';
2
+ export type { NativeMenuButtonProps } from './NativeMenuButton';
3
+ export type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "@quyvt.dev/react-native-native-menu",
3
+ "version": "0.1.0",
4
+ "description": "Native system menus for React Native using Fabric and UIKit UIMenu",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "react-native-native-menu-source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "!src/__tests__",
18
+ "lib",
19
+ "ios",
20
+ "react-native-native-menu.podspec",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "scripts": {
25
+ "example": "npm --workspace @quyvt.dev/react-native-native-menu-example run",
26
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
27
+ "prepare": "bob build",
28
+ "test": "jest",
29
+ "typecheck": "tsc",
30
+ "lint": "eslint \"**/*.{js,ts,tsx}\""
31
+ },
32
+ "keywords": [
33
+ "react-native",
34
+ "ios",
35
+ "android"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/vtquy98/react-native-native-menu.git"
40
+ },
41
+ "author": "Quy Vo <vtquy98@gmail.com> (https://github.com/vtquy98)",
42
+ "license": "MIT",
43
+ "bugs": {
44
+ "url": "https://github.com/vtquy98/react-native-native-menu/issues"
45
+ },
46
+ "homepage": "https://github.com/vtquy98/react-native-native-menu#readme",
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "registry": "https://registry.npmjs.org/"
50
+ },
51
+ "devDependencies": {
52
+ "@eslint/compat": "^2.1.0",
53
+ "@eslint/eslintrc": "^3.3.5",
54
+ "@eslint/js": "^9.39.5",
55
+ "@react-native/babel-preset": "0.85.3",
56
+ "@react-native/eslint-config": "0.85.0",
57
+ "@types/react": "^19.2.0",
58
+ "@types/jest": "^29.5.14",
59
+ "@types/react-test-renderer": "^19.1.0",
60
+ "del-cli": "^7.0.0",
61
+ "eslint": "^9.39.4",
62
+ "eslint-config-prettier": "^10.1.8",
63
+ "eslint-plugin-ft-flow": "^3.0.11",
64
+ "eslint-plugin-prettier": "^5.5.6",
65
+ "jest": "^29.7.0",
66
+ "prettier": "^3.8.3",
67
+ "react": "19.2.3",
68
+ "react-native": "0.85.3",
69
+ "react-native-builder-bob": "^0.43.0",
70
+ "react-test-renderer": "19.2.3",
71
+ "turbo": "^2.9.16",
72
+ "typescript": "^6.0.3"
73
+ },
74
+ "peerDependencies": {
75
+ "react": "*",
76
+ "react-native": "*"
77
+ },
78
+ "workspaces": [
79
+ "example"
80
+ ],
81
+ "packageManager": "npm@11.4.2",
82
+ "react-native-builder-bob": {
83
+ "source": "src",
84
+ "output": "lib",
85
+ "targets": [
86
+ [
87
+ "module",
88
+ {
89
+ "esm": true
90
+ }
91
+ ],
92
+ [
93
+ "typescript",
94
+ {
95
+ "project": "tsconfig.build.json"
96
+ }
97
+ ]
98
+ ]
99
+ },
100
+ "codegenConfig": {
101
+ "name": "VTNativeMenuButtonSpec",
102
+ "type": "components",
103
+ "jsSrcsDir": "src",
104
+ "ios": {
105
+ "componentProvider": {
106
+ "VTNativeMenuButton": "VTNativeMenuButton"
107
+ }
108
+ }
109
+ },
110
+ "prettier": {
111
+ "quoteProps": "consistent",
112
+ "singleQuote": true,
113
+ "tabWidth": 2,
114
+ "trailingComma": "es5",
115
+ "useTabs": false
116
+ },
117
+ "create-react-native-library": {
118
+ "type": "fabric-view",
119
+ "languages": "kotlin-objc",
120
+ "tools": [
121
+ "eslint"
122
+ ],
123
+ "version": "0.63.0"
124
+ }
125
+ }
@@ -0,0 +1,19 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-native-menu"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/vtquy98/react-native-native-menu.git", :tag => s.version.to_s }
15
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
16
+ s.swift_version = "5.0"
17
+
18
+ install_modules_dependencies(s)
19
+ end
@@ -0,0 +1,34 @@
1
+ import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
2
+
3
+ import NativeMenuButtonComponent from './NativeMenuButtonNativeComponent';
4
+ import type { NativeMenuItem } from './NativeMenuButtonNativeComponent';
5
+
6
+ type NativeMenuSelectEvent = NativeSyntheticEvent<{ id: string }>;
7
+
8
+ export type NativeMenuButtonProps = {
9
+ accessibilityLabel?: string;
10
+ items: ReadonlyArray<NativeMenuItem>;
11
+ onSelect: (id: string) => void;
12
+ style?: StyleProp<ViewStyle>;
13
+ };
14
+
15
+ export const getNativeMenuItemId = (event: NativeMenuSelectEvent) =>
16
+ event.nativeEvent.id;
17
+
18
+ const NativeMenuButton = ({
19
+ accessibilityLabel,
20
+ items,
21
+ onSelect,
22
+ style,
23
+ }: NativeMenuButtonProps) => (
24
+ <NativeMenuButtonComponent
25
+ items={items}
26
+ menuAccessibilityLabel={accessibilityLabel}
27
+ style={style}
28
+ onSelect={(event: NativeMenuSelectEvent) =>
29
+ onSelect(getNativeMenuItemId(event))
30
+ }
31
+ />
32
+ );
33
+
34
+ export default NativeMenuButton;
@@ -0,0 +1,6 @@
1
+ import type { NativeMenuButtonProps } from './NativeMenuButton.ios';
2
+
3
+ const NativeMenuButton = (_props: NativeMenuButtonProps) => null;
4
+
5
+ export default NativeMenuButton;
6
+ export type { NativeMenuButtonProps };
@@ -0,0 +1,19 @@
1
+ import { codegenNativeComponent } from 'react-native';
2
+ import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
3
+
4
+ export type NativeMenuItem = Readonly<{
5
+ destructive?: boolean;
6
+ disabled?: boolean;
7
+ id: string;
8
+ label: string;
9
+ }>;
10
+
11
+ export interface NativeProps extends ViewProps {
12
+ items: ReadonlyArray<NativeMenuItem>;
13
+ menuAccessibilityLabel?: string;
14
+ onSelect?: CodegenTypes.BubblingEventHandler<Readonly<{ id: string }>>;
15
+ }
16
+
17
+ export default codegenNativeComponent<NativeProps>(
18
+ 'VTNativeMenuButton'
19
+ ) as HostComponent<NativeProps>;
package/src/index.tsx ADDED
@@ -0,0 +1,3 @@
1
+ export { default as NativeMenuButton } from './NativeMenuButton';
2
+ export type { NativeMenuButtonProps } from './NativeMenuButton';
3
+ export type { NativeMenuItem } from './NativeMenuButtonNativeComponent';