├── .editorconfig
├── .gitattributes
├── .github
├── CODEOWNERS
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .tool-versions
├── .yarn
└── releases
│ └── yarn-4.1.1.cjs
├── .yarnrc.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── android
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── AndroidManifestNew.xml
│ └── java
│ │ └── com
│ │ └── reactnativemenu
│ │ ├── MenuOnCloseEvent.kt
│ │ ├── MenuOnOpenEvent.kt
│ │ ├── MenuOnPressActionEvent.kt
│ │ ├── MenuPackage.kt
│ │ ├── MenuView.kt
│ │ └── MenuViewManagerBase.kt
│ ├── newarch
│ └── MenuViewManagerSpec.kt
│ ├── oldarch
│ └── MenuViewManagerSpec.kt
│ └── reactNativeVersionPatch
│ └── MenuViewManager
│ ├── 75
│ └── com
│ │ └── reactnativemenu
│ │ └── MenuViewManager.kt
│ └── latest
│ └── com
│ └── reactnativemenu
│ └── MenuViewManager.kt
├── babel.config.js
├── biome.json
├── example
├── .gitignore
├── .watchmanconfig
├── android
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app.json
├── babel.config.js
├── index.js
├── ios
│ ├── Podfile
│ └── Podfile.lock
├── src
│ └── App.tsx
└── visionos
│ ├── Podfile
│ └── Podfile.lock
├── ios
├── Menu-Bridging-Header.h
├── MenuViewManager.mm
├── NewArch
│ ├── FabricActionSheetView.swift
│ ├── FabricMenuViewImplementation.swift
│ ├── FabricViewImplementationProtocol.swift
│ ├── MenuView.h
│ └── MenuView.mm
├── OldArch
│ ├── LegacyActionSheetView.swift
│ └── LegacyMenuViewImplementation.swift
└── Shared
│ ├── ActionSheetView.swift
│ ├── MenuViewImplementation.swift
│ ├── RCTAlertAction.swift
│ └── RCTMenuItem.swift
├── metro.config.js
├── package.json
├── plugin
└── withAndroidDrawables.js
├── react-native-menu.podspec
├── react-native.config.js
├── src
├── NativeModuleSpecs
│ └── UIMenuNativeComponent.ts
├── UIMenuView.android.tsx
├── UIMenuView.ios.tsx
├── UIMenuView.tsx
├── __tests__
│ └── index.test.tsx
├── index.tsx
├── types.ts
└── utils.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @Naturalclar
2 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 |
4 |
5 |
6 | # Test Plan
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | ignore:
9 | - dependency-name: pod-install
10 | versions:
11 | - 0.1.16
12 | - 0.1.17
13 | - 0.1.19
14 | - 0.1.20
15 | - dependency-name: release-it
16 | versions:
17 | - 14.2.2
18 | - 14.3.0
19 | - 14.4.0
20 | - 14.4.1
21 | - 14.5.0
22 | - 14.5.1
23 | - dependency-name: eslint
24 | versions:
25 | - 7.18.0
26 | - 7.20.0
27 | - 7.23.0
28 | - dependency-name: "@types/react-native"
29 | versions:
30 | - 0.63.49
31 | - 0.63.51
32 | - 0.64.0
33 | - dependency-name: react-native
34 | versions:
35 | - 0.64.0
36 | - dependency-name: react
37 | versions:
38 | - 17.0.1
39 | - dependency-name: typescript
40 | versions:
41 | - 4.1.4
42 | - 4.2.3
43 | - dependency-name: eslint-config-prettier
44 | versions:
45 | - 8.0.0
46 | - dependency-name: prettier
47 | versions:
48 | - 2.2.1
49 | - dependency-name: "@types/react"
50 | versions:
51 | - 17.0.0
52 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | pull_request:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | lint:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | node-version: [20]
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v1
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | - name: Get yarn cache
20 | id: yarn-cache
21 | run: echo "::set-output name=dir::$(yarn cache dir)"
22 | - uses: actions/cache@v4
23 | with:
24 | path: ${{ steps.yarn-cache.outputs.dir }}
25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
26 | - name: Install Dependencies
27 | run: yarn
28 | - name: ESLint Checks
29 | run: yarn lint
30 | tsc:
31 | runs-on: ubuntu-latest
32 | strategy:
33 | matrix:
34 | node-version: [20]
35 | steps:
36 | - uses: actions/checkout@v2
37 | - uses: actions/setup-node@v1
38 | with:
39 | node-version: ${{ matrix.node-version }}
40 | - name: Get yarn cache
41 | id: yarn-cache
42 | run: echo "::set-output name=dir::$(yarn cache dir)"
43 | - uses: actions/cache@v4
44 | with:
45 | path: ${{ steps.yarn-cache.outputs.dir }}
46 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
47 | - name: Install Dependencies
48 | run: yarn
49 | - name: TypeScript type check
50 | run: yarn typescript
51 | android:
52 | runs-on: ubuntu-latest
53 | strategy:
54 | matrix:
55 | node-version: [20]
56 | steps:
57 | - uses: actions/checkout@v2
58 | - uses: actions/setup-node@v1
59 | with:
60 | node-version: ${{ matrix.node-version }}
61 | - uses: actions/setup-java@v1
62 | with:
63 | java-version: "17"
64 | - name: Get yarn cache
65 | id: yarn-cache
66 | run: echo "::set-output name=dir::$(yarn cache dir)"
67 | - uses: actions/cache@v4
68 | with:
69 | path: ${{ steps.yarn-cache.outputs.dir }}
70 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
71 | - name: Install Dependencies
72 | run: yarn && yarn bootstrap
73 | - name: Build Android Example App and Library
74 | run: cd example/android && ./gradlew clean assembleDebug
75 | ios:
76 | runs-on: macos-latest
77 | strategy:
78 | matrix:
79 | node-version: [20]
80 | arch: ["NewArch", "OldArch"]
81 | steps:
82 | - uses: actions/checkout@v2
83 | - uses: actions/setup-node@v1
84 | with:
85 | node-version: ${{ matrix.node-version }}
86 | - name: Get yarn cache
87 | id: yarn-cache
88 | run: echo "::set-output name=dir::$(yarn cache dir)"
89 | - uses: actions/cache@v4
90 | with:
91 | path: ${{ steps.yarn-cache.outputs.dir }}
92 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
93 | - name: Get arch value
94 | run: echo "RCT_NEW_ARCH_ENABLED=${{ matrix.arch == 'NewArch' && '1' || '0' }}" >> $GITHUB_ENV
95 | - name: Install Dependencies
96 | run: yarn && yarn bootstrap
97 | - name: Install Podfiles
98 | run: cd example && npx pod-install
99 | - name: Print React Native Info
100 | run: cd example && npx react-native info
101 | - name: Build example app
102 | run: cd example && yarn ios --no-packager
103 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 |
32 | # Android/IJ
33 | #
34 | .idea
35 | .gradle
36 | local.properties
37 | android.iml
38 |
39 | # Cocoapods
40 | #
41 | example/ios/Pods
42 |
43 | # node.js
44 | #
45 | node_modules/
46 | npm-debug.log
47 | yarn-debug.log
48 | yarn-error.log
49 |
50 | # BUCK
51 | buck-out/
52 | \.buckd/
53 | android/app/libs
54 | android/keystores/debug.keystore
55 |
56 | # Expo
57 | .expo/*
58 |
59 | # generated by bob
60 | lib/
61 |
62 | # yarn
63 | .yarn/install-state.gz
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | ruby 2.7.6
2 | java adoptopenjdk-17.0.10+7
3 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
3 | yarnPath: .yarn/releases/yarn-4.1.1.cjs
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
4 |
5 | ## Development workflow
6 |
7 | To get started with the project, run `yarn bootstrap` in the root directory to install the required dependencies for each package:
8 |
9 | ```sh
10 | yarn bootstrap
11 | ```
12 |
13 | While developing, you can run the [example app](/example/) to test your changes.
14 |
15 | To start the packager:
16 |
17 | ```sh
18 | yarn start
19 | ```
20 |
21 | To run the example app on Android:
22 |
23 | ```sh
24 | yarn android
25 | ```
26 |
27 | To run the example app on iOS:
28 |
29 | ```sh
30 | yarn ios
31 | ```
32 |
33 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
34 |
35 | ```sh
36 | yarn typescript
37 | yarn lint
38 | ```
39 |
40 | To fix formatting errors, run the following:
41 |
42 | ```sh
43 | yarn lint --fix
44 | ```
45 |
46 | Remember to add tests for your change if possible. Run the unit tests by:
47 |
48 | ```sh
49 | yarn test
50 | ```
51 |
52 | To edit the Objective-C files, open `example/ios/MenuExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-menu`.
53 |
54 | To edit the Kotlin files, open `example/android` in Android studio and find the source files at `reactnativemenu` under `Android`.
55 |
56 | ### Versioning in `MenuViewManager`
57 |
58 | As of the latest update, `com.reactnativemenu.MenuViewManager` has been refactored into an abstract base class, `MenuViewManagerBase`. Any React Native version-dependent changes should be implemented in the specific version folders under `reactNativeVersionPatch`.
59 |
60 | For consistency:
61 |
62 | - When making version-specific modifications, ensure you update the appropriate implementation of `MenuViewManager`:
63 | - `src/reactNativeVersionPatch/75/MenuViewManager.kt` for React Native < 0.76
64 | - `src/reactNativeVersionPatch/latest/MenuViewManager.kt` for React Native >= 0.76
65 | - If adding a new file that depends on React Native version, create folders under `reactNativeVersionPatch` for both `75` and `latest` and include the version-specific implementations there.
66 | - Update `build.gradle` to include the new file in the `sourceSets`, so it’s dynamically selected based on the React Native version.
67 |
68 | This approach ensures consistent version handling and clean separation of code across versions.
69 |
70 | ### Commit message convention
71 |
72 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
73 |
74 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
75 | - `feat`: new features, e.g. add new method to the module.
76 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
77 | - `docs`: changes into documentation, e.g. add usage example for the module..
78 | - `test`: adding or updating tests, e.g. add integration tests using detox.
79 | - `chore`: tooling changes, e.g. change CI config.
80 |
81 | Our pre-commit hooks verify that your commit message matches this format when committing.
82 |
83 | ### Linting and tests
84 |
85 | [Biome](https://biomejs.dev/), [TypeScript](https://www.typescriptlang.org/)
86 |
87 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [Biome](https://biomejs.dev/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
88 |
89 | Our pre-commit hooks verify that the linter and tests pass when committing.
90 |
91 | ### Scripts
92 |
93 | The `package.json` file contains various scripts for common tasks:
94 |
95 | - `yarn bootstrap`: setup project by installing all dependencies and pods.
96 | - `yarn typescript`: type-check files with TypeScript.
97 | - `yarn lint`: lint files with Biome.
98 | - `yarn test`: run unit tests with Jest.
99 | - `yarn start`: start the Metro server for the example app.
100 | - `yarn android`: run the example app on Android.
101 | - `yarn ios`: run the example app on iOS.
102 |
103 | ### Sending a pull request
104 |
105 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
106 |
107 | When you're sending a pull request:
108 |
109 | - Prefer small pull requests focused on one change.
110 | - Verify that linters and tests are passing.
111 | - Review the documentation to make sure it looks good.
112 | - Follow the pull request template when opening a pull request.
113 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
114 | - For version-dependent changes, follow the versioning structure for `MenuViewManager` outlined in the **Versioning in `MenuViewManager`** section. Ensure all version-specific files are included in `reactNativeVersionPatch` and referenced in `build.gradle`.
115 |
116 | ## Code of Conduct
117 |
118 | ### Our Pledge
119 |
120 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
121 |
122 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
123 |
124 | ### Our Standards
125 |
126 | Examples of behavior that contributes to a positive environment for our community include:
127 |
128 | - Demonstrating empathy and kindness toward other people
129 | - Being respectful of differing opinions, viewpoints, and experiences
130 | - Giving and gracefully accepting constructive feedback
131 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
132 | - Focusing on what is best not just for us as individuals, but for the overall community
133 |
134 | Examples of unacceptable behavior include:
135 |
136 | - The use of sexualized language or imagery, and sexual attention or
137 | advances of any kind
138 | - Trolling, insulting or derogatory comments, and personal or political attacks
139 | - Public or private harassment
140 | - Publishing others' private information, such as a physical or email
141 | address, without their explicit permission
142 | - Other conduct which could reasonably be considered inappropriate in a
143 | professional setting
144 |
145 | ### Enforcement Responsibilities
146 |
147 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
148 |
149 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
150 |
151 | ### Scope
152 |
153 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
154 |
155 | ### Enforcement
156 |
157 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
158 |
159 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
160 |
161 | ### Enforcement Guidelines
162 |
163 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
164 |
165 | #### 1. Correction
166 |
167 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
168 |
169 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
170 |
171 | #### 2. Warning
172 |
173 | **Community Impact**: A violation through a single incident or series of actions.
174 |
175 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
176 |
177 | #### 3. Temporary Ban
178 |
179 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
180 |
181 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
182 |
183 | #### 4. Permanent Ban
184 |
185 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
186 |
187 | **Consequence**: A permanent ban from any sort of public interaction within the community.
188 |
189 | ### Attribution
190 |
191 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
192 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
193 |
194 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
195 |
196 | [homepage]: https://www.contributor-covenant.org
197 |
198 | For answers to common questions about this code of conduct, see the FAQ at
199 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
200 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jesse Katsumata
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @react-native-menu/menu
2 |
3 | ![Supports Android, iOS][support-badge]![Github Action Badge][gha-badge] ![npm][npm-badge]
4 |
5 | Android PopupMenu and iOS14+ UIMenu components for react-native.
6 | Falls back to ActionSheet for versions below iOS14.
7 |
8 | | Android | iOS 14+ | iOS 13 |
9 | |--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
10 | |
|
|
|
11 |
12 | ## Installation
13 |
14 | via npm:
15 |
16 | ```sh
17 | npm install @react-native-menu/menu
18 | ```
19 |
20 | via yarn:
21 |
22 | ```sh
23 | yarn add @react-native-menu/menu
24 | ```
25 |
26 | ### Installing on iOS with React Native 0.63 and above
27 |
28 | 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.
29 | This issue may be fixed in future versions of react native.
30 | 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`.
31 |
32 | ## Linking
33 |
34 | 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:
35 |
36 | ```sh
37 | npx pod-install
38 | ```
39 |
40 | ## Usage
41 |
42 | ```jsx
43 | import { MenuView, MenuComponentRef } from '@react-native-menu/menu';
44 |
45 | // ...
46 |
47 | const App = () => {
48 | const menuRef = useRef(null);
49 | return (
50 |
51 |
128 | );
129 | };
130 | ```
131 |
132 | ### Declarative usage
133 |
134 | 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).
135 |
136 | ## Reference
137 |
138 | ### Props
139 |
140 | #### `ref` (Android only)
141 |
142 | Ref to the menu component.
143 |
144 | | Type | Required |
145 | |------|----------|
146 | | ref | No |
147 |
148 | ### `title` (iOS only)
149 |
150 | The title of the menu.
151 |
152 | | Type | Required |
153 | |--------|----------|
154 | | string | Yes |
155 |
156 | ### `isAnchoredToRight` (Android only)
157 |
158 | Boolean determining if menu should anchored to right or left corner of parent view.
159 |
160 | | Type | Required |
161 | |---------|----------|
162 | | boolean | No |
163 |
164 | ### `shouldOpenOnLongPress`
165 |
166 | Boolean determining if menu should open after long press or on normal press
167 |
168 | | Type | Required |
169 | |---------|----------|
170 | | boolean | No |
171 |
172 | ### `actions`
173 |
174 | Actions to be displayed in the menu.
175 |
176 | | Type | Required |
177 | |--------------|----------|
178 | | MenuAction[] | Yes |
179 |
180 | ### `themeVariant` (iOS only)
181 |
182 | 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).
183 |
184 | | Type | Required |
185 | |-----------------------|----------|
186 | | enum('light', 'dark') | No |
187 |
188 | #### `MenuAction`
189 |
190 | Object representing Menu Action.
191 |
192 | ```ts
193 | export type MenuAction = {
194 | /**
195 | * Identifier of the menu action.
196 | * The value set in this id will be returned when menu is selected.
197 | */
198 | id?: string;
199 | /**
200 | * The action's title.
201 | */
202 | title: string;
203 | /**
204 | * (Android only)
205 | * The action's title color.
206 | * @platform Android
207 | */
208 | titleColor?: number | ColorValue;
209 | /**
210 | * (iOS14+ only)
211 | * An elaborated title that explains the purpose of the action.
212 | * @platform iOS
213 | */
214 | subtitle?: string;
215 | /**
216 | * The attributes indicating the style of the action.
217 | */
218 | attributes?: MenuAttributes;
219 | /**
220 | * (iOS14+ only)
221 | * The state of the action.
222 | * @platform iOS
223 | */
224 | state?: MenuState;
225 | /**
226 | * (Android and iOS13+ only)
227 | * - The action's image.
228 | * - Allows icon name included in project or system (Android) resources drawables and
229 | * in SF Symbol (iOS)
230 | * @example // (iOS)
231 | * image="plus"
232 | * @example // (Android)
233 | * image="ic_menu_add"
234 | */
235 | image?: string;
236 | /**
237 | * (Android and iOS13+ only)
238 | * - The action's image color.
239 | */
240 | imageColor?: number | ColorValue;
241 | /**
242 | * (Android and iOS14+ only)
243 | * - Actions to be displayed in the sub menu
244 | * - On Android it does not support nesting next sub menus in sub menu item
245 | */
246 | subactions?: MenuAction[];
247 | };
248 | ```
249 |
250 | #### `MenuAttributes`
251 |
252 | The attributes indicating the style of the action.
253 |
254 | ```ts
255 | type MenuAttributes = {
256 | /**
257 | * An attribute indicating the destructive style.
258 | */
259 | destructive?: boolean;
260 | /**
261 | * An attribute indicating the disabled style.
262 | */
263 | disabled?: boolean;
264 | /**
265 | * An attribute indicating the hidden style.
266 | */
267 | hidden?: boolean;
268 | };
269 | ```
270 |
271 | #### `MenuState`
272 |
273 | The state of the action.
274 |
275 | ```ts
276 | /**
277 | * The state of the action.
278 | * - off: A constant indicating the menu element is in the “off” state.
279 | * - on: A constant indicating the menu element is in the “on” state.
280 | * - mixed: A constant indicating the menu element is in the “mixed” state.
281 | */
282 | type MenuState = 'off' | 'on' | 'mixed';
283 | ```
284 |
285 | ### `onPressAction`
286 |
287 | Callback function that will be called when selecting a menu item.
288 | It will contain id of the given action.
289 |
290 | | Type | Required |
291 | |-------------------------|----------|
292 | | ({nativeEvent}) => void | No |
293 |
294 | ### Events
295 |
296 | #### `onCloseMenu`
297 |
298 | Callback function that will be called when the menu is dismissed. This event fires at the start of the dismissal, before any animations complete.
299 |
300 | | Type | Required |
301 | |------------|----------|
302 | | () => void | No |
303 |
304 | #### `onOpenMenu`
305 |
306 | Callback function that will be called when the menu is opened. This event fires right before the menu is displayed.
307 |
308 | | Type | Required |
309 | |------------|----------|
310 | | () => void | No |
311 |
312 | Example usage:
313 | ```jsx
314 | {
316 | console.log('Menu was opened');
317 | }}
318 | onCloseMenu={() => {
319 | console.log('Menu was closed');
320 | }}
321 | // ... other props
322 | >
323 |
324 | Open Menu
325 |
326 |
327 | ```
328 |
329 | ### Custom icons (Android)
330 |
331 | You might want to use custom icons in the MenuAction `image` attribute. To do so, follow these steps.
332 |
333 | 1. Search for your icon on e.g. [Material Icons](https://fonts.google.com/icons), customize the fill, weight, grade etc.
334 | to your liking and then press the `Android` tab and click `Download`. This wil download an xml file, e.g.
335 | `search_24px.xml`. You can create your own icon or get it somewhere else, as long as it is in a format that Android
336 | understands.
337 | 2. If using bare react-native, copy the downloaded xml file to your `android/app/src/main/res/drawable` folder.
338 | If you are using Expo, add a dependency
339 | on [@expo/config-plugins,](https://www.npmjs.com/package/@expo/config-plugins) and then you can use
340 | an [expo config plugin](./plugin/withAndroidDrawables.js) to copy the file from your `assets` folder to the drawable
341 | folder. Copy the config plugin to your app and reference it in your `app.json` or `app.config.js` in the `plugins`
342 | section like this:
343 |
344 | ```json
345 | {
346 | "expo": {
347 | "plugins": [
348 | [
349 | "./plugin/withAndroidDrawables",
350 | {
351 | "drawableFiles": [ "./assets/my_icon.xml" ]
352 | }
353 | ]
354 | ]
355 | }
356 | }
357 | ```
358 | 3. In your `MenuAction` you can now reference the icon using its file name, without the `.xml` extension. For example,
359 | `image: 'my_icon'` will use the `my_icon.xml` file you copied to the drawable folder.
360 | 4. Remember to run a new build to see changes to these icons.
361 |
362 | ## Testing with Jest
363 |
364 | In some cases, you might want to mock the package to test your components. You can do this by using the `jest.mock` function.
365 |
366 | ```ts
367 | import type { MenuComponentProps } from '@react-native-menu/menu';
368 |
369 | jest.mock('@react-native-menu/menu', () => ({
370 | MenuView: jest.fn((props: MenuComponentProps) => {
371 | const React = require('react');
372 |
373 | class MockMenuView extends React.Component {
374 | render() {
375 | return React.createElement(
376 | 'View',
377 | { testID: props.testID },
378 | // Dynamically mock each action
379 | props.actions.map(action =>
380 | React.createElement('Button', {
381 | key: action.id,
382 | title: action.title,
383 | onPress: () => {
384 | if (action.id && props?.onPressAction) {
385 | props.onPressAction({ nativeEvent: { event: action.id } });
386 | }
387 | },
388 | testID: action.id
389 | })
390 | ),
391 | this.props.children
392 | );
393 | }
394 | }
395 |
396 | return React.createElement(MockMenuView, props);
397 | })
398 | }));
399 | ```
400 |
401 | ## Contributing
402 |
403 | See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
404 |
405 | ## License
406 |
407 | MIT
408 |
409 | [gha-badge]: https://github.com/react-native-menu/menu/workflows/Build/badge.svg
410 | [npm-badge]: https://img.shields.io/npm/v/@react-native-menu/menu.svg?style=flat-square
411 | [support-badge]: https://img.shields.io/badge/platforms-android%20|%20ios-lightgrey.svg?style=flat-square
412 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android_
4 | Project android_ created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
19 | 1708364511770
20 |
21 | 30
22 |
23 | org.eclipse.core.resources.regexFilterMatcher
24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3 | def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['Menu_kotlinVersion']
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | classpath "com.android.tools.build:gradle:7.2.1"
12 | // noinspection DifferentKotlinGradleVersion
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | def isNewArchitectureEnabled() {
18 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19 | }
20 |
21 | apply plugin: "com.android.library"
22 | apply plugin: "kotlin-android"
23 |
24 | if (isNewArchitectureEnabled()) {
25 | apply plugin: "com.facebook.react"
26 | }
27 |
28 | def getExtOrDefault(name) {
29 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Menu_" + name]
30 | }
31 |
32 | def getExtOrIntegerDefault(name) {
33 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Menu_" + name]).toInteger()
34 | }
35 |
36 | def getExtOrFallback(name, fallback) {
37 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
38 | }
39 |
40 | def supportsNamespace() {
41 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
42 | def major = parsed[0].toInteger()
43 | def minor = parsed[1].toInteger()
44 |
45 | // Namespace support was added in 7.3.0
46 | return (major == 7 && minor >= 3) || major >= 8
47 | }
48 |
49 | def resolveReactNativeDirectory() {
50 | def reactNativeLocation = getExtOrFallback("REACT_NATIVE_NODE_MODULES_DIR", null)
51 | if (reactNativeLocation != null) {
52 | return file(reactNativeLocation)
53 | }
54 |
55 | // monorepo workaround.
56 | def reactNativePackage = file(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim())
57 | if(reactNativePackage.exists()) {
58 | return reactNativePackage.parentFile
59 | }
60 |
61 | throw new GradleException(
62 | "[@react-native-menu/menu] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
63 | )
64 | }
65 |
66 | def getReactNativeMinorVersion() {
67 | def REACT_NATIVE_DIR = resolveReactNativeDirectory()
68 |
69 | def reactProperties = new Properties()
70 | file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
71 |
72 | def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
73 | def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
74 |
75 | return REACT_NATIVE_MINOR_VERSION
76 | }
77 |
78 | android {
79 | if (supportsNamespace()) {
80 | namespace "com.reactnativemenu"
81 |
82 | sourceSets {
83 | main {
84 | manifest.srcFile "src/main/AndroidManifestNew.xml"
85 | }
86 | }
87 | }
88 |
89 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
90 |
91 | defaultConfig {
92 | minSdkVersion 24
93 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
94 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
95 | versionCode 1
96 | versionName "1.0"
97 | }
98 |
99 | buildFeatures {
100 | buildConfig true
101 | }
102 |
103 | buildTypes {
104 | release {
105 | minifyEnabled false
106 | }
107 | }
108 |
109 | lintOptions {
110 | disable "GradleCompatible"
111 | }
112 |
113 | compileOptions {
114 | sourceCompatibility JavaVersion.VERSION_1_8
115 | targetCompatibility JavaVersion.VERSION_1_8
116 | }
117 |
118 | sourceSets {
119 | main {
120 | if (isNewArchitectureEnabled()) {
121 | java.srcDirs += [
122 | "src/newarch",
123 | // This is needed to build Kotlin project with NewArch enabled
124 | "${project.buildDir}/generated/source/codegen/java"
125 | ]
126 | } else {
127 | java.srcDirs += ["src/oldarch"]
128 | }
129 |
130 | // MenuViewManager
131 | if (getReactNativeMinorVersion() <= 75) {
132 | java.srcDirs += "src/reactNativeVersionPatch/MenuViewManager/75"
133 | } else {
134 | java.srcDirs += "src/reactNativeVersionPatch/MenuViewManager/latest"
135 | }
136 | }
137 | }
138 | }
139 |
140 | repositories {
141 | mavenCentral()
142 | google()
143 | }
144 |
145 | def kotlin_version = getExtOrDefault("kotlinVersion")
146 |
147 | dependencies {
148 | // For < 0.71, this will be from the local maven repo
149 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
150 | //noinspection GradleDynamicVersion
151 | implementation "com.facebook.react:react-native:+"
152 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
153 | }
154 |
155 | if (isNewArchitectureEnabled()) {
156 | react {
157 | jsRootDir = file("../src/")
158 | libraryName = "MenuView"
159 | codegenJavaPackageName = "com.reactnativemenu"
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | Menu_kotlinVersion=1.7.0
2 | Menu_minSdkVersion=24
3 | Menu_targetSdkVersion=31
4 | Menu_compileSdkVersion=31
5 | Menu_ndkversion=21.4.7075529
6 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-native-menu/menu/f629f1cc4ddca75b5d08c24684b947d4a7066b3f/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 21 12:53:47 CET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifestNew.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuOnCloseEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.bridge.WritableMap
5 | import com.facebook.react.uimanager.events.Event
6 |
7 | class MenuOnCloseEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event(surfaceId, viewId) {
8 | override fun getEventName() = "onCloseMenu"
9 |
10 | override fun getEventData(): WritableMap? {
11 | return Arguments.createMap()
12 | }
13 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuOnOpenEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.bridge.WritableMap
5 | import com.facebook.react.uimanager.events.Event
6 |
7 | class MenuOnOpenEvent(surfaceId: Int, viewId: Int, private val targetId: Int) : Event(surfaceId, viewId) {
8 | override fun getEventName() = "onOpenMenu"
9 |
10 | override fun getEventData(): WritableMap? {
11 | return Arguments.createMap()
12 | }
13 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuOnPressActionEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.bridge.WritableMap
5 | import com.facebook.react.uimanager.events.Event
6 |
7 | class MenuOnPressActionEvent(
8 | surfaceId: Int,
9 | viewTag: Int,
10 | private val event: String?,
11 | private val target: Int
12 | ) : Event(surfaceId, viewTag) {
13 |
14 | override fun getEventName(): String = "onPressAction"
15 | override fun getEventData(): WritableMap = Arguments.createMap().apply {
16 | if (event != null) {
17 | putString("event", event)
18 | }
19 | putString("target", target.toString())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuPackage.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import java.util.Arrays
4 | import java.util.Collections
5 |
6 | import com.facebook.react.ReactPackage
7 | import com.facebook.react.bridge.NativeModule
8 | import com.facebook.react.bridge.ReactApplicationContext
9 | import com.facebook.react.uimanager.ViewManager
10 | import com.facebook.react.bridge.JavaScriptModule
11 |
12 | class MenuPackage : ReactPackage {
13 | override fun createNativeModules(reactContext: ReactApplicationContext): List {
14 | return emptyList()
15 | }
16 |
17 | override fun createViewManagers(reactContext: ReactApplicationContext): List> {
18 | return mutableListOf(MenuViewManager())
19 | }
20 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuView.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import android.content.res.ColorStateList
4 | import android.content.res.Resources
5 | import android.graphics.Color
6 | import android.graphics.Rect
7 | import android.os.Build
8 | import android.text.Spannable
9 | import android.text.SpannableStringBuilder
10 | import android.text.style.ForegroundColorSpan
11 | import android.view.*
12 | import android.widget.PopupMenu
13 | import com.facebook.react.bridge.*
14 | import com.facebook.react.uimanager.UIManagerHelper
15 | import com.facebook.react.views.view.ReactViewGroup
16 | import java.lang.reflect.Field
17 |
18 |
19 | class MenuView(private val mContext: ReactContext) : ReactViewGroup(mContext) {
20 | private lateinit var mActions: ReadableArray
21 | private var mIsAnchoredToRight = false
22 | private val mPopupMenu: PopupMenu = PopupMenu(context, this)
23 | private var mIsMenuDisplayed = false
24 | private var mIsOnLongPress = false
25 | private var mGestureDetector: GestureDetector
26 | private var mHitSlopRect: Rect? = null
27 |
28 | init {
29 | mGestureDetector = GestureDetector(mContext, object : GestureDetector.SimpleOnGestureListener() {
30 | override fun onLongPress(e: MotionEvent) {
31 | if (!mIsOnLongPress) {
32 | return
33 | }
34 | prepareMenu()
35 | }
36 |
37 | override fun onSingleTapUp(e: MotionEvent): Boolean {
38 | if (!mIsOnLongPress) {
39 | prepareMenu()
40 | }
41 | return true
42 | }
43 | })
44 | }
45 |
46 | fun show(){
47 | prepareMenu()
48 | }
49 |
50 | override fun setHitSlopRect(rect: Rect?) {
51 | super.setHitSlopRect(rect)
52 | mHitSlopRect = rect
53 | updateTouchDelegate()
54 | }
55 |
56 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
57 | return true
58 | }
59 |
60 | override fun onTouchEvent(ev: MotionEvent): Boolean {
61 | mGestureDetector.onTouchEvent(ev)
62 | return true
63 | }
64 |
65 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
66 | super.onSizeChanged(w, h, oldw, oldh)
67 | updateTouchDelegate()
68 | }
69 |
70 | override fun onAttachedToWindow() {
71 | super.onAttachedToWindow()
72 | updateTouchDelegate()
73 | }
74 |
75 | override fun onDetachedFromWindow() {
76 | super.onDetachedFromWindow()
77 | if (mIsMenuDisplayed) {
78 | mPopupMenu.dismiss()
79 | }
80 | }
81 |
82 | fun setActions(actions: ReadableArray) {
83 | mActions = actions
84 | }
85 |
86 | fun setIsAnchoredToRight(isAnchoredToRight: Boolean) {
87 | if (mIsAnchoredToRight == isAnchoredToRight) {
88 | return
89 | }
90 | mIsAnchoredToRight = isAnchoredToRight
91 | }
92 |
93 | fun setIsOpenOnLongPress(isLongPress: Boolean) {
94 | mIsOnLongPress = isLongPress
95 | }
96 |
97 | private val getActionsCount: Int
98 | get() = mActions.size()
99 |
100 | private fun prepareMenuItem(menuItem: MenuItem, config: ReadableMap?) {
101 | val titleColor = when (config != null && config.hasKey("titleColor") && !config.isNull("titleColor")) {
102 | true -> config.getInt("titleColor")
103 | else -> null
104 | }
105 | val imageName = when (config != null && config.hasKey("image") && !config.isNull("image")) {
106 | true -> config.getString("image")
107 | else -> null
108 | }
109 | val imageColor = when (config != null && config.hasKey("imageColor") && !config.isNull("imageColor")) {
110 | true -> config.getInt("imageColor")
111 | else -> null
112 | }
113 | val attributes = when (config != null && config.hasKey("attributes") && !config.isNull(("attributes"))) {
114 | true -> config.getMap("attributes")
115 | else -> null
116 | }
117 | val subactions = when (config != null && config.hasKey("subactions") && !config.isNull(("subactions"))) {
118 | true -> config.getArray("subactions")
119 | else -> null
120 | }
121 | val menuState = config?.getString("state")
122 |
123 | if (titleColor != null) {
124 | menuItem.title = getMenuItemTextWithColor(menuItem.title.toString(), titleColor)
125 | }
126 |
127 | if (imageName != null) {
128 | val resourceId: Int = getDrawableIdWithName(imageName)
129 | if (resourceId != 0) {
130 | val icon = resources.getDrawable(resourceId, context.theme)
131 | if (imageColor != null) {
132 | icon.setTintList(ColorStateList.valueOf(imageColor))
133 | }
134 | menuItem.icon = icon
135 | }
136 | }
137 |
138 | if (attributes != null) {
139 | // actions.attributes.disabled
140 | val disabled = when (attributes.hasKey("disabled") && !attributes.isNull("disabled")) {
141 | true -> attributes.getBoolean("disabled")
142 | else -> false
143 | }
144 | menuItem.isEnabled = !disabled
145 | if (!menuItem.isEnabled) {
146 | val disabledColor = 0x77888888
147 | menuItem.title = getMenuItemTextWithColor(menuItem.title.toString(), disabledColor)
148 | if (imageName != null) {
149 | val icon = menuItem.icon
150 | icon?.setTintList(ColorStateList.valueOf(disabledColor))
151 | menuItem.icon = icon
152 | }
153 | }
154 |
155 | // actions.attributes.hidden
156 | val hidden = when (attributes.hasKey("hidden") && !attributes.isNull("hidden")) {
157 | true -> attributes.getBoolean("hidden")
158 | else -> false
159 | }
160 | menuItem.isVisible = !hidden
161 |
162 | // actions.attributes.destructive
163 | val destructive = when (attributes.hasKey("destructive") && !attributes.isNull("destructive")) {
164 | true -> attributes.getBoolean("destructive")
165 | else -> false
166 | }
167 | if (destructive) {
168 | menuItem.title = getMenuItemTextWithColor(menuItem.title.toString(), Color.RED)
169 | if (imageName != null) {
170 | val icon = menuItem.icon
171 | icon?.setTintList(ColorStateList.valueOf(Color.RED))
172 | menuItem.icon = icon
173 | }
174 | }
175 | }
176 |
177 | // Handle menuState for checkable items
178 | when (menuState) {
179 | "on", "off" -> {
180 | menuItem.isCheckable = true
181 | menuItem.isChecked = menuState == "on"
182 | }
183 | else -> menuItem.isCheckable = false
184 | }
185 |
186 | // On Android SubMenu cannot contain another SubMenu, so even if there are subactions provided
187 | // we are checking if item has submenu (which will occur only for 1 lvl nesting)
188 | if (subactions != null && menuItem.hasSubMenu()) {
189 | var i = 0
190 | val subactionsCount = subactions.size()
191 | while (i < subactionsCount) {
192 | if (!subactions.isNull(i)) {
193 | val subMenuConfig = subactions.getMap(i)
194 | val subMenuItem = menuItem.subMenu?.add(Menu.NONE, Menu.NONE, i, subMenuConfig?.getString("title"))
195 | if (subMenuItem != null) {
196 | prepareMenuItem(subMenuItem, subMenuConfig)
197 | subMenuItem.setOnMenuItemClickListener {
198 | if (!it.hasSubMenu()) {
199 | mIsMenuDisplayed = false
200 | if (!subactions.isNull(it.order)) {
201 | val selectedItem = subactions.getMap(it.order)
202 | val dispatcher =
203 | UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
204 | val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
205 | dispatcher?.dispatchEvent(
206 | MenuOnPressActionEvent(surfaceId, id, selectedItem?.getString("id"), id)
207 | )
208 | }
209 | true
210 | } else {
211 | false
212 | }
213 | }
214 | }
215 | }
216 | i++
217 | }
218 | }
219 | }
220 |
221 | private fun prepareMenu() {
222 | if (getActionsCount > 0) {
223 | mPopupMenu.menu.clear()
224 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
225 | mPopupMenu.gravity = when (mIsAnchoredToRight) {
226 | true -> Gravity.RIGHT
227 | false -> Gravity.LEFT
228 | }
229 | }
230 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
231 | mPopupMenu.setForceShowIcon(true)
232 | }
233 | var i = 0
234 | while (i < getActionsCount) {
235 | if (!mActions.isNull(i)) {
236 | val item = mActions.getMap(i)
237 | val menuItem = when (item != null && item.hasKey("subactions") && !item.isNull("subactions")) {
238 | true -> mPopupMenu.menu.addSubMenu(Menu.NONE, Menu.NONE, i, item.getString("title")).item
239 | else -> mPopupMenu.menu.add(Menu.NONE, Menu.NONE, i, item?.getString("title"))
240 | }
241 | prepareMenuItem(menuItem, item)
242 | menuItem.setOnMenuItemClickListener {
243 | if (!it.hasSubMenu()) {
244 | mIsMenuDisplayed = false
245 | if (!mActions.isNull(it.order)) {
246 | val selectedItem = mActions.getMap(it.order)
247 | val dispatcher =
248 | UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
249 | val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
250 | dispatcher?.dispatchEvent(
251 | MenuOnPressActionEvent(surfaceId, id, selectedItem?.getString("id"), id)
252 | )
253 | }
254 | true
255 | } else {
256 | false
257 | }
258 | }
259 | }
260 | i++
261 | }
262 | mPopupMenu.setOnDismissListener {
263 | mIsMenuDisplayed = false
264 | val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
265 | val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
266 | dispatcher?.dispatchEvent(MenuOnCloseEvent(surfaceId, id, id))
267 | }
268 | mIsMenuDisplayed = true
269 | val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mContext, id)
270 | val surfaceId: Int = UIManagerHelper.getSurfaceId(this)
271 | dispatcher?.dispatchEvent(MenuOnOpenEvent(surfaceId, id, id))
272 | mPopupMenu.show()
273 | }
274 | }
275 |
276 | private fun updateTouchDelegate() {
277 | post {
278 | val hitRect = Rect()
279 | getHitRect(hitRect)
280 |
281 | mHitSlopRect?.let {
282 | hitRect.left -= it.left
283 | hitRect.top -= it.top
284 | hitRect.right += it.right
285 | hitRect.bottom += it.bottom
286 | }
287 |
288 | (parent as? ViewGroup)?.let {
289 | it.touchDelegate = TouchDelegate(hitRect, this)
290 | }
291 | }
292 | }
293 |
294 | private fun getDrawableIdWithName(name: String): Int {
295 | val appResources: Resources = context.resources
296 | var resourceId = appResources.getIdentifier(name, "drawable", context.packageName)
297 | if (resourceId == 0) {
298 | // If drawable is not present in app's resources, check system's resources
299 | resourceId = getResId(name, android.R.drawable::class.java)
300 | }
301 | return resourceId
302 | }
303 |
304 | private fun getResId(resName: String?, c: Class<*>): Int {
305 | return try {
306 | val idField: Field = c.getDeclaredField(resName!!)
307 | idField.getInt(idField)
308 | } catch (e: Exception) {
309 | e.printStackTrace()
310 | 0
311 | }
312 | }
313 |
314 | private fun getMenuItemTextWithColor(text: String, color: Int): SpannableStringBuilder {
315 | val textWithColor = SpannableStringBuilder()
316 | textWithColor.append(text)
317 | textWithColor.setSpan(ForegroundColorSpan(color),
318 | 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
319 | return textWithColor
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativemenu/MenuViewManagerBase.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import android.annotation.TargetApi
4 | import android.graphics.Rect
5 | import android.os.Build
6 | import android.view.View
7 | import androidx.annotation.NonNull
8 | import androidx.annotation.Nullable
9 | import com.facebook.react.bridge.ReadableArray
10 | import com.facebook.react.bridge.ReadableMap
11 | import com.facebook.react.common.MapBuilder
12 | import com.facebook.react.uimanager.*
13 | import com.facebook.react.uimanager.annotations.ReactProp
14 | import com.facebook.react.uimanager.annotations.ReactPropGroup
15 | import com.facebook.react.views.view.ReactClippingViewManager
16 | import com.facebook.react.views.view.ReactDrawableHelper
17 | import com.facebook.react.views.view.ReactViewGroup
18 | import com.facebook.yoga.YogaConstants
19 |
20 | abstract class MenuViewManagerBase : ReactClippingViewManager() {
21 | override fun getName() = "MenuView"
22 |
23 | @ReactProp(name = "actions")
24 | fun setActions(view: MenuView, actions: ReadableArray) {
25 | view.setActions(actions)
26 | }
27 |
28 | @ReactProp(name = "isAnchoredToRight", defaultBoolean = false)
29 | fun setIsAnchoredToRight(view: MenuView, isAnchoredToRight: Boolean) {
30 | view.setIsAnchoredToRight(isAnchoredToRight)
31 | }
32 |
33 | @ReactProp(name = "shouldOpenOnLongPress", defaultBoolean = false)
34 | fun setIsOnLongPress(view: MenuView, isOnLongPress: Boolean) {
35 | view.setIsOpenOnLongPress(isOnLongPress)
36 | }
37 |
38 | override fun getExportedCustomDirectEventTypeConstants(): MutableMap {
39 | return MapBuilder.of(
40 | "onPressAction",
41 | MapBuilder.of("registrationName", "onPressAction"),
42 | "onCloseMenu",
43 | MapBuilder.of("registrationName", "onCloseMenu"),
44 | "onOpenMenu",
45 | MapBuilder.of("registrationName", "onOpenMenu")
46 | )
47 | }
48 |
49 | // ---- Rest of regular view props ----//
50 | @ReactProp(name = "accessible")
51 | fun setAccessible(view: MenuView, accessible: Boolean) {
52 | view.isFocusable = accessible
53 | }
54 |
55 | @ReactProp(name = "hasTVPreferredFocus")
56 | fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean) {
57 | if (hasTVPreferredFocus) {
58 | view.isFocusable = true
59 | view.isFocusableInTouchMode = true
60 | view.requestFocus()
61 | }
62 | }
63 |
64 | @ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
65 | fun nextFocusDown(view: ReactViewGroup, viewId: Int) {
66 | view.nextFocusDownId = viewId
67 | }
68 |
69 | @ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
70 | fun nextFocusForward(view: ReactViewGroup, viewId: Int) {
71 | view.nextFocusForwardId = viewId
72 | }
73 |
74 | @ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
75 | fun nextFocusLeft(view: ReactViewGroup, viewId: Int) {
76 | view.nextFocusLeftId = viewId
77 | }
78 |
79 | @ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
80 | fun nextFocusRight(view: ReactViewGroup, viewId: Int) {
81 | view.nextFocusRightId = viewId
82 | }
83 |
84 | @ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
85 | fun nextFocusUp(view: ReactViewGroup, viewId: Int) {
86 | view.nextFocusUpId = viewId
87 | }
88 |
89 | @ReactPropGroup(
90 | names =
91 | [
92 | ViewProps.BORDER_RADIUS,
93 | ViewProps.BORDER_TOP_LEFT_RADIUS,
94 | ViewProps.BORDER_TOP_RIGHT_RADIUS,
95 | ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
96 | ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
97 | ViewProps.BORDER_TOP_START_RADIUS,
98 | ViewProps.BORDER_TOP_END_RADIUS,
99 | ViewProps.BORDER_BOTTOM_START_RADIUS,
100 | ViewProps.BORDER_BOTTOM_END_RADIUS]
101 | )
102 | fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float) {
103 | var borderRadius = borderRadius
104 | if (!YogaConstants.isUndefined(borderRadius) && borderRadius < 0) {
105 | borderRadius = YogaConstants.UNDEFINED
106 | }
107 | if (!YogaConstants.isUndefined(borderRadius)) {
108 | borderRadius = PixelUtil.toPixelFromDIP(borderRadius)
109 | }
110 | if (index == 0) {
111 | view.setBorderRadius(borderRadius)
112 | } else {
113 | view.setBorderRadius(borderRadius, index - 1)
114 | }
115 | }
116 |
117 | @ReactProp(name = "borderStyle")
118 | fun setBorderStyle(view: ReactViewGroup, @Nullable borderStyle: String?) {
119 | view.setBorderStyle(borderStyle)
120 | }
121 |
122 | @ReactProp(name = "hitSlop")
123 | fun setHitSlop(view: ReactViewGroup, @Nullable hitSlop: ReadableMap?) {
124 | if (hitSlop == null) {
125 | // We should keep using setters as `Val cannot be reassigned`
126 | view.setHitSlopRect(null)
127 | } else {
128 | view.setHitSlopRect(
129 | Rect(
130 | if (hitSlop.hasKey("left"))
131 | PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")).toInt()
132 | else 0,
133 | if (hitSlop.hasKey("top"))
134 | PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")).toInt()
135 | else 0,
136 | if (hitSlop.hasKey("right"))
137 | PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")).toInt()
138 | else 0,
139 | if (hitSlop.hasKey("bottom"))
140 | PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")).toInt()
141 | else 0
142 | )
143 | )
144 | }
145 | }
146 |
147 | @ReactProp(name = "nativeBackgroundAndroid")
148 | fun setNativeBackground(view: ReactViewGroup, @Nullable bg: ReadableMap?) {
149 | view.setTranslucentBackgroundDrawable(
150 | if (bg == null) null
151 | else ReactDrawableHelper.createDrawableFromJSDescription(view.context, bg)
152 | )
153 | }
154 |
155 | @TargetApi(Build.VERSION_CODES.M)
156 | @ReactProp(name = "nativeForegroundAndroid")
157 | fun setNativeForeground(view: ReactViewGroup, @Nullable fg: ReadableMap?) {
158 | view.foreground =
159 | if (fg == null) null
160 | else ReactDrawableHelper.createDrawableFromJSDescription(view.context, fg)
161 | }
162 |
163 | @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
164 | fun setNeedsOffscreenAlphaCompositing(
165 | view: ReactViewGroup,
166 | needsOffscreenAlphaCompositing: Boolean
167 | ) {
168 | view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing)
169 | }
170 |
171 | @ReactPropGroup(
172 | names =
173 | [
174 | ViewProps.BORDER_WIDTH,
175 | ViewProps.BORDER_LEFT_WIDTH,
176 | ViewProps.BORDER_RIGHT_WIDTH,
177 | ViewProps.BORDER_TOP_WIDTH,
178 | ViewProps.BORDER_BOTTOM_WIDTH,
179 | ViewProps.BORDER_START_WIDTH,
180 | ViewProps.BORDER_END_WIDTH]
181 | )
182 | fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float) {
183 | var width = width
184 | if (!YogaConstants.isUndefined(width) && width < 0) {
185 | width = YogaConstants.UNDEFINED
186 | }
187 | if (!YogaConstants.isUndefined(width)) {
188 | width = PixelUtil.toPixelFromDIP(width)
189 | }
190 | view.setBorderWidth(SPACING_TYPES[index], width)
191 | }
192 |
193 | @ReactPropGroup(
194 | names =
195 | [
196 | ViewProps.BORDER_COLOR,
197 | ViewProps.BORDER_LEFT_COLOR,
198 | ViewProps.BORDER_RIGHT_COLOR,
199 | ViewProps.BORDER_TOP_COLOR,
200 | ViewProps.BORDER_BOTTOM_COLOR,
201 | ViewProps.BORDER_START_COLOR,
202 | ViewProps.BORDER_END_COLOR],
203 | customType = "Color"
204 | )
205 | abstract fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?)
206 |
207 | @ReactProp(name = ViewProps.OVERFLOW)
208 | fun setOverflow(view: ReactViewGroup, overflow: String?) {
209 | view.setOverflow(overflow)
210 | }
211 |
212 | @ReactProp(name = "backfaceVisibility")
213 | fun setBackfaceVisibility(view: ReactViewGroup, backfaceVisibility: String) {
214 | view.setBackfaceVisibility(backfaceVisibility)
215 | }
216 |
217 | override fun setOpacity(@NonNull view: MenuView, opacity: Float) {
218 | view.setOpacityIfPossible(opacity)
219 | }
220 |
221 | override fun setTransform(@NonNull view: MenuView, @Nullable matrix: ReadableArray?) {
222 | super.setTransform(view, matrix)
223 | view.setBackfaceVisibilityDependantOpacity()
224 | }
225 |
226 | override fun getCommandsMap(): MutableMap? {
227 | return MapBuilder.of("show", COMMAND_SHOW)
228 | }
229 |
230 | override fun receiveCommand(root: MenuView, commandId: Int, args: ReadableArray?) {
231 | when (commandId) {
232 | COMMAND_SHOW -> root.show()
233 | }
234 | }
235 |
236 | companion object {
237 | val COMMAND_SHOW = 1
238 | val SPACING_TYPES =
239 | arrayOf(
240 | Spacing.ALL,
241 | Spacing.LEFT,
242 | Spacing.RIGHT,
243 | Spacing.TOP,
244 | Spacing.BOTTOM,
245 | Spacing.START,
246 | Spacing.END
247 | )
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/android/src/newarch/MenuViewManagerSpec.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import android.view.View
4 |
5 | import com.facebook.react.bridge.ReactApplicationContext
6 | import com.facebook.react.uimanager.SimpleViewManager
7 | import com.facebook.react.uimanager.ViewManagerDelegate
8 | import com.facebook.react.viewmanagers.MenuViewManagerDelegate
9 | import com.facebook.react.viewmanagers.MenuViewManagerInterface
10 |
11 | abstract class MenuViewManagerSpec : SimpleViewManager(), MenuViewManagerInterface {
12 | private val mDelegate: ViewManagerDelegate
13 |
14 | init {
15 | mDelegate = MenuViewManagerDelegate(this)
16 | }
17 |
18 | override fun getDelegate(): ViewManagerDelegate? {
19 | return mDelegate
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/src/oldarch/MenuViewManagerSpec.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import android.view.View
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.uimanager.SimpleViewManager
6 |
7 | abstract class MenuViewManagerSpec : SimpleViewManager() {
8 | abstract fun setColor(view: T?, value: String?)
9 | }
10 |
--------------------------------------------------------------------------------
/android/src/reactNativeVersionPatch/MenuViewManager/75/com/reactnativemenu/MenuViewManager.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import com.facebook.react.views.view.ReactViewGroup
4 | import com.facebook.yoga.YogaConstants
5 | import com.facebook.react.uimanager.ThemedReactContext
6 |
7 | class MenuViewManager : MenuViewManagerBase() {
8 | override fun createViewInstance(reactContext: ThemedReactContext): MenuView {
9 | return MenuView(reactContext)
10 | }
11 |
12 | override fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?) {
13 | val rgbComponent = color?.let { (it and 0x00FFFFFF).toFloat() } ?: YogaConstants.UNDEFINED
14 | val alphaComponent = color?.let { (it ushr 24).toFloat() } ?: YogaConstants.UNDEFINED
15 | view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/android/src/reactNativeVersionPatch/MenuViewManager/latest/com/reactnativemenu/MenuViewManager.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativemenu
2 |
3 | import com.facebook.react.views.view.ReactViewGroup
4 | import com.facebook.react.uimanager.ThemedReactContext
5 |
6 | class MenuViewManager : MenuViewManagerBase() {
7 | override fun createViewInstance(reactContext: ThemedReactContext): MenuView {
8 | return MenuView(reactContext)
9 | }
10 |
11 | override fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?) {
12 | view.setBorderColor(index, color)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["module:@react-native/babel-preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "vcs": {
4 | "enabled": false,
5 | "clientKind": "git",
6 | "useIgnoreFile": false
7 | },
8 | "files": {
9 | "ignoreUnknown": false,
10 | "ignore": ["lib"]
11 | },
12 | "formatter": {
13 | "enabled": true,
14 | "indentStyle": "tab"
15 | },
16 | "organizeImports": {
17 | "enabled": true
18 | },
19 | "linter": {
20 | "enabled": true,
21 | "rules": {
22 | "recommended": true
23 | }
24 | },
25 | "javascript": {
26 | "formatter": {
27 | "quoteStyle": "double"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | *.binlog
2 | *.hprof
3 | *.xcworkspace/
4 | *.zip
5 | .DS_Store
6 | .gradle/
7 | .idea/
8 | .vs/
9 | .xcode.env
10 | Pods/
11 | build/
12 | dist/*
13 | !dist/.gitignore
14 | local.properties
15 | msbuild.binlog
16 | node_modules/
17 |
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | apply(from: {
3 | def searchDir = rootDir.toPath()
4 | do {
5 | def p = searchDir.resolve("node_modules/react-native-test-app/android/dependencies.gradle")
6 | if (p.toFile().exists()) {
7 | return p.toRealPath().toString()
8 | }
9 | } while (searchDir = searchDir.getParent())
10 | throw new GradleException("Could not find `react-native-test-app`");
11 | }())
12 |
13 | repositories {
14 | mavenCentral()
15 | google()
16 | }
17 |
18 | dependencies {
19 | getReactNativeDependencies().each { dependency ->
20 | classpath(dependency)
21 | }
22 | }
23 | }
24 |
25 | allprojects {
26 | repositories {
27 | maven {
28 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
29 | url({
30 | def searchDir = rootDir.toPath()
31 | do {
32 | def p = searchDir.resolve("node_modules/react-native/android")
33 | if (p.toFile().exists()) {
34 | return p.toRealPath().toString()
35 | }
36 | } while (searchDir = searchDir.getParent())
37 | throw new GradleException("Could not find `react-native`");
38 | }())
39 | }
40 | mavenCentral()
41 | google()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the Gradle Daemon. The setting is
11 | # particularly useful for configuring JVM memory settings for build performance.
12 | # This does not affect the JVM settings for the Gradle client VM.
13 | # The default is `-Xmx512m -XX:MaxMetaspaceSize=256m`.
14 | org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
15 |
16 | # When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute
17 | # projects in parallel. To learn more about parallel task execution, see the
18 | # section on Gradle build performance:
19 | # https://docs.gradle.org/current/userguide/performance.html#parallel_execution.
20 | # Default is `false`.
21 | #org.gradle.parallel=true
22 |
23 | # AndroidX package structure to make it clearer which packages are bundled with the
24 | # Android operating system, and which are packaged with your app's APK
25 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
26 | android.useAndroidX=true
27 | # Automatically convert third-party libraries to use AndroidX
28 | android.enableJetifier=true
29 | # Jetifier randomly fails on these libraries
30 | android.jetifier.ignorelist=hermes-android
31 |
32 | # Use this property to specify which architecture you want to build.
33 | # You can also override it from the CLI using
34 | # ./gradlew -PreactNativeArchitectures=x86_64
35 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
36 |
37 | # Use this property to enable support to the new architecture.
38 | # This will allow you to use TurboModules and the Fabric render in
39 | # your application. You should enable this flag either if you want
40 | # to write custom TurboModules/Fabric components OR use libraries that
41 | # are providing them.
42 | # Note that this is incompatible with web debugging.
43 | #newArchEnabled=true
44 | #bridgelessEnabled=true
45 |
46 | # Uncomment the line below to build React Native from source.
47 | #react.buildFromSource=true
48 |
49 | # Version of Android NDK to build against.
50 | #ANDROID_NDK_VERSION=26.1.10909125
51 |
52 | # Version of Kotlin to build against.
53 | #KOTLIN_VERSION=1.8.22
54 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-native-menu/menu/f629f1cc4ddca75b5d08c24684b947d4a7066b3f/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | mavenCentral()
5 | google()
6 | }
7 | }
8 |
9 | rootProject.name = "MenuExample"
10 |
11 | apply(from: {
12 | def searchDir = rootDir.toPath()
13 | do {
14 | def p = searchDir.resolve("node_modules/react-native-test-app/test-app.gradle")
15 | if (p.toFile().exists()) {
16 | return p.toRealPath().toString()
17 | }
18 | } while (searchDir = searchDir.getParent())
19 | throw new GradleException("Could not find `react-native-test-app`");
20 | }())
21 | applyTestAppSettings(settings)
22 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MenuExample",
3 | "displayName": "MenuExample",
4 | "components": [
5 | {
6 | "appKey": "MenuExample",
7 | "displayName": "MenuExample"
8 | }
9 | ],
10 | "resources": {
11 | "android": ["dist/res", "dist/main.android.jsbundle"],
12 | "ios": ["dist/assets", "dist/main.ios.jsbundle"],
13 | "macos": ["dist/assets", "dist/main.macos.jsbundle"],
14 | "visionos": ["dist/assets", "dist/main.visionos.jsbundle"],
15 | "windows": ["dist/assets", "dist/main.windows.bundle"]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["module:@react-native/babel-preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from "react-native";
2 | import { App } from "./src/App";
3 | import { name as appName } from "./app.json";
4 |
5 | AppRegistry.registerComponent(appName, () => App);
6 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | ws_dir = Pathname.new(__dir__)
2 | ws_dir = ws_dir.parent until
3 | File.exist?("#{ws_dir}/node_modules/react-native-test-app/test_app.rb") ||
4 | ws_dir.expand_path.to_s == '/'
5 | require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb"
6 |
7 | workspace 'MenuExample.xcworkspace'
8 |
9 | use_test_app!
10 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - boost (1.83.0)
3 | - DoubleConversion (1.1.6)
4 | - FBLazyVector (0.73.6)
5 | - FBReactNativeSpec (0.73.6):
6 | - RCT-Folly (= 2022.05.16.00)
7 | - RCTRequired (= 0.73.6)
8 | - RCTTypeSafety (= 0.73.6)
9 | - React-Core (= 0.73.6)
10 | - React-jsi (= 0.73.6)
11 | - ReactCommon/turbomodule/core (= 0.73.6)
12 | - fmt (6.2.1)
13 | - glog (0.3.5)
14 | - RCT-Folly (2022.05.16.00):
15 | - boost
16 | - DoubleConversion
17 | - fmt (~> 6.2.1)
18 | - glog
19 | - RCT-Folly/Default (= 2022.05.16.00)
20 | - RCT-Folly/Default (2022.05.16.00):
21 | - boost
22 | - DoubleConversion
23 | - fmt (~> 6.2.1)
24 | - glog
25 | - RCT-Folly/Fabric (2022.05.16.00):
26 | - boost
27 | - DoubleConversion
28 | - fmt (~> 6.2.1)
29 | - glog
30 | - RCTRequired (0.73.6)
31 | - RCTTypeSafety (0.73.6):
32 | - FBLazyVector (= 0.73.6)
33 | - RCTRequired (= 0.73.6)
34 | - React-Core (= 0.73.6)
35 | - React (0.73.6):
36 | - React-Core (= 0.73.6)
37 | - React-Core/DevSupport (= 0.73.6)
38 | - React-Core/RCTWebSocket (= 0.73.6)
39 | - React-RCTActionSheet (= 0.73.6)
40 | - React-RCTAnimation (= 0.73.6)
41 | - React-RCTBlob (= 0.73.6)
42 | - React-RCTImage (= 0.73.6)
43 | - React-RCTLinking (= 0.73.6)
44 | - React-RCTNetwork (= 0.73.6)
45 | - React-RCTSettings (= 0.73.6)
46 | - React-RCTText (= 0.73.6)
47 | - React-RCTVibration (= 0.73.6)
48 | - React-callinvoker (0.73.6)
49 | - React-Codegen (0.73.6):
50 | - DoubleConversion
51 | - FBReactNativeSpec
52 | - glog
53 | - RCT-Folly
54 | - RCTRequired
55 | - RCTTypeSafety
56 | - React-Core
57 | - React-jsc
58 | - React-jsi
59 | - React-jsiexecutor
60 | - React-NativeModulesApple
61 | - React-rncore
62 | - ReactCommon/turbomodule/bridging
63 | - ReactCommon/turbomodule/core
64 | - React-Core (0.73.6):
65 | - glog
66 | - RCT-Folly (= 2022.05.16.00)
67 | - React-Core/Default (= 0.73.6)
68 | - React-cxxreact
69 | - React-jsc
70 | - React-jsi
71 | - React-jsiexecutor
72 | - React-perflogger
73 | - React-runtimescheduler
74 | - React-utils
75 | - SocketRocket (= 0.6.1)
76 | - Yoga
77 | - React-Core/CoreModulesHeaders (0.73.6):
78 | - glog
79 | - RCT-Folly (= 2022.05.16.00)
80 | - React-Core/Default
81 | - React-cxxreact
82 | - React-jsc
83 | - React-jsi
84 | - React-jsiexecutor
85 | - React-perflogger
86 | - React-runtimescheduler
87 | - React-utils
88 | - SocketRocket (= 0.6.1)
89 | - Yoga
90 | - React-Core/Default (0.73.6):
91 | - glog
92 | - RCT-Folly (= 2022.05.16.00)
93 | - React-cxxreact
94 | - React-jsc
95 | - React-jsi
96 | - React-jsiexecutor
97 | - React-perflogger
98 | - React-runtimescheduler
99 | - React-utils
100 | - SocketRocket (= 0.6.1)
101 | - Yoga
102 | - React-Core/DevSupport (0.73.6):
103 | - glog
104 | - RCT-Folly (= 2022.05.16.00)
105 | - React-Core/Default (= 0.73.6)
106 | - React-Core/RCTWebSocket (= 0.73.6)
107 | - React-cxxreact
108 | - React-jsc
109 | - React-jsi
110 | - React-jsiexecutor
111 | - React-jsinspector (= 0.73.6)
112 | - React-perflogger
113 | - React-runtimescheduler
114 | - React-utils
115 | - SocketRocket (= 0.6.1)
116 | - Yoga
117 | - React-Core/RCTActionSheetHeaders (0.73.6):
118 | - glog
119 | - RCT-Folly (= 2022.05.16.00)
120 | - React-Core/Default
121 | - React-cxxreact
122 | - React-jsc
123 | - React-jsi
124 | - React-jsiexecutor
125 | - React-perflogger
126 | - React-runtimescheduler
127 | - React-utils
128 | - SocketRocket (= 0.6.1)
129 | - Yoga
130 | - React-Core/RCTAnimationHeaders (0.73.6):
131 | - glog
132 | - RCT-Folly (= 2022.05.16.00)
133 | - React-Core/Default
134 | - React-cxxreact
135 | - React-jsc
136 | - React-jsi
137 | - React-jsiexecutor
138 | - React-perflogger
139 | - React-runtimescheduler
140 | - React-utils
141 | - SocketRocket (= 0.6.1)
142 | - Yoga
143 | - React-Core/RCTBlobHeaders (0.73.6):
144 | - glog
145 | - RCT-Folly (= 2022.05.16.00)
146 | - React-Core/Default
147 | - React-cxxreact
148 | - React-jsc
149 | - React-jsi
150 | - React-jsiexecutor
151 | - React-perflogger
152 | - React-runtimescheduler
153 | - React-utils
154 | - SocketRocket (= 0.6.1)
155 | - Yoga
156 | - React-Core/RCTImageHeaders (0.73.6):
157 | - glog
158 | - RCT-Folly (= 2022.05.16.00)
159 | - React-Core/Default
160 | - React-cxxreact
161 | - React-jsc
162 | - React-jsi
163 | - React-jsiexecutor
164 | - React-perflogger
165 | - React-runtimescheduler
166 | - React-utils
167 | - SocketRocket (= 0.6.1)
168 | - Yoga
169 | - React-Core/RCTLinkingHeaders (0.73.6):
170 | - glog
171 | - RCT-Folly (= 2022.05.16.00)
172 | - React-Core/Default
173 | - React-cxxreact
174 | - React-jsc
175 | - React-jsi
176 | - React-jsiexecutor
177 | - React-perflogger
178 | - React-runtimescheduler
179 | - React-utils
180 | - SocketRocket (= 0.6.1)
181 | - Yoga
182 | - React-Core/RCTNetworkHeaders (0.73.6):
183 | - glog
184 | - RCT-Folly (= 2022.05.16.00)
185 | - React-Core/Default
186 | - React-cxxreact
187 | - React-jsc
188 | - React-jsi
189 | - React-jsiexecutor
190 | - React-perflogger
191 | - React-runtimescheduler
192 | - React-utils
193 | - SocketRocket (= 0.6.1)
194 | - Yoga
195 | - React-Core/RCTSettingsHeaders (0.73.6):
196 | - glog
197 | - RCT-Folly (= 2022.05.16.00)
198 | - React-Core/Default
199 | - React-cxxreact
200 | - React-jsc
201 | - React-jsi
202 | - React-jsiexecutor
203 | - React-perflogger
204 | - React-runtimescheduler
205 | - React-utils
206 | - SocketRocket (= 0.6.1)
207 | - Yoga
208 | - React-Core/RCTTextHeaders (0.73.6):
209 | - glog
210 | - RCT-Folly (= 2022.05.16.00)
211 | - React-Core/Default
212 | - React-cxxreact
213 | - React-jsc
214 | - React-jsi
215 | - React-jsiexecutor
216 | - React-perflogger
217 | - React-runtimescheduler
218 | - React-utils
219 | - SocketRocket (= 0.6.1)
220 | - Yoga
221 | - React-Core/RCTVibrationHeaders (0.73.6):
222 | - glog
223 | - RCT-Folly (= 2022.05.16.00)
224 | - React-Core/Default
225 | - React-cxxreact
226 | - React-jsc
227 | - React-jsi
228 | - React-jsiexecutor
229 | - React-perflogger
230 | - React-runtimescheduler
231 | - React-utils
232 | - SocketRocket (= 0.6.1)
233 | - Yoga
234 | - React-Core/RCTWebSocket (0.73.6):
235 | - glog
236 | - RCT-Folly (= 2022.05.16.00)
237 | - React-Core/Default (= 0.73.6)
238 | - React-cxxreact
239 | - React-jsc
240 | - React-jsi
241 | - React-jsiexecutor
242 | - React-perflogger
243 | - React-runtimescheduler
244 | - React-utils
245 | - SocketRocket (= 0.6.1)
246 | - Yoga
247 | - React-CoreModules (0.73.6):
248 | - RCT-Folly (= 2022.05.16.00)
249 | - RCTTypeSafety (= 0.73.6)
250 | - React-Codegen
251 | - React-Core/CoreModulesHeaders (= 0.73.6)
252 | - React-jsi (= 0.73.6)
253 | - React-NativeModulesApple
254 | - React-RCTBlob
255 | - React-RCTImage (= 0.73.6)
256 | - ReactCommon
257 | - SocketRocket (= 0.6.1)
258 | - React-cxxreact (0.73.6):
259 | - boost (= 1.83.0)
260 | - DoubleConversion
261 | - fmt (~> 6.2.1)
262 | - glog
263 | - RCT-Folly (= 2022.05.16.00)
264 | - React-callinvoker (= 0.73.6)
265 | - React-debug (= 0.73.6)
266 | - React-jsi (= 0.73.6)
267 | - React-jsinspector (= 0.73.6)
268 | - React-logger (= 0.73.6)
269 | - React-perflogger (= 0.73.6)
270 | - React-runtimeexecutor (= 0.73.6)
271 | - React-debug (0.73.6)
272 | - React-Fabric (0.73.6):
273 | - DoubleConversion
274 | - fmt (~> 6.2.1)
275 | - glog
276 | - RCT-Folly/Fabric (= 2022.05.16.00)
277 | - RCTRequired
278 | - RCTTypeSafety
279 | - React-Core
280 | - React-cxxreact
281 | - React-debug
282 | - React-Fabric/animations (= 0.73.6)
283 | - React-Fabric/attributedstring (= 0.73.6)
284 | - React-Fabric/componentregistry (= 0.73.6)
285 | - React-Fabric/componentregistrynative (= 0.73.6)
286 | - React-Fabric/components (= 0.73.6)
287 | - React-Fabric/core (= 0.73.6)
288 | - React-Fabric/imagemanager (= 0.73.6)
289 | - React-Fabric/leakchecker (= 0.73.6)
290 | - React-Fabric/mounting (= 0.73.6)
291 | - React-Fabric/scheduler (= 0.73.6)
292 | - React-Fabric/telemetry (= 0.73.6)
293 | - React-Fabric/templateprocessor (= 0.73.6)
294 | - React-Fabric/textlayoutmanager (= 0.73.6)
295 | - React-Fabric/uimanager (= 0.73.6)
296 | - React-graphics
297 | - React-jsc
298 | - React-jsi
299 | - React-jsiexecutor
300 | - React-logger
301 | - React-rendererdebug
302 | - React-runtimescheduler
303 | - React-utils
304 | - ReactCommon/turbomodule/core
305 | - React-Fabric/animations (0.73.6):
306 | - DoubleConversion
307 | - fmt (~> 6.2.1)
308 | - glog
309 | - RCT-Folly/Fabric (= 2022.05.16.00)
310 | - RCTRequired
311 | - RCTTypeSafety
312 | - React-Core
313 | - React-cxxreact
314 | - React-debug
315 | - React-graphics
316 | - React-jsc
317 | - React-jsi
318 | - React-jsiexecutor
319 | - React-logger
320 | - React-rendererdebug
321 | - React-runtimescheduler
322 | - React-utils
323 | - ReactCommon/turbomodule/core
324 | - React-Fabric/attributedstring (0.73.6):
325 | - DoubleConversion
326 | - fmt (~> 6.2.1)
327 | - glog
328 | - RCT-Folly/Fabric (= 2022.05.16.00)
329 | - RCTRequired
330 | - RCTTypeSafety
331 | - React-Core
332 | - React-cxxreact
333 | - React-debug
334 | - React-graphics
335 | - React-jsc
336 | - React-jsi
337 | - React-jsiexecutor
338 | - React-logger
339 | - React-rendererdebug
340 | - React-runtimescheduler
341 | - React-utils
342 | - ReactCommon/turbomodule/core
343 | - React-Fabric/componentregistry (0.73.6):
344 | - DoubleConversion
345 | - fmt (~> 6.2.1)
346 | - glog
347 | - RCT-Folly/Fabric (= 2022.05.16.00)
348 | - RCTRequired
349 | - RCTTypeSafety
350 | - React-Core
351 | - React-cxxreact
352 | - React-debug
353 | - React-graphics
354 | - React-jsc
355 | - React-jsi
356 | - React-jsiexecutor
357 | - React-logger
358 | - React-rendererdebug
359 | - React-runtimescheduler
360 | - React-utils
361 | - ReactCommon/turbomodule/core
362 | - React-Fabric/componentregistrynative (0.73.6):
363 | - DoubleConversion
364 | - fmt (~> 6.2.1)
365 | - glog
366 | - RCT-Folly/Fabric (= 2022.05.16.00)
367 | - RCTRequired
368 | - RCTTypeSafety
369 | - React-Core
370 | - React-cxxreact
371 | - React-debug
372 | - React-graphics
373 | - React-jsc
374 | - React-jsi
375 | - React-jsiexecutor
376 | - React-logger
377 | - React-rendererdebug
378 | - React-runtimescheduler
379 | - React-utils
380 | - ReactCommon/turbomodule/core
381 | - React-Fabric/components (0.73.6):
382 | - DoubleConversion
383 | - fmt (~> 6.2.1)
384 | - glog
385 | - RCT-Folly/Fabric (= 2022.05.16.00)
386 | - RCTRequired
387 | - RCTTypeSafety
388 | - React-Core
389 | - React-cxxreact
390 | - React-debug
391 | - React-Fabric/components/inputaccessory (= 0.73.6)
392 | - React-Fabric/components/legacyviewmanagerinterop (= 0.73.6)
393 | - React-Fabric/components/modal (= 0.73.6)
394 | - React-Fabric/components/rncore (= 0.73.6)
395 | - React-Fabric/components/root (= 0.73.6)
396 | - React-Fabric/components/safeareaview (= 0.73.6)
397 | - React-Fabric/components/scrollview (= 0.73.6)
398 | - React-Fabric/components/text (= 0.73.6)
399 | - React-Fabric/components/textinput (= 0.73.6)
400 | - React-Fabric/components/unimplementedview (= 0.73.6)
401 | - React-Fabric/components/view (= 0.73.6)
402 | - React-graphics
403 | - React-jsc
404 | - React-jsi
405 | - React-jsiexecutor
406 | - React-logger
407 | - React-rendererdebug
408 | - React-runtimescheduler
409 | - React-utils
410 | - ReactCommon/turbomodule/core
411 | - React-Fabric/components/inputaccessory (0.73.6):
412 | - DoubleConversion
413 | - fmt (~> 6.2.1)
414 | - glog
415 | - RCT-Folly/Fabric (= 2022.05.16.00)
416 | - RCTRequired
417 | - RCTTypeSafety
418 | - React-Core
419 | - React-cxxreact
420 | - React-debug
421 | - React-graphics
422 | - React-jsc
423 | - React-jsi
424 | - React-jsiexecutor
425 | - React-logger
426 | - React-rendererdebug
427 | - React-runtimescheduler
428 | - React-utils
429 | - ReactCommon/turbomodule/core
430 | - React-Fabric/components/legacyviewmanagerinterop (0.73.6):
431 | - DoubleConversion
432 | - fmt (~> 6.2.1)
433 | - glog
434 | - RCT-Folly/Fabric (= 2022.05.16.00)
435 | - RCTRequired
436 | - RCTTypeSafety
437 | - React-Core
438 | - React-cxxreact
439 | - React-debug
440 | - React-graphics
441 | - React-jsc
442 | - React-jsi
443 | - React-jsiexecutor
444 | - React-logger
445 | - React-rendererdebug
446 | - React-runtimescheduler
447 | - React-utils
448 | - ReactCommon/turbomodule/core
449 | - React-Fabric/components/modal (0.73.6):
450 | - DoubleConversion
451 | - fmt (~> 6.2.1)
452 | - glog
453 | - RCT-Folly/Fabric (= 2022.05.16.00)
454 | - RCTRequired
455 | - RCTTypeSafety
456 | - React-Core
457 | - React-cxxreact
458 | - React-debug
459 | - React-graphics
460 | - React-jsc
461 | - React-jsi
462 | - React-jsiexecutor
463 | - React-logger
464 | - React-rendererdebug
465 | - React-runtimescheduler
466 | - React-utils
467 | - ReactCommon/turbomodule/core
468 | - React-Fabric/components/rncore (0.73.6):
469 | - DoubleConversion
470 | - fmt (~> 6.2.1)
471 | - glog
472 | - RCT-Folly/Fabric (= 2022.05.16.00)
473 | - RCTRequired
474 | - RCTTypeSafety
475 | - React-Core
476 | - React-cxxreact
477 | - React-debug
478 | - React-graphics
479 | - React-jsc
480 | - React-jsi
481 | - React-jsiexecutor
482 | - React-logger
483 | - React-rendererdebug
484 | - React-runtimescheduler
485 | - React-utils
486 | - ReactCommon/turbomodule/core
487 | - React-Fabric/components/root (0.73.6):
488 | - DoubleConversion
489 | - fmt (~> 6.2.1)
490 | - glog
491 | - RCT-Folly/Fabric (= 2022.05.16.00)
492 | - RCTRequired
493 | - RCTTypeSafety
494 | - React-Core
495 | - React-cxxreact
496 | - React-debug
497 | - React-graphics
498 | - React-jsc
499 | - React-jsi
500 | - React-jsiexecutor
501 | - React-logger
502 | - React-rendererdebug
503 | - React-runtimescheduler
504 | - React-utils
505 | - ReactCommon/turbomodule/core
506 | - React-Fabric/components/safeareaview (0.73.6):
507 | - DoubleConversion
508 | - fmt (~> 6.2.1)
509 | - glog
510 | - RCT-Folly/Fabric (= 2022.05.16.00)
511 | - RCTRequired
512 | - RCTTypeSafety
513 | - React-Core
514 | - React-cxxreact
515 | - React-debug
516 | - React-graphics
517 | - React-jsc
518 | - React-jsi
519 | - React-jsiexecutor
520 | - React-logger
521 | - React-rendererdebug
522 | - React-runtimescheduler
523 | - React-utils
524 | - ReactCommon/turbomodule/core
525 | - React-Fabric/components/scrollview (0.73.6):
526 | - DoubleConversion
527 | - fmt (~> 6.2.1)
528 | - glog
529 | - RCT-Folly/Fabric (= 2022.05.16.00)
530 | - RCTRequired
531 | - RCTTypeSafety
532 | - React-Core
533 | - React-cxxreact
534 | - React-debug
535 | - React-graphics
536 | - React-jsc
537 | - React-jsi
538 | - React-jsiexecutor
539 | - React-logger
540 | - React-rendererdebug
541 | - React-runtimescheduler
542 | - React-utils
543 | - ReactCommon/turbomodule/core
544 | - React-Fabric/components/text (0.73.6):
545 | - DoubleConversion
546 | - fmt (~> 6.2.1)
547 | - glog
548 | - RCT-Folly/Fabric (= 2022.05.16.00)
549 | - RCTRequired
550 | - RCTTypeSafety
551 | - React-Core
552 | - React-cxxreact
553 | - React-debug
554 | - React-graphics
555 | - React-jsc
556 | - React-jsi
557 | - React-jsiexecutor
558 | - React-logger
559 | - React-rendererdebug
560 | - React-runtimescheduler
561 | - React-utils
562 | - ReactCommon/turbomodule/core
563 | - React-Fabric/components/textinput (0.73.6):
564 | - DoubleConversion
565 | - fmt (~> 6.2.1)
566 | - glog
567 | - RCT-Folly/Fabric (= 2022.05.16.00)
568 | - RCTRequired
569 | - RCTTypeSafety
570 | - React-Core
571 | - React-cxxreact
572 | - React-debug
573 | - React-graphics
574 | - React-jsc
575 | - React-jsi
576 | - React-jsiexecutor
577 | - React-logger
578 | - React-rendererdebug
579 | - React-runtimescheduler
580 | - React-utils
581 | - ReactCommon/turbomodule/core
582 | - React-Fabric/components/unimplementedview (0.73.6):
583 | - DoubleConversion
584 | - fmt (~> 6.2.1)
585 | - glog
586 | - RCT-Folly/Fabric (= 2022.05.16.00)
587 | - RCTRequired
588 | - RCTTypeSafety
589 | - React-Core
590 | - React-cxxreact
591 | - React-debug
592 | - React-graphics
593 | - React-jsc
594 | - React-jsi
595 | - React-jsiexecutor
596 | - React-logger
597 | - React-rendererdebug
598 | - React-runtimescheduler
599 | - React-utils
600 | - ReactCommon/turbomodule/core
601 | - React-Fabric/components/view (0.73.6):
602 | - DoubleConversion
603 | - fmt (~> 6.2.1)
604 | - glog
605 | - RCT-Folly/Fabric (= 2022.05.16.00)
606 | - RCTRequired
607 | - RCTTypeSafety
608 | - React-Core
609 | - React-cxxreact
610 | - React-debug
611 | - React-graphics
612 | - React-jsc
613 | - React-jsi
614 | - React-jsiexecutor
615 | - React-logger
616 | - React-rendererdebug
617 | - React-runtimescheduler
618 | - React-utils
619 | - ReactCommon/turbomodule/core
620 | - Yoga
621 | - React-Fabric/core (0.73.6):
622 | - DoubleConversion
623 | - fmt (~> 6.2.1)
624 | - glog
625 | - RCT-Folly/Fabric (= 2022.05.16.00)
626 | - RCTRequired
627 | - RCTTypeSafety
628 | - React-Core
629 | - React-cxxreact
630 | - React-debug
631 | - React-graphics
632 | - React-jsc
633 | - React-jsi
634 | - React-jsiexecutor
635 | - React-logger
636 | - React-rendererdebug
637 | - React-runtimescheduler
638 | - React-utils
639 | - ReactCommon/turbomodule/core
640 | - React-Fabric/imagemanager (0.73.6):
641 | - DoubleConversion
642 | - fmt (~> 6.2.1)
643 | - glog
644 | - RCT-Folly/Fabric (= 2022.05.16.00)
645 | - RCTRequired
646 | - RCTTypeSafety
647 | - React-Core
648 | - React-cxxreact
649 | - React-debug
650 | - React-graphics
651 | - React-jsc
652 | - React-jsi
653 | - React-jsiexecutor
654 | - React-logger
655 | - React-rendererdebug
656 | - React-runtimescheduler
657 | - React-utils
658 | - ReactCommon/turbomodule/core
659 | - React-Fabric/leakchecker (0.73.6):
660 | - DoubleConversion
661 | - fmt (~> 6.2.1)
662 | - glog
663 | - RCT-Folly/Fabric (= 2022.05.16.00)
664 | - RCTRequired
665 | - RCTTypeSafety
666 | - React-Core
667 | - React-cxxreact
668 | - React-debug
669 | - React-graphics
670 | - React-jsc
671 | - React-jsi
672 | - React-jsiexecutor
673 | - React-logger
674 | - React-rendererdebug
675 | - React-runtimescheduler
676 | - React-utils
677 | - ReactCommon/turbomodule/core
678 | - React-Fabric/mounting (0.73.6):
679 | - DoubleConversion
680 | - fmt (~> 6.2.1)
681 | - glog
682 | - RCT-Folly/Fabric (= 2022.05.16.00)
683 | - RCTRequired
684 | - RCTTypeSafety
685 | - React-Core
686 | - React-cxxreact
687 | - React-debug
688 | - React-graphics
689 | - React-jsc
690 | - React-jsi
691 | - React-jsiexecutor
692 | - React-logger
693 | - React-rendererdebug
694 | - React-runtimescheduler
695 | - React-utils
696 | - ReactCommon/turbomodule/core
697 | - React-Fabric/scheduler (0.73.6):
698 | - DoubleConversion
699 | - fmt (~> 6.2.1)
700 | - glog
701 | - RCT-Folly/Fabric (= 2022.05.16.00)
702 | - RCTRequired
703 | - RCTTypeSafety
704 | - React-Core
705 | - React-cxxreact
706 | - React-debug
707 | - React-graphics
708 | - React-jsc
709 | - React-jsi
710 | - React-jsiexecutor
711 | - React-logger
712 | - React-rendererdebug
713 | - React-runtimescheduler
714 | - React-utils
715 | - ReactCommon/turbomodule/core
716 | - React-Fabric/telemetry (0.73.6):
717 | - DoubleConversion
718 | - fmt (~> 6.2.1)
719 | - glog
720 | - RCT-Folly/Fabric (= 2022.05.16.00)
721 | - RCTRequired
722 | - RCTTypeSafety
723 | - React-Core
724 | - React-cxxreact
725 | - React-debug
726 | - React-graphics
727 | - React-jsc
728 | - React-jsi
729 | - React-jsiexecutor
730 | - React-logger
731 | - React-rendererdebug
732 | - React-runtimescheduler
733 | - React-utils
734 | - ReactCommon/turbomodule/core
735 | - React-Fabric/templateprocessor (0.73.6):
736 | - DoubleConversion
737 | - fmt (~> 6.2.1)
738 | - glog
739 | - RCT-Folly/Fabric (= 2022.05.16.00)
740 | - RCTRequired
741 | - RCTTypeSafety
742 | - React-Core
743 | - React-cxxreact
744 | - React-debug
745 | - React-graphics
746 | - React-jsc
747 | - React-jsi
748 | - React-jsiexecutor
749 | - React-logger
750 | - React-rendererdebug
751 | - React-runtimescheduler
752 | - React-utils
753 | - ReactCommon/turbomodule/core
754 | - React-Fabric/textlayoutmanager (0.73.6):
755 | - DoubleConversion
756 | - fmt (~> 6.2.1)
757 | - glog
758 | - RCT-Folly/Fabric (= 2022.05.16.00)
759 | - RCTRequired
760 | - RCTTypeSafety
761 | - React-Core
762 | - React-cxxreact
763 | - React-debug
764 | - React-Fabric/uimanager
765 | - React-graphics
766 | - React-jsc
767 | - React-jsi
768 | - React-jsiexecutor
769 | - React-logger
770 | - React-rendererdebug
771 | - React-runtimescheduler
772 | - React-utils
773 | - ReactCommon/turbomodule/core
774 | - React-Fabric/uimanager (0.73.6):
775 | - DoubleConversion
776 | - fmt (~> 6.2.1)
777 | - glog
778 | - RCT-Folly/Fabric (= 2022.05.16.00)
779 | - RCTRequired
780 | - RCTTypeSafety
781 | - React-Core
782 | - React-cxxreact
783 | - React-debug
784 | - React-graphics
785 | - React-jsc
786 | - React-jsi
787 | - React-jsiexecutor
788 | - React-logger
789 | - React-rendererdebug
790 | - React-runtimescheduler
791 | - React-utils
792 | - ReactCommon/turbomodule/core
793 | - React-FabricImage (0.73.6):
794 | - DoubleConversion
795 | - fmt (~> 6.2.1)
796 | - glog
797 | - RCT-Folly/Fabric (= 2022.05.16.00)
798 | - RCTRequired (= 0.73.6)
799 | - RCTTypeSafety (= 0.73.6)
800 | - React-Fabric
801 | - React-graphics
802 | - React-ImageManager
803 | - React-jsc
804 | - React-jsi
805 | - React-jsiexecutor (= 0.73.6)
806 | - React-logger
807 | - React-rendererdebug
808 | - React-utils
809 | - ReactCommon
810 | - Yoga
811 | - React-graphics (0.73.6):
812 | - glog
813 | - RCT-Folly/Fabric (= 2022.05.16.00)
814 | - React-Core/Default (= 0.73.6)
815 | - React-utils
816 | - React-ImageManager (0.73.6):
817 | - glog
818 | - RCT-Folly/Fabric
819 | - React-Core/Default
820 | - React-debug
821 | - React-Fabric
822 | - React-graphics
823 | - React-rendererdebug
824 | - React-utils
825 | - React-jsc (0.73.6):
826 | - React-jsc/Fabric (= 0.73.6)
827 | - React-jsi (= 0.73.6)
828 | - React-jsc/Fabric (0.73.6):
829 | - React-jsi (= 0.73.6)
830 | - React-jserrorhandler (0.73.6):
831 | - RCT-Folly/Fabric (= 2022.05.16.00)
832 | - React-debug
833 | - React-jsi
834 | - React-Mapbuffer
835 | - React-jsi (0.73.6):
836 | - boost (= 1.83.0)
837 | - DoubleConversion
838 | - fmt (~> 6.2.1)
839 | - glog
840 | - RCT-Folly (= 2022.05.16.00)
841 | - React-jsiexecutor (0.73.6):
842 | - DoubleConversion
843 | - fmt (~> 6.2.1)
844 | - glog
845 | - RCT-Folly (= 2022.05.16.00)
846 | - React-cxxreact (= 0.73.6)
847 | - React-jsi (= 0.73.6)
848 | - React-perflogger (= 0.73.6)
849 | - React-jsinspector (0.73.6)
850 | - React-logger (0.73.6):
851 | - glog
852 | - React-Mapbuffer (0.73.6):
853 | - glog
854 | - React-debug
855 | - react-native-menu (1.0.3):
856 | - React
857 | - React-nativeconfig (0.73.6)
858 | - React-NativeModulesApple (0.73.6):
859 | - glog
860 | - React-callinvoker
861 | - React-Core
862 | - React-cxxreact
863 | - React-jsc
864 | - React-jsi
865 | - React-runtimeexecutor
866 | - ReactCommon/turbomodule/bridging
867 | - ReactCommon/turbomodule/core
868 | - React-perflogger (0.73.6)
869 | - React-RCTActionSheet (0.73.6):
870 | - React-Core/RCTActionSheetHeaders (= 0.73.6)
871 | - React-RCTAnimation (0.73.6):
872 | - RCT-Folly (= 2022.05.16.00)
873 | - RCTTypeSafety
874 | - React-Codegen
875 | - React-Core/RCTAnimationHeaders
876 | - React-jsi
877 | - React-NativeModulesApple
878 | - ReactCommon
879 | - React-RCTAppDelegate (0.73.6):
880 | - RCT-Folly
881 | - RCTRequired
882 | - RCTTypeSafety
883 | - React-Core
884 | - React-CoreModules
885 | - React-jsc
886 | - React-nativeconfig
887 | - React-NativeModulesApple
888 | - React-RCTFabric
889 | - React-RCTImage
890 | - React-RCTNetwork
891 | - React-runtimescheduler
892 | - ReactCommon
893 | - React-RCTBlob (0.73.6):
894 | - RCT-Folly (= 2022.05.16.00)
895 | - React-Codegen
896 | - React-Core/RCTBlobHeaders
897 | - React-Core/RCTWebSocket
898 | - React-jsi
899 | - React-NativeModulesApple
900 | - React-RCTNetwork
901 | - ReactCommon
902 | - React-RCTFabric (0.73.6):
903 | - glog
904 | - RCT-Folly/Fabric (= 2022.05.16.00)
905 | - React-Core
906 | - React-debug
907 | - React-Fabric
908 | - React-FabricImage
909 | - React-graphics
910 | - React-ImageManager
911 | - React-jsc
912 | - React-jsi
913 | - React-nativeconfig
914 | - React-RCTImage
915 | - React-RCTText
916 | - React-rendererdebug
917 | - React-runtimescheduler
918 | - React-utils
919 | - Yoga
920 | - React-RCTImage (0.73.6):
921 | - RCT-Folly (= 2022.05.16.00)
922 | - RCTTypeSafety
923 | - React-Codegen
924 | - React-Core/RCTImageHeaders
925 | - React-jsi
926 | - React-NativeModulesApple
927 | - React-RCTNetwork
928 | - ReactCommon
929 | - React-RCTLinking (0.73.6):
930 | - React-Codegen
931 | - React-Core/RCTLinkingHeaders (= 0.73.6)
932 | - React-jsi (= 0.73.6)
933 | - React-NativeModulesApple
934 | - ReactCommon
935 | - ReactCommon/turbomodule/core (= 0.73.6)
936 | - React-RCTNetwork (0.73.6):
937 | - RCT-Folly (= 2022.05.16.00)
938 | - RCTTypeSafety
939 | - React-Codegen
940 | - React-Core/RCTNetworkHeaders
941 | - React-jsi
942 | - React-NativeModulesApple
943 | - ReactCommon
944 | - React-RCTSettings (0.73.6):
945 | - RCT-Folly (= 2022.05.16.00)
946 | - RCTTypeSafety
947 | - React-Codegen
948 | - React-Core/RCTSettingsHeaders
949 | - React-jsi
950 | - React-NativeModulesApple
951 | - ReactCommon
952 | - React-RCTText (0.73.6):
953 | - React-Core/RCTTextHeaders (= 0.73.6)
954 | - Yoga
955 | - React-RCTVibration (0.73.6):
956 | - RCT-Folly (= 2022.05.16.00)
957 | - React-Codegen
958 | - React-Core/RCTVibrationHeaders
959 | - React-jsi
960 | - React-NativeModulesApple
961 | - ReactCommon
962 | - React-rendererdebug (0.73.6):
963 | - DoubleConversion
964 | - fmt (~> 6.2.1)
965 | - RCT-Folly (= 2022.05.16.00)
966 | - React-debug
967 | - React-rncore (0.73.6)
968 | - React-runtimeexecutor (0.73.6):
969 | - React-jsi (= 0.73.6)
970 | - React-runtimescheduler (0.73.6):
971 | - glog
972 | - RCT-Folly (= 2022.05.16.00)
973 | - React-callinvoker
974 | - React-cxxreact
975 | - React-debug
976 | - React-jsc
977 | - React-jsi
978 | - React-rendererdebug
979 | - React-runtimeexecutor
980 | - React-utils
981 | - React-utils (0.73.6):
982 | - glog
983 | - RCT-Folly (= 2022.05.16.00)
984 | - React-debug
985 | - ReactCommon (0.73.6):
986 | - React-logger (= 0.73.6)
987 | - ReactCommon/turbomodule (= 0.73.6)
988 | - ReactCommon/turbomodule (0.73.6):
989 | - DoubleConversion
990 | - fmt (~> 6.2.1)
991 | - glog
992 | - RCT-Folly (= 2022.05.16.00)
993 | - React-callinvoker (= 0.73.6)
994 | - React-cxxreact (= 0.73.6)
995 | - React-jsi (= 0.73.6)
996 | - React-logger (= 0.73.6)
997 | - React-perflogger (= 0.73.6)
998 | - ReactCommon/turbomodule/bridging (= 0.73.6)
999 | - ReactCommon/turbomodule/core (= 0.73.6)
1000 | - ReactCommon/turbomodule/bridging (0.73.6):
1001 | - DoubleConversion
1002 | - fmt (~> 6.2.1)
1003 | - glog
1004 | - RCT-Folly (= 2022.05.16.00)
1005 | - React-callinvoker (= 0.73.6)
1006 | - React-cxxreact (= 0.73.6)
1007 | - React-jsi (= 0.73.6)
1008 | - React-logger (= 0.73.6)
1009 | - React-perflogger (= 0.73.6)
1010 | - ReactCommon/turbomodule/core (0.73.6):
1011 | - DoubleConversion
1012 | - fmt (~> 6.2.1)
1013 | - glog
1014 | - RCT-Folly (= 2022.05.16.00)
1015 | - React-callinvoker (= 0.73.6)
1016 | - React-cxxreact (= 0.73.6)
1017 | - React-jsi (= 0.73.6)
1018 | - React-logger (= 0.73.6)
1019 | - React-perflogger (= 0.73.6)
1020 | - ReactNativeHost (0.4.9):
1021 | - glog
1022 | - RCT-Folly (= 2022.05.16.00)
1023 | - React-Core
1024 | - React-cxxreact
1025 | - ReactCommon/turbomodule/core
1026 | - ReactTestApp-DevSupport (3.7.2):
1027 | - React-Core
1028 | - React-jsi
1029 | - ReactTestApp-Resources (1.0.0-dev)
1030 | - SocketRocket (0.6.1)
1031 | - Yoga (1.14.0)
1032 |
1033 | DEPENDENCIES:
1034 | - boost (from `../../node_modules/react-native/third-party-podspecs/boost.podspec`)
1035 | - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
1036 | - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`)
1037 | - FBReactNativeSpec (from `../../node_modules/react-native/React/FBReactNativeSpec`)
1038 | - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
1039 | - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
1040 | - RCT-Folly/Fabric (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
1041 | - RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
1042 | - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
1043 | - React (from `../../node_modules/react-native/`)
1044 | - React-callinvoker (from `../../node_modules/react-native/ReactCommon/callinvoker`)
1045 | - React-Codegen (from `build/generated/ios`)
1046 | - React-Core (from `../../node_modules/react-native/`)
1047 | - React-Core/RCTWebSocket (from `../../node_modules/react-native/`)
1048 | - React-CoreModules (from `../../node_modules/react-native/React/CoreModules`)
1049 | - React-cxxreact (from `../../node_modules/react-native/ReactCommon/cxxreact`)
1050 | - React-debug (from `../../node_modules/react-native/ReactCommon/react/debug`)
1051 | - React-Fabric (from `../../node_modules/react-native/ReactCommon`)
1052 | - React-FabricImage (from `../../node_modules/react-native/ReactCommon`)
1053 | - React-graphics (from `../../node_modules/react-native/ReactCommon/react/renderer/graphics`)
1054 | - React-ImageManager (from `../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)
1055 | - React-jsc (from `../../node_modules/react-native/ReactCommon/jsc`)
1056 | - React-jserrorhandler (from `../../node_modules/react-native/ReactCommon/jserrorhandler`)
1057 | - React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`)
1058 | - React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
1059 | - React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector-modern`)
1060 | - React-logger (from `../../node_modules/react-native/ReactCommon/logger`)
1061 | - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`)
1062 | - react-native-menu (from `../..`)
1063 | - React-nativeconfig (from `../../node_modules/react-native/ReactCommon`)
1064 | - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
1065 | - React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`)
1066 | - React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
1067 | - React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`)
1068 | - React-RCTAppDelegate (from `../../node_modules/react-native/Libraries/AppDelegate`)
1069 | - React-RCTBlob (from `../../node_modules/react-native/Libraries/Blob`)
1070 | - React-RCTFabric (from `../../node_modules/react-native/React`)
1071 | - React-RCTImage (from `../../node_modules/react-native/Libraries/Image`)
1072 | - React-RCTLinking (from `../../node_modules/react-native/Libraries/LinkingIOS`)
1073 | - React-RCTNetwork (from `../../node_modules/react-native/Libraries/Network`)
1074 | - React-RCTSettings (from `../../node_modules/react-native/Libraries/Settings`)
1075 | - React-RCTText (from `../../node_modules/react-native/Libraries/Text`)
1076 | - React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`)
1077 | - React-rendererdebug (from `../../node_modules/react-native/ReactCommon/react/renderer/debug`)
1078 | - React-rncore (from `../../node_modules/react-native/ReactCommon`)
1079 | - React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`)
1080 | - React-runtimescheduler (from `../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
1081 | - React-utils (from `../../node_modules/react-native/ReactCommon/react/utils`)
1082 | - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
1083 | - "ReactNativeHost (from `../../node_modules/@rnx-kit/react-native-host`)"
1084 | - ReactTestApp-DevSupport (from `../../node_modules/react-native-test-app`)
1085 | - ReactTestApp-Resources (from `..`)
1086 | - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`)
1087 |
1088 | SPEC REPOS:
1089 | trunk:
1090 | - fmt
1091 | - SocketRocket
1092 |
1093 | EXTERNAL SOURCES:
1094 | boost:
1095 | :podspec: "../../node_modules/react-native/third-party-podspecs/boost.podspec"
1096 | DoubleConversion:
1097 | :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
1098 | FBLazyVector:
1099 | :path: "../../node_modules/react-native/Libraries/FBLazyVector"
1100 | FBReactNativeSpec:
1101 | :path: "../../node_modules/react-native/React/FBReactNativeSpec"
1102 | glog:
1103 | :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
1104 | RCT-Folly:
1105 | :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
1106 | RCTRequired:
1107 | :path: "../../node_modules/react-native/Libraries/RCTRequired"
1108 | RCTTypeSafety:
1109 | :path: "../../node_modules/react-native/Libraries/TypeSafety"
1110 | React:
1111 | :path: "../../node_modules/react-native/"
1112 | React-callinvoker:
1113 | :path: "../../node_modules/react-native/ReactCommon/callinvoker"
1114 | React-Codegen:
1115 | :path: build/generated/ios
1116 | React-Core:
1117 | :path: "../../node_modules/react-native/"
1118 | React-CoreModules:
1119 | :path: "../../node_modules/react-native/React/CoreModules"
1120 | React-cxxreact:
1121 | :path: "../../node_modules/react-native/ReactCommon/cxxreact"
1122 | React-debug:
1123 | :path: "../../node_modules/react-native/ReactCommon/react/debug"
1124 | React-Fabric:
1125 | :path: "../../node_modules/react-native/ReactCommon"
1126 | React-FabricImage:
1127 | :path: "../../node_modules/react-native/ReactCommon"
1128 | React-graphics:
1129 | :path: "../../node_modules/react-native/ReactCommon/react/renderer/graphics"
1130 | React-ImageManager:
1131 | :path: "../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios"
1132 | React-jsc:
1133 | :path: "../../node_modules/react-native/ReactCommon/jsc"
1134 | React-jserrorhandler:
1135 | :path: "../../node_modules/react-native/ReactCommon/jserrorhandler"
1136 | React-jsi:
1137 | :path: "../../node_modules/react-native/ReactCommon/jsi"
1138 | React-jsiexecutor:
1139 | :path: "../../node_modules/react-native/ReactCommon/jsiexecutor"
1140 | React-jsinspector:
1141 | :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern"
1142 | React-logger:
1143 | :path: "../../node_modules/react-native/ReactCommon/logger"
1144 | React-Mapbuffer:
1145 | :path: "../../node_modules/react-native/ReactCommon"
1146 | react-native-menu:
1147 | :path: "../.."
1148 | React-nativeconfig:
1149 | :path: "../../node_modules/react-native/ReactCommon"
1150 | React-NativeModulesApple:
1151 | :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
1152 | React-perflogger:
1153 | :path: "../../node_modules/react-native/ReactCommon/reactperflogger"
1154 | React-RCTActionSheet:
1155 | :path: "../../node_modules/react-native/Libraries/ActionSheetIOS"
1156 | React-RCTAnimation:
1157 | :path: "../../node_modules/react-native/Libraries/NativeAnimation"
1158 | React-RCTAppDelegate:
1159 | :path: "../../node_modules/react-native/Libraries/AppDelegate"
1160 | React-RCTBlob:
1161 | :path: "../../node_modules/react-native/Libraries/Blob"
1162 | React-RCTFabric:
1163 | :path: "../../node_modules/react-native/React"
1164 | React-RCTImage:
1165 | :path: "../../node_modules/react-native/Libraries/Image"
1166 | React-RCTLinking:
1167 | :path: "../../node_modules/react-native/Libraries/LinkingIOS"
1168 | React-RCTNetwork:
1169 | :path: "../../node_modules/react-native/Libraries/Network"
1170 | React-RCTSettings:
1171 | :path: "../../node_modules/react-native/Libraries/Settings"
1172 | React-RCTText:
1173 | :path: "../../node_modules/react-native/Libraries/Text"
1174 | React-RCTVibration:
1175 | :path: "../../node_modules/react-native/Libraries/Vibration"
1176 | React-rendererdebug:
1177 | :path: "../../node_modules/react-native/ReactCommon/react/renderer/debug"
1178 | React-rncore:
1179 | :path: "../../node_modules/react-native/ReactCommon"
1180 | React-runtimeexecutor:
1181 | :path: "../../node_modules/react-native/ReactCommon/runtimeexecutor"
1182 | React-runtimescheduler:
1183 | :path: "../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler"
1184 | React-utils:
1185 | :path: "../../node_modules/react-native/ReactCommon/react/utils"
1186 | ReactCommon:
1187 | :path: "../../node_modules/react-native/ReactCommon"
1188 | ReactNativeHost:
1189 | :path: "../../node_modules/@rnx-kit/react-native-host"
1190 | ReactTestApp-DevSupport:
1191 | :path: "../../node_modules/react-native-test-app"
1192 | ReactTestApp-Resources:
1193 | :path: ".."
1194 | Yoga:
1195 | :path: "../../node_modules/react-native/ReactCommon/yoga"
1196 |
1197 | SPEC CHECKSUMS:
1198 | boost: d3f49c53809116a5d38da093a8aa78bf551aed09
1199 | DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
1200 | FBLazyVector: f64d1e2ea739b4d8f7e4740cde18089cd97fe864
1201 | FBReactNativeSpec: 133b4c4362bacd8d6527b897ee5e5e3bc90bb075
1202 | fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
1203 | glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
1204 | RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
1205 | RCTRequired: ca1d7414aba0b27efcfa2ccd37637edb1ab77d96
1206 | RCTTypeSafety: 678e344fb976ff98343ca61dc62e151f3a042292
1207 | React: e296bcebb489deaad87326067204eb74145934ab
1208 | React-callinvoker: d0b7015973fa6ccb592bb0363f6bc2164238ab8c
1209 | React-Codegen: 26596f3dc3269bed13220f0609f778fcc93b50dd
1210 | React-Core: 1d1f8ef65353751bf57d5c6f01a2bf3fbe644df8
1211 | React-CoreModules: 558228e12cddb9ca00ff7937894cc5104a21be6b
1212 | React-cxxreact: 92db3068083c5790656277d5f1ef56c615cd1177
1213 | React-debug: d444db402065cca460d9c5b072caab802a04f729
1214 | React-Fabric: 433731b157b8f63aa0cac89327ece388ca1748a0
1215 | React-FabricImage: 01a6990e58926aebbd13ea075cb6e9b70b022840
1216 | React-graphics: 5500206f7c9a481456365403c9fcf1638de108b7
1217 | React-ImageManager: df193215ff3cf1a8dad297e554c89c632e42436c
1218 | React-jsc: 423fa1db16947e4dffb83c72f6fe4248913b0e00
1219 | React-jserrorhandler: a4d0f541c5852cf031db2f82f51de90be55b1334
1220 | React-jsi: 730ad8dffa69e4196361e10cddc89fd8a9d32202
1221 | React-jsiexecutor: e9d8ef2051048ae7d32c7da38c2c0c22cfa3d7f3
1222 | React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066
1223 | React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec
1224 | React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab
1225 | react-native-menu: 2e368669c7375dd1049ef3c7b2cab190ebe45d9e
1226 | React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f
1227 | React-NativeModulesApple: ae99dc0e80c9027f54572c45635449fbdc36e4f1
1228 | React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2
1229 | React-RCTActionSheet: 37edf35aeb8e4f30e76c82aab61f12d1b75c04ec
1230 | React-RCTAnimation: a69de7f3daa8462743094f4736c455e844ea63f7
1231 | React-RCTAppDelegate: d25d143b0918fb0614201f2b5d56eca1679d8462
1232 | React-RCTBlob: 4ae95e6ba3508771b8fa0fedb64df07aa561b548
1233 | React-RCTFabric: d792d7bad2238d67a33dd7ee0f2e32de6eaf669f
1234 | React-RCTImage: a0bfe87b6908c7b76bd7d74520f40660bd0ad881
1235 | React-RCTLinking: 5f10be1647952cceddfa1970fdb374087582fc34
1236 | React-RCTNetwork: a0bc3dd45a2dc7c879c80cebb6f9707b2c8bbed6
1237 | React-RCTSettings: 28c202b68afa59afb4067510f2c69c5a530fb9e3
1238 | React-RCTText: 4119d9e53ca5db9502b916e1b146e99798986d21
1239 | React-RCTVibration: 55bd7c48487eb9a2562f2bd3fdc833274f5b0636
1240 | React-rendererdebug: 5fa97ba664806cee4700e95aec42dff1b6f8ea36
1241 | React-rncore: e15b68c32138fded0519efc1b156f9716175954c
1242 | React-runtimeexecutor: bb328dbe2865f3a550df0240df8e2d8c3aaa4c57
1243 | React-runtimescheduler: 9daefa990db62f8874bc9d6e7e504272c6b6c57f
1244 | React-utils: d16c1d2251c088ad817996621947d0ac8167b46c
1245 | ReactCommon: 447281ad2034ea3252bf81a60d1f77d5afb0b636
1246 | ReactNativeHost: 8d42a784a88c420acba96197e779d0f8ac467e04
1247 | ReactTestApp-DevSupport: f845db38b4b4ce8d341f8acdba934ee85ed3d7b2
1248 | ReactTestApp-Resources: 857244f3a23f2b3157b364fa06cf3e8866deff9c
1249 | SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
1250 | Yoga: d17d2cc8105eed528474683b42e2ea310e1daf61
1251 |
1252 | PODFILE CHECKSUM: ca281f1ce39458080aeb99104e385e550148264c
1253 |
1254 | COCOAPODS: 1.14.3
1255 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Button, Platform, StyleSheet, Text, View } from "react-native";
3 | import { MenuView, type MenuComponentRef } from "@react-native-menu/menu";
4 | import { useRef } from "react";
5 |
6 | export const App = () => {
7 | const [themeVariant] = React.useState("light");
8 | const menuRef = useRef(null);
9 |
10 | return (
11 |
12 |
180 | );
181 | };
182 |
183 | const styles = StyleSheet.create({
184 | container: {
185 | flex: 1,
186 | alignItems: "center",
187 | justifyContent: "center",
188 | gap: 10,
189 | },
190 | button: {
191 | height: 100,
192 | width: 100,
193 | backgroundColor: "red",
194 | borderRadius: 50,
195 | justifyContent: "center",
196 | alignItems: "center",
197 | },
198 | buttonText: { color: "white" },
199 | });
200 |
--------------------------------------------------------------------------------
/example/visionos/Podfile:
--------------------------------------------------------------------------------
1 | ws_dir = Pathname.new(__dir__)
2 | ws_dir = ws_dir.parent until
3 | File.exist?("#{ws_dir}/node_modules/react-native-test-app/visionos/test_app.rb") ||
4 | ws_dir.expand_path.to_s == '/'
5 | require "#{ws_dir}/node_modules/react-native-test-app/visionos/test_app.rb"
6 |
7 | workspace 'MenuExample.xcworkspace'
8 |
9 | use_test_app!
10 |
--------------------------------------------------------------------------------
/ios/Menu-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import
4 |
--------------------------------------------------------------------------------
/ios/MenuViewManager.mm:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "RCTBridge.h"
4 |
5 | #ifdef RCT_NEW_ARCH_ENABLED
6 | // NEW ARCH
7 | #import "MenuView.h"
8 | #else
9 | // OLD ARCH
10 | #if __has_include()
11 | #import
12 | #else
13 | #import
14 | #endif
15 |
16 | #endif
17 |
18 | @interface MenuViewManager : RCTViewManager
19 | @end
20 |
21 | @implementation MenuViewManager
22 |
23 | RCT_EXPORT_MODULE(MenuView)
24 |
25 | - (UIView *)view
26 | {
27 | #ifdef RCT_NEW_ARCH_ENABLED
28 | // NEW ARCH
29 | return [[MenuView alloc] init];
30 | #else
31 | // OLD ARCH
32 | if (@available(iOS 14.0, *)) {
33 | return [[LegacyMenuViewImplementation alloc] init];
34 | } else {
35 | return [[LegacyActionSheetView alloc] init];
36 | }
37 | #endif /* RCT_NEW_ARCH_ENABLED */
38 | }
39 |
40 | /**
41 | * title: Short description to be displayed above the menu.
42 | */
43 | RCT_EXPORT_VIEW_PROPERTY(title, NSString);
44 | /**
45 | * actions: Array of actions that are included in the menu
46 | */
47 | RCT_EXPORT_VIEW_PROPERTY(actions, NSArray);
48 | /**
49 | * actionsHash: String hash that changes any time the actions change (so that we don't have to deeply compare values)
50 | */
51 | #ifdef RCT_NEW_ARCH_ENABLED
52 | // NEW ARCH
53 | RCT_EXPORT_VIEW_PROPERTY(actionsHash, NSString);
54 | #endif
55 |
56 | /**
57 | * onPressAction: callback to be called once user selects an action
58 | */
59 | RCT_EXPORT_VIEW_PROPERTY(onPressAction, RCTDirectEventBlock);
60 | /**
61 | * onCloseMenu: callback to be called when the menu is closed
62 | */
63 | RCT_EXPORT_VIEW_PROPERTY(onCloseMenu, RCTDirectEventBlock);
64 | /**
65 | * onOpenMenu: callback to be called when the menu is opened
66 | */
67 | RCT_EXPORT_VIEW_PROPERTY(onOpenMenu, RCTDirectEventBlock);
68 | /**
69 | * shouldOpenOnLongPress: determines whether menu should be opened after long press or normal press
70 | */
71 | RCT_EXPORT_VIEW_PROPERTY(shouldOpenOnLongPress, BOOL)
72 | /**
73 | * themeVariant: determines whether menu should use dark theme, light theme or system theme
74 | */
75 | RCT_EXPORT_VIEW_PROPERTY(themeVariant, NSString)
76 |
77 | /**
78 | * hitSlop: The same as hitSlop in React Native
79 | */
80 | RCT_EXPORT_VIEW_PROPERTY(hitSlop, UIEdgeInsets)
81 |
82 | @end
83 |
--------------------------------------------------------------------------------
/ios/NewArch/FabricActionSheetView.swift:
--------------------------------------------------------------------------------
1 | @objc(FabricActionSheetView)
2 | public class FabricActionSheetView: ActionSheetView, FabricViewImplementationProtocol {
3 | public var onPressAction: ((String) -> Void)?
4 | public var onCloseMenu: (() -> Void)?
5 | public var onOpenMenu: (() -> Void)?
6 |
7 | @objc override func sendButtonAction(_ action: String) {
8 | if let onPress = onPressAction {
9 | onPress(action)
10 | }
11 | }
12 |
13 | @objc override func sendMenuClose() {
14 | if let onCloseMenu = onCloseMenu {
15 | onCloseMenu()
16 | }
17 | }
18 | @objc override func sendMenuOpen() {
19 | if let onOpenMenu = onOpenMenu {
20 | onOpenMenu()
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/NewArch/FabricMenuViewImplementation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuViewImplementation.swift
3 | // react-native-menu
4 | //
5 | // Created by Jesse Katsumata on 11/3/20.
6 | //
7 |
8 | import UIKit
9 | @available(iOS 14.0, *)
10 | @objc(FabricMenuViewImplementation)
11 | public class FabricMenuViewImplementation: MenuViewImplementation, FabricViewImplementationProtocol {
12 | public var onPressAction: ((String) -> Void)?
13 | public var onCloseMenu: (() -> Void)?
14 | public var onOpenMenu: (() -> Void)?
15 |
16 | @objc override func sendButtonAction(_ action: UIAction) {
17 | if let onPress = onPressAction {
18 | onPress(action.identifier.rawValue)
19 | }
20 | }
21 |
22 | @objc override func sendMenuClose() {
23 | if let onCloseMenu = onCloseMenu {
24 | onCloseMenu()
25 | }
26 | }
27 |
28 | @objc override func sendMenuOpen() {
29 | if let onOpenMenu = onOpenMenu {
30 | onOpenMenu()
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/ios/NewArch/FabricViewImplementationProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | @objc public protocol FabricViewImplementationProtocol {
3 | var actions: [NSDictionary]? { get set }
4 | var title: NSString? { get set }
5 | var themeVariant: NSString? { get set }
6 | var shouldOpenOnLongPress: Bool { get set }
7 | @objc optional var hitSlop: UIEdgeInsets { get set }
8 | var onPressAction: ((String) -> Void)? { get set }
9 | var onCloseMenu: (() -> Void)? { get set }
10 | var onOpenMenu: (() -> Void)? { get set }
11 | }
12 |
--------------------------------------------------------------------------------
/ios/NewArch/MenuView.h:
--------------------------------------------------------------------------------
1 | // This guard prevent this file to be compiled in the old architecture.
2 |
3 | #ifdef RCT_NEW_ARCH_ENABLED
4 | #import
5 | #import
6 |
7 | #ifndef MenuViewNativeComponent_h
8 | #define MenuViewNativeComponent_h
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @protocol FabricViewImplementationProtocol;
13 | @interface MenuView : RCTViewComponentView
14 | @end
15 |
16 | NS_ASSUME_NONNULL_END
17 |
18 | #endif /* MenuViewNativeComponent_h */
19 | #endif /* RCT_NEW_ARCH_ENABLED */
20 |
--------------------------------------------------------------------------------
/ios/NewArch/MenuView.mm:
--------------------------------------------------------------------------------
1 | #ifdef RCT_NEW_ARCH_ENABLED
2 | #if __has_include()
3 | #import
4 | #else
5 | #import
6 | #endif
7 | #import "MenuView.h"
8 |
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import "RCTFabricComponentsPlugins.h"
14 |
15 | using namespace facebook::react;
16 |
17 | @interface MenuView ()
18 |
19 | @end
20 |
21 | @implementation MenuView {
22 | UIView * _view;
23 | }
24 |
25 | + (ComponentDescriptorProvider)componentDescriptorProvider
26 | {
27 | return concreteComponentDescriptorProvider();
28 | }
29 |
30 | - (instancetype)initWithFrame:(CGRect)frame
31 | {
32 | if (self = [super initWithFrame:frame]) {
33 | static const auto defaultProps = std::make_shared();
34 | _props = defaultProps;
35 |
36 | if (@available(iOS 14.0, *)) {
37 | _view = [[FabricMenuViewImplementation alloc] init];
38 | } else {
39 | _view = [[FabricActionSheetView alloc] init];
40 | }
41 | _view.onPressAction = ^(NSString *eventString) {
42 | [self onPressAction:eventString];
43 | };
44 | _view.onCloseMenu = ^{
45 | [self onCloseMenu];
46 | };
47 | self.contentView = _view;
48 | }
49 |
50 | return self;
51 | }
52 |
53 | - (std::shared_ptr)getEventEmitter
54 | {
55 | if (!self->_eventEmitter) {
56 | return nullptr;
57 | }
58 |
59 | assert(std::dynamic_pointer_cast(self->_eventEmitter));
60 | return std::static_pointer_cast(self->_eventEmitter);
61 | }
62 |
63 | - (void)onPressAction:(NSString * _Nonnull)eventString {
64 | // If screen is already unmounted then there will be no event emitter
65 | const auto eventEmitter = [self getEventEmitter];
66 | if (eventEmitter != nullptr) {
67 |
68 | eventEmitter->onPressAction(MenuViewEventEmitter::OnPressAction{
69 | .event = [eventString UTF8String]
70 | });
71 | }
72 | }
73 |
74 | - (void)onCloseMenu {
75 | // If screen is already unmounted then there will be no event emitter
76 | const auto eventEmitter = [self getEventEmitter];
77 | if (eventEmitter != nullptr) {
78 | eventEmitter->onCloseMenu({});
79 | }
80 | }
81 |
82 | - (void)onOpenMenu {
83 | // If screen is already unmounted then there will be no event emitter
84 | const auto eventEmitter = [self getEventEmitter];
85 | if (eventEmitter != nullptr) {
86 | eventEmitter->onOpenMenu({});
87 | }
88 | }
89 |
90 | /**
91 | Responsible for iterating through the C++ vector and convert each struct element to NSDictionary, then return it all in an NSArray
92 | */
93 | - (NSMutableArray *) convertActionsToObjC: (std::vector) actions {
94 | if (actions.size() == 0) {
95 | return @[];
96 | }
97 | NSMutableArray *actionsArray = [NSMutableArray arrayWithCapacity:actions.size()];
98 | for (const MenuViewActionsStruct &action : actions) {
99 | // first convert the subactions if they exist
100 | NSMutableArray *subactionsArray = [NSMutableArray arrayWithCapacity:actions.size()];
101 | if (action.subactions.size() > 0) {
102 | for (const MenuViewActionsSubactionsStruct &subaction : action.subactions) {
103 | NSDictionary *subactionDict = @{
104 | @"id": [NSString stringWithUTF8String:subaction.id.c_str()],
105 | @"title": [NSString stringWithUTF8String:subaction.title.c_str()],
106 | @"titleColor": @(subaction.titleColor),
107 | @"subtitle": [NSString stringWithUTF8String:subaction.subtitle.c_str()],
108 | @"state": [NSString stringWithUTF8String:subaction.state.c_str()],
109 | @"image": [NSString stringWithUTF8String:subaction.image.c_str()],
110 | @"imageColor": @(subaction.imageColor),
111 | @"displayInline": @(subaction.displayInline),
112 | @"attributes": @{
113 | @"destructive": @(subaction.attributes.destructive),
114 | @"disabled": @(subaction.attributes.disabled),
115 | @"hidden": @(subaction.attributes.hidden),
116 | },
117 | };
118 | [subactionsArray addObject:subactionDict];
119 | }
120 | }
121 |
122 | // then convert the full actions object
123 | NSDictionary *actionDict = @{
124 | @"id": [NSString stringWithUTF8String:action.id.c_str()],
125 | @"title": [NSString stringWithUTF8String:action.title.c_str()],
126 | @"titleColor": @(action.titleColor),
127 | @"subtitle": [NSString stringWithUTF8String:action.subtitle.c_str()],
128 | @"state": [NSString stringWithUTF8String:action.state.c_str()],
129 | @"image": [NSString stringWithUTF8String:action.image.c_str()],
130 | @"imageColor": @(action.imageColor),
131 | @"displayInline": @(action.displayInline),
132 | @"attributes": @{
133 | @"destructive": @(action.attributes.destructive),
134 | @"disabled": @(action.attributes.disabled),
135 | @"hidden": @(action.attributes.hidden),
136 | },
137 | @"subactions": subactionsArray,
138 | };
139 |
140 | [actionsArray addObject:actionDict];
141 | }
142 | return actionsArray;
143 | }
144 |
145 | - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
146 | {
147 | const auto &oldViewProps = *std::static_pointer_cast(_props);
148 | const auto &newViewProps = *std::static_pointer_cast(props);
149 |
150 |
151 | if (oldViewProps.actionsHash != newViewProps.actionsHash) {
152 | _view.actions = [self convertActionsToObjC: newViewProps.actions];
153 | }
154 |
155 | if (oldViewProps.title != newViewProps.title) {
156 | _view.title = [NSString stringWithUTF8String:newViewProps.title.c_str()];
157 | }
158 |
159 | if (oldViewProps.themeVariant != newViewProps.themeVariant) {
160 | _view.themeVariant = [NSString stringWithUTF8String:newViewProps.themeVariant.c_str()];
161 | }
162 |
163 | if (oldViewProps.shouldOpenOnLongPress != newViewProps.shouldOpenOnLongPress) {
164 | _view.shouldOpenOnLongPress = newViewProps.shouldOpenOnLongPress;
165 | }
166 |
167 | if (oldViewProps.hitSlop.top != newViewProps.hitSlop.top ||
168 | oldViewProps.hitSlop.bottom != newViewProps.hitSlop.bottom ||
169 | oldViewProps.hitSlop.left != newViewProps.hitSlop.left ||
170 | oldViewProps.hitSlop.right != newViewProps.hitSlop.right) {
171 | _view.hitSlop = UIEdgeInsetsMake(
172 | newViewProps.hitSlop.top,
173 | newViewProps.hitSlop.left,
174 | newViewProps.hitSlop.bottom,
175 | newViewProps.hitSlop.right
176 | );
177 | }
178 |
179 | [super updateProps:props oldProps:oldProps];
180 | }
181 |
182 | Class MenuViewCls(void)
183 | {
184 | return MenuView.class;
185 | }
186 |
187 | @end
188 | #endif
189 |
--------------------------------------------------------------------------------
/ios/OldArch/LegacyActionSheetView.swift:
--------------------------------------------------------------------------------
1 | @objc(LegacyActionSheetView)
2 | public class LegacyActionSheetView: ActionSheetView {
3 | @objc var onPressAction: RCTDirectEventBlock?
4 | @objc var onCloseMenu: RCTDirectEventBlock?
5 | @objc var onOpenMenu: RCTDirectEventBlock?
6 |
7 |
8 |
9 | @objc override func sendButtonAction(_ action: String) {
10 | if let onPress = onPressAction {
11 | onPress(["event":action])
12 | }
13 | }
14 |
15 | @objc override func sendMenuClose() {
16 | if let onCloseMenu = onCloseMenu {
17 | onCloseMenu([:])
18 | }
19 | }
20 |
21 | @objc override func sendMenuOpen() {
22 | if let onOpenMenu = onOpenMenu {
23 | onOpenMenu([:])
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/OldArch/LegacyMenuViewImplementation.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | @available(iOS 14.0, *)
3 | @objc(LegacyMenuViewImplementation)
4 | public class LegacyMenuViewImplementation: MenuViewImplementation {
5 | @objc var onPressAction: RCTDirectEventBlock?
6 | @objc var onCloseMenu: RCTDirectEventBlock?
7 | @objc var onOpenMenu: RCTDirectEventBlock?
8 |
9 | @objc override func sendButtonAction(_ action: UIAction) {
10 | if let onPress = onPressAction {
11 | onPress(["event":action.identifier.rawValue])
12 | }
13 | }
14 |
15 | @objc override func sendMenuClose() {
16 | if let onCloseMenu = onCloseMenu {
17 | onCloseMenu([:])
18 | }
19 | }
20 |
21 | @objc override func sendMenuOpen() {
22 | if let onOpenMenu = onOpenMenu {
23 | onOpenMenu([:])
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/ios/Shared/ActionSheetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuView.swift
3 | // react-native-menu
4 | //
5 | // Created by Jesse Katsumata on 11/3/20.
6 | //
7 |
8 | import UIKit
9 |
10 |
11 | @objc(ActionSheetView)
12 | public class ActionSheetView: UIView {
13 |
14 | private var _title: String?
15 | @objc public var title: NSString? {
16 | didSet { self._title = title as? String }
17 | }
18 |
19 | private var _actions: [UIAlertAction] = []
20 | @objc public var actions: [NSDictionary]? {
21 | didSet {
22 | guard let actions = self.actions else {
23 | return
24 | }
25 | _actions.removeAll()
26 | actions.forEach({ alertAction in
27 | if let action = RCTAlertAction(details: alertAction).createAction({
28 | event in self.sendButtonAction(event)
29 | self.sendMenuClose()
30 | }) {
31 | _actions.append(action)
32 | }
33 | })
34 | }
35 | }
36 |
37 | @objc public var shouldOpenOnLongPress: Bool = false
38 |
39 | private var _themeVariant: String?
40 | @objc public var themeVariant: NSString? {
41 | didSet { self._themeVariant = themeVariant as? String }
42 | }
43 |
44 | override init(frame: CGRect) {
45 | super.init(frame: frame)
46 | let tap = UITapGestureRecognizer(target: self, action: #selector (self.handleTap (_:)))
47 | let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress(_:)))
48 | self.addGestureRecognizer(tap)
49 | self.addGestureRecognizer(longPress)
50 | }
51 |
52 | func launchActionSheet() {
53 | self.sendMenuOpen()
54 |
55 | let alert = UIAlertController(title: _title, message: nil, preferredStyle: .actionSheet)
56 |
57 | if #available(iOS 13.0, *) {
58 | if self._themeVariant != nil {
59 | if self._themeVariant == "dark" {
60 | alert.overrideUserInterfaceStyle = .dark
61 | } else if self._themeVariant == "light" {
62 | alert.overrideUserInterfaceStyle = .light
63 | } else {
64 | alert.overrideUserInterfaceStyle = .unspecified
65 | }
66 | }
67 | }
68 |
69 | self._actions.forEach({action in
70 | alert.addAction(action.copy() as! UIAlertAction)
71 | })
72 |
73 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
74 | self.sendMenuClose()
75 | }))
76 |
77 | if UIDevice.current.userInterfaceIdiom == .pad {
78 | alert.modalPresentationStyle = .popover
79 | alert.popoverPresentationController?.sourceView = self
80 | alert.popoverPresentationController?.sourceRect = self.bounds
81 | }
82 |
83 | if let root = RCTPresentedViewController() {
84 | root.present(alert, animated: true, completion: nil)
85 | }
86 |
87 | }
88 |
89 |
90 | @objc func handleTap(_ sender:UITapGestureRecognizer) {
91 | if shouldOpenOnLongPress {
92 | return
93 | }
94 | if sender.state == .ended {
95 | DispatchQueue.main.async {
96 | self.launchActionSheet()
97 | }
98 | }
99 | }
100 |
101 | @objc func handleLongPress(_ sender: UILongPressGestureRecognizer) {
102 | if !shouldOpenOnLongPress {
103 | return
104 | }
105 | if sender.state == .ended {
106 | DispatchQueue.main.async {
107 | self.launchActionSheet()
108 | }
109 | }
110 | }
111 |
112 | @objc func sendButtonAction(_ action: String) {
113 | // NO-OP (should be overriden by parent)
114 | }
115 |
116 | @objc func sendMenuClose() {
117 | // NO-OP (should be overriden by parent)
118 | }
119 |
120 | @objc func sendMenuOpen() {
121 | // NO-OP (should be overriden by parent)
122 | }
123 |
124 | required init?(coder: NSCoder) {
125 | fatalError("init(coder:) has not been implemented")
126 | }
127 |
128 | public override func reactSetFrame(_ frame: CGRect) {
129 | super.reactSetFrame(frame)
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/ios/Shared/MenuViewImplementation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuViewImplementation.swift
3 | // react-native-menu
4 | //
5 | // Created by Jesse Katsumata on 11/3/20.
6 | //
7 |
8 | import UIKit
9 | @available(iOS 14.0, *)
10 | @objc(MenuViewImplementation)
11 | public class MenuViewImplementation: UIButton {
12 |
13 | @objc public var actions: [NSDictionary]? {
14 | didSet {
15 | guard let actions = self.actions else {
16 | return
17 | }
18 | _actions.removeAll()
19 | actions.forEach { menuAction in
20 | _actions.append(RCTMenuAction(details: menuAction).createUIMenuElement({action in self.sendButtonAction(action)}))
21 | }
22 | self.setup()
23 | }
24 | }
25 |
26 | private var _actions: [UIMenuElement] = [];
27 |
28 | private var _title: String = "";
29 | @objc public var title: NSString? {
30 | didSet {
31 | guard let title = self.title else {
32 | return
33 | }
34 | self._title = title as String
35 | self.setup()
36 | }
37 | }
38 |
39 | @objc public var shouldOpenOnLongPress: Bool = false {
40 | didSet {
41 | self.setup()
42 | }
43 | }
44 |
45 | private var _themeVariant: String?
46 | @objc public var themeVariant: NSString? {
47 | didSet {
48 | self._themeVariant = themeVariant as? String
49 | self.setup()
50 | }
51 | }
52 |
53 | @objc public var hitSlop: UIEdgeInsets = .zero
54 |
55 | override init(frame: CGRect) {
56 | super.init(frame: frame)
57 | let interaction = UIContextMenuInteraction(delegate: self)
58 | self.addInteraction(interaction)
59 | self.setup()
60 | }
61 |
62 | public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
63 | sendMenuOpen()
64 | return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
65 | guard let self = self else { return nil }
66 | return self.menu
67 | }
68 | }
69 |
70 | public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
71 | sendMenuClose()
72 | }
73 |
74 | func setup () {
75 | let menu = UIMenu(title: _title,
76 | identifier: nil,
77 | children: self._actions)
78 |
79 | if self._themeVariant != nil {
80 | if self._themeVariant == "dark" {
81 | self.overrideUserInterfaceStyle = .dark
82 | } else if self._themeVariant == "light" {
83 | self.overrideUserInterfaceStyle = .light
84 | } else {
85 | self.overrideUserInterfaceStyle = .unspecified
86 | }
87 | }
88 |
89 | self.menu = menu
90 | self.showsMenuAsPrimaryAction = !shouldOpenOnLongPress
91 | }
92 |
93 | public override func reactSetFrame(_ frame: CGRect) {
94 | super.reactSetFrame(frame);
95 | }
96 |
97 | public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
98 | if hitSlop == .zero || !self.isEnabled || self.isHidden {
99 | return super.point(inside: point, with: event)
100 | }
101 |
102 | let largerFrame = CGRect(
103 | x: self.bounds.origin.x - hitSlop.left,
104 | y: self.bounds.origin.y - hitSlop.top,
105 | width: self.bounds.size.width + hitSlop.left + hitSlop.right,
106 | height: self.bounds.size.height + hitSlop.top + hitSlop.bottom
107 | )
108 |
109 | return largerFrame.contains(point)
110 | }
111 |
112 | required init?(coder aDecoder: NSCoder) {
113 | fatalError("init(coder:) has not been implemented")
114 | }
115 |
116 | @objc func sendButtonAction(_ action: UIAction) {
117 | // NO-OP (should be overriden by parent)
118 | }
119 |
120 | @objc func sendMenuClose() {
121 | // NO-OP (should be overriden by parent)
122 | }
123 |
124 | @objc func sendMenuOpen() {
125 | // NO-OP (should be overriden by parent)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/ios/Shared/RCTAlertAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCTAlertAction.swift
3 | // react-native-menu
4 | //
5 | // Created by Jesse Katsumata on 11/9/20.
6 | //
7 |
8 | import UIKit;
9 |
10 | class RCTAlertAction {
11 |
12 | var identifier: String?;
13 | var title: String;
14 | var image: UIImage?
15 | var style: UIAlertAction.Style = .default
16 | var isEnabled: Bool = true
17 | var isHidden: Bool = false
18 |
19 | init(details: NSDictionary){
20 |
21 | if let identifier = details["id"] as? NSString {
22 | self.identifier = identifier as String;
23 | }
24 |
25 | if let title = details["title"] as? NSString {
26 | self.title = title as String;
27 | } else {
28 | self.title = "";
29 | }
30 |
31 | if let image = details["image"] as? NSString {
32 | if #available(iOS 13.0, *) {
33 | self.image = UIImage(systemName: image as String)
34 | if let imageColor = details["imageColor"] {
35 | self.image = self.image?.withTintColor(RCTConvert.uiColor(imageColor), renderingMode: .alwaysOriginal)
36 | }
37 | } else {
38 | self.image = UIImage(named: image as String)
39 | };
40 | }
41 |
42 | if let attributes = details["attributes"] as? NSDictionary {
43 | if (attributes["destructive"] as? Bool) == true {
44 | self.style = .destructive
45 | }
46 | if (attributes["disabled"] as? Bool) == true {
47 | self.isEnabled = false
48 | }
49 | if (attributes["hidden"] as? Bool == true){
50 | self.isHidden = true
51 | }
52 | }
53 |
54 | }
55 |
56 | func createAction(_ handleEvent: @escaping ((String) -> Void)) -> UIAlertAction? {
57 | if self.isHidden {
58 | return nil
59 | }
60 | let action = UIAlertAction(title: title, style: style, handler: {_ in
61 | handleEvent(self.identifier ?? self.title)
62 | })
63 | if self.image != nil {
64 | action.setValue(self.image, forKey: "image")
65 | }
66 | action.isEnabled = self.isEnabled
67 |
68 | return action
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/ios/Shared/RCTMenuItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RCTMenuItem.swift
3 | // react-native-menu
4 | //
5 | // Created by Jesse Katsumata on 11/8/20.
6 | //
7 |
8 | import UIKit;
9 |
10 | @available(iOS 13.0, *)
11 | class RCTMenuAction {
12 |
13 | var identifier: UIAction.Identifier?;
14 | var title: String;
15 | var subtitle: String?
16 | var displayInline: Bool
17 | var image: UIImage?
18 | var attributes: UIAction.Attributes = []
19 | var state: UIAction.State = .off
20 | var subactions: [RCTMenuAction] = []
21 | // Only available in iOS 16+
22 | var preferredElementSizeString: String?
23 |
24 | init(details: NSDictionary){
25 |
26 | if let identifier = details["id"] as? NSString {
27 | self.identifier = UIAction.Identifier(rawValue: identifier as String);
28 | }
29 |
30 | if let image = details["image"] as? NSString {
31 | self.image = UIImage(systemName: image as String);
32 | if self.image === nil {
33 | self.image = UIImage(named: image as String)
34 | }
35 | if let imageColor = details["imageColor"] {
36 | self.image = self.image?.withTintColor(RCTConvert.uiColor(imageColor), renderingMode: .alwaysOriginal)
37 | }
38 | }
39 |
40 | if let title = details["title"] as? NSString {
41 | self.title = title as String;
42 | } else {
43 | self.title = "";
44 | }
45 |
46 | if let subtitle = details["subtitle"] as? NSString {
47 | self.subtitle = subtitle as String;
48 | }
49 |
50 | if let displayInline = details["displayInline"] as? Bool {
51 | self.displayInline = displayInline as Bool;
52 | } else {
53 | self.displayInline = false;
54 | }
55 |
56 | if let attributes = details["attributes"] as? NSDictionary {
57 | if (attributes["destructive"] as? Bool) == true {
58 | self.attributes.update(with: .destructive)
59 | }
60 | if (attributes["disabled"] as? Bool) == true {
61 | self.attributes.update(with: .disabled)
62 | }
63 | if (attributes["hidden"] as? Bool) == true {
64 | self.attributes.update(with: .hidden)
65 | }
66 | if(attributes["keepsMenuPresented"] as? Bool) == true {
67 | if #available(iOS 16.0, *) {
68 | self.attributes.update(with: .keepsMenuPresented)
69 | }
70 | }
71 | }
72 |
73 | if let state = details["state"] as? NSString {
74 | if state=="on" {
75 | self.state = .on
76 | }
77 | if state=="off" {
78 | self.state = .off
79 | }
80 | if state=="mixed" {
81 | self.state = .mixed
82 | }
83 | }
84 |
85 | if let subactions = details["subactions"] as? NSArray {
86 | if subactions.count > 0 {
87 | for subaction in subactions {
88 | self.subactions.append(RCTMenuAction(details: subaction as! NSDictionary))
89 | }
90 | }
91 | }
92 |
93 | if let preferredElementSizeString = details["preferredElementSize"] as? String {
94 | self.preferredElementSizeString = preferredElementSizeString
95 | }
96 |
97 | }
98 |
99 | func createUIMenuElement(_ handler: @escaping UIActionHandler) -> UIMenuElement {
100 | if subactions.count > 0 {
101 | var subMenuActions: [UIMenuElement] = []
102 | subactions.forEach { subaction in
103 | subMenuActions.append(subaction.createUIMenuElement(handler))
104 | }
105 | var menu: UIMenu;
106 | if self.displayInline {
107 | menu = UIMenu(title: title, image: image, options: .displayInline, children: subMenuActions)
108 | } else {
109 | menu = UIMenu(title: title, image: image, children: subMenuActions)
110 | }
111 |
112 | if #available(iOS 16.0, *) {
113 | if(preferredElementSizeString == "small") {
114 | menu.preferredElementSize = .small
115 | } else if(preferredElementSizeString == "medium") {
116 | menu.preferredElementSize = .medium
117 | } else if(preferredElementSizeString == "large") {
118 | menu.preferredElementSize = .large
119 | }
120 | }
121 |
122 | return menu
123 | }
124 |
125 | if #available(iOS 15, *) {
126 | return UIAction(title: title, subtitle: subtitle, image: image, identifier: identifier, attributes: attributes, state: state, handler: handler)
127 | } else {
128 | return UIAction(title: title, image: image, identifier: identifier, discoverabilityTitle: subtitle, attributes: attributes, state: state, handler: handler)
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require("node:path");
2 |
3 | const { makeMetroConfig } = require("@rnx-kit/metro-config");
4 | module.exports = makeMetroConfig({
5 | projectRoot: path.join(__dirname, "example"),
6 | watchFolders: [__dirname],
7 | resolver: {
8 | extraNodeModules: {
9 | "@react-native-menu/menu": __dirname,
10 | },
11 | },
12 | transformer: {
13 | getTransformOptions: async () => ({
14 | transform: {
15 | experimentalImportSupport: false,
16 | inlineRequires: false,
17 | },
18 | }),
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-native-menu/menu",
3 | "version": "1.2.3",
4 | "description": "UIMenu component for react-native",
5 | "main": "lib/commonjs/index",
6 | "module": "lib/module/index",
7 | "types": "lib/typescript/src/index.d.ts",
8 | "react-native": "src/index",
9 | "source": "src/index",
10 | "files": [
11 | "src",
12 | "lib",
13 | "android",
14 | "ios",
15 | "cpp",
16 | "react-native-menu.podspec",
17 | "!lib/typescript/example",
18 | "!**/__tests__",
19 | "!**/__fixtures__",
20 | "!**/__mocks__"
21 | ],
22 | "scripts": {
23 | "android": "react-native run-android",
24 | "bootstrap": "yarn && yarn pods",
25 | "format": "biome format --write .",
26 | "ios": "react-native run-ios",
27 | "lint": "biome lint --write .",
28 | "pods": "cd example && pod-install --quiet",
29 | "prepare": "bob build",
30 | "release": "release-it",
31 | "start": "react-native start",
32 | "test": "jest",
33 | "typescript": "tsc --noEmit"
34 | },
35 | "keywords": [
36 | "react-native",
37 | "ios",
38 | "android"
39 | ],
40 | "repository": "https://github.com/react-native-menu/menu",
41 | "author": "Jesse Katsumata (https://github.com/Naturalclar)",
42 | "license": "MIT",
43 | "bugs": {
44 | "url": "https://github.com/react-native-menu/menu/issues"
45 | },
46 | "homepage": "https://github.com/react-native-menu/menu#readme",
47 | "devDependencies": {
48 | "@biomejs/biome": "1.9.4",
49 | "@callstack/react-native-visionos": "^0.73.0",
50 | "@react-native/babel-preset": "^0.78.1",
51 | "@react-native/metro-config": "0.78.1",
52 | "@release-it/conventional-changelog": "^10.0.0",
53 | "@rnx-kit/metro-config": "^2.0.0",
54 | "@types/jest": "^29.1.2",
55 | "@types/react": "^18.2.2",
56 | "@types/react-native": "0.73.0",
57 | "jest": "^29.2.1",
58 | "pod-install": "^0.3.2",
59 | "react": "18.2.0",
60 | "react-native": "^0.73.0",
61 | "react-native-builder-bob": "^0.38.0",
62 | "react-native-test-app": "^4.0.4",
63 | "release-it": "^18.1.1",
64 | "typescript": "5.8.2"
65 | },
66 | "peerDependencies": {
67 | "react": "*",
68 | "react-native": "*"
69 | },
70 | "jest": {
71 | "preset": "react-native",
72 | "modulePathIgnorePatterns": [
73 | "/example/node_modules",
74 | "/lib/"
75 | ]
76 | },
77 | "release-it": {
78 | "git": {
79 | "commitMessage": "chore: release ${version}",
80 | "tagName": "v${version}"
81 | },
82 | "npm": {
83 | "publish": true
84 | },
85 | "github": {
86 | "release": true
87 | },
88 | "plugins": {
89 | "@release-it/conventional-changelog": {
90 | "preset": "angular"
91 | }
92 | }
93 | },
94 | "react-native-builder-bob": {
95 | "source": "src",
96 | "output": "lib",
97 | "targets": [
98 | "commonjs",
99 | "module",
100 | "typescript"
101 | ]
102 | },
103 | "publishConfig": {
104 | "access": "public"
105 | },
106 | "volta": {
107 | "node": "20.10.0",
108 | "yarn": "1.22.21"
109 | },
110 | "codegenConfig": {
111 | "name": "RNMenuViewSpec",
112 | "type": "components",
113 | "jsSrcsDir": "src"
114 | },
115 | "packageManager": "yarn@4.1.1"
116 | }
117 |
--------------------------------------------------------------------------------
/plugin/withAndroidDrawables.js:
--------------------------------------------------------------------------------
1 | const { withDangerousMod } = require('@expo/config-plugins');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | /**
6 | * This config plugin copies files from the specified paths, like the assets
7 | * directory, to the Android drawable directory.
8 | */
9 | function withAndroidDrawables(config, { drawableFiles }) {
10 | return withDangerousMod(config, [
11 | 'android',
12 | (config) => {
13 | // Validate drawableFiles
14 | if (!Array.isArray(drawableFiles)) {
15 | throw new Error('drawableFiles must be an array of file paths');
16 | }
17 |
18 | // Define the target drawable directory
19 | const drawableDir = path.join(
20 | config.modRequest.projectRoot,
21 | 'android/app/src/main/res/drawable',
22 | );
23 |
24 | // Create the drawable directory if it doesn't exist
25 | if (!fs.existsSync(drawableDir)) {
26 | fs.mkdirSync(drawableDir, { recursive: true });
27 | }
28 |
29 | // Copy each drawable file to the drawable directory
30 | drawableFiles.forEach((filePath) => {
31 | const sourcePath = path.resolve(config.modRequest.projectRoot, filePath);
32 | const fileName = path.basename(filePath);
33 | const destPath = path.join(drawableDir, fileName);
34 |
35 | if (!fs.existsSync(sourcePath)) {
36 | console.warn(`Warning: Drawable file not found: ${sourcePath}`);
37 | return;
38 | }
39 |
40 | // Copy the file
41 | fs.copyFileSync(sourcePath, destPath);
42 | });
43 |
44 | return config;
45 | },
46 | ]);
47 | }
48 |
49 | module.exports = withAndroidDrawables;
50 |
--------------------------------------------------------------------------------
/react-native-menu.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5 |
6 | Pod::Spec.new do |s|
7 | s.name = "react-native-menu"
8 | s.version = package["version"]
9 | s.summary = package["description"]
10 | s.homepage = package["homepage"]
11 | s.license = package["license"]
12 | s.authors = package["author"]
13 |
14 | s.platforms = { :ios => "11.0", :visionos => "1.0"}
15 | s.source = { :git => "https://github.com/react-native-menu/menu.git", :tag => "#{s.version}" }
16 |
17 | s.source_files = "ios/**/*.{h,m,mm,swift}"
18 |
19 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then # new architecture
20 | # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
21 | # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
22 | if respond_to?(:install_modules_dependencies, true)
23 | install_modules_dependencies(s)
24 | else
25 | s.dependency "React-Core"
26 | # Don't install the dependencies when we run `pod install` in the old architecture.
27 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
28 | s.pod_target_xcconfig = {
29 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
30 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
31 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
32 | }
33 | s.dependency "React-RCTFabric"
34 | s.dependency "React-Codegen"
35 | s.dependency "RCT-Folly"
36 | s.dependency "RCTRequired"
37 | s.dependency "RCTTypeSafety"
38 | s.dependency "ReactCommon/turbomodule/core"
39 | end
40 | else # old arch
41 | s.dependency "React"
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/react-native.config.js:
--------------------------------------------------------------------------------
1 | const project = (() => {
2 | const path = require("node:path");
3 | try {
4 | const { configureProjects } = require("react-native-test-app");
5 |
6 | return configureProjects({
7 | android: {
8 | sourceDir: path.join("example", "android"),
9 | manifestPath: path.join(__dirname, "example", "android"),
10 | },
11 | ios: {
12 | sourceDir: path.join("example", "ios"),
13 | },
14 | });
15 | } catch (e) {
16 | return undefined;
17 | }
18 | })();
19 |
20 | module.exports = {
21 | dependencies: {
22 | // Help rn-cli find and autolink this library
23 | "@react-native-menu/menu": {
24 | root: __dirname,
25 | },
26 | },
27 | ...(project ? { project } : undefined),
28 | };
29 |
--------------------------------------------------------------------------------
/src/NativeModuleSpecs/UIMenuNativeComponent.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | DirectEventHandler,
3 | Int32,
4 | } from "react-native/Libraries/Types/CodegenTypes";
5 | import type { HostComponent, ViewProps } from "react-native";
6 | import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";
7 | /*
8 | Caution, those below are not just typescript types.
9 | Codegen is using them to create the corresponding C++ data types.
10 |
11 | Codegen doesn't play very well with reusing the same type within a type,
12 | OR with extending types in an interface, so for now we'll just keep some duplicate
13 | types here, to avoid issues while `pod install` takes place.
14 | */
15 |
16 | type SubAction = {
17 | id?: string;
18 | title: string;
19 | titleColor?: Int32;
20 | subtitle?: string;
21 | state?: string;
22 | image?: string;
23 | imageColor?: Int32;
24 | displayInline?: boolean;
25 | attributes?: {
26 | destructive?: boolean;
27 | disabled?: boolean;
28 | hidden?: boolean;
29 | };
30 | };
31 | type MenuAction = {
32 | id?: string;
33 | title: string;
34 | titleColor?: Int32;
35 | subtitle?: string;
36 | state?: string;
37 | image?: string;
38 | imageColor?: Int32;
39 | displayInline?: boolean;
40 | attributes?: {
41 | destructive?: boolean;
42 | disabled?: boolean;
43 | hidden?: boolean;
44 | };
45 | subactions?: Array;
46 | };
47 | export interface NativeProps extends ViewProps {
48 | onPressAction?: DirectEventHandler<{ event: string }>;
49 | onCloseMenu?: DirectEventHandler<{ event: string }>;
50 | onOpenMenu?: DirectEventHandler<{ event: string }>;
51 | actions: Array;
52 | actionsHash: string; // just a workaround to make sure we don't have to manually compare MenuActions manually in C++ (since it's a struct and that's a pain)
53 | title?: string;
54 | themeVariant?: string;
55 | shouldOpenOnLongPress?: boolean;
56 | hitSlop: {
57 | top: Int32;
58 | bottom: Int32;
59 | left: Int32;
60 | right: Int32;
61 | };
62 | }
63 |
64 | export default codegenNativeComponent(
65 | "MenuView",
66 | ) as HostComponent;
67 |
--------------------------------------------------------------------------------
/src/UIMenuView.android.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | findNodeHandle,
3 | type HostComponent,
4 | requireNativeComponent,
5 | UIManager,
6 | } from "react-native";
7 | import type { MenuComponentRef, NativeMenuComponentProps } from "./types";
8 | import { forwardRef, useImperativeHandle, useRef } from "react";
9 |
10 | const NativeMenuComponent = requireNativeComponent(
11 | "MenuView",
12 | ) as HostComponent;
13 |
14 | const MenuComponent = forwardRef(
15 | (props, ref) => {
16 | const nativeRef = useRef(null);
17 |
18 | useImperativeHandle(
19 | ref,
20 | () => ({
21 | show: () => {
22 | if (nativeRef.current) {
23 | const node = findNodeHandle(nativeRef.current);
24 | const command =
25 | UIManager.getViewManagerConfig("MenuView").Commands.show;
26 |
27 | UIManager.dispatchViewManagerCommand(node, command, undefined);
28 | }
29 | },
30 | }),
31 | [],
32 | );
33 |
34 | return ;
35 | },
36 | );
37 |
38 | export default MenuComponent;
39 |
--------------------------------------------------------------------------------
/src/UIMenuView.ios.tsx:
--------------------------------------------------------------------------------
1 | import UIMenuNativeComponent from "./NativeModuleSpecs/UIMenuNativeComponent";
2 |
3 | export default UIMenuNativeComponent;
4 |
--------------------------------------------------------------------------------
/src/UIMenuView.tsx:
--------------------------------------------------------------------------------
1 | import type * as React from "react";
2 | import { View } from "react-native";
3 | import type { NativeMenuComponentProps } from "./types";
4 |
5 | /**
6 | * TODO: implement for Web
7 | */
8 | const MenuView: React.FC> = ({
9 | style,
10 | children,
11 | testID,
12 | }) => {
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | };
19 |
20 | export default MenuView;
21 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo("write a test");
2 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, useMemo } from "react";
2 | import { processColor } from "react-native";
3 |
4 | import UIMenuView from "./UIMenuView";
5 | import type {
6 | MenuComponentProps,
7 | MenuAction,
8 | ProcessedMenuAction,
9 | NativeActionEvent,
10 | MenuComponentRef,
11 | } from "./types";
12 | import { objectHash } from "./utils";
13 |
14 | function processAction(action: MenuAction): ProcessedMenuAction {
15 | return {
16 | ...action,
17 | imageColor: processColor(action.imageColor),
18 | titleColor: processColor(action.titleColor),
19 | subactions: action.subactions?.map((subAction) => processAction(subAction)),
20 | };
21 | }
22 |
23 | const defaultHitslop = { top: 0, left: 0, bottom: 0, right: 0 };
24 |
25 | const MenuView = forwardRef(
26 | ({ actions, hitSlop = defaultHitslop, ...props }, ref) => {
27 | const processedActions = actions.map((action) =>
28 | processAction(action),
29 | );
30 | const hash = useMemo(() => {
31 | return objectHash(processedActions);
32 | }, [processedActions]);
33 |
34 | return (
35 |
42 | );
43 | },
44 | );
45 |
46 | export { MenuView };
47 | export type {
48 | MenuComponentProps,
49 | MenuComponentRef,
50 | MenuAction,
51 | NativeActionEvent,
52 | };
53 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | ColorValue,
3 | processColor,
4 | StyleProp,
5 | ViewStyle,
6 | } from "react-native";
7 | import type * as React from "react";
8 |
9 | export type NativeActionEvent = {
10 | nativeEvent: {
11 | event: string;
12 | };
13 | };
14 |
15 | type MenuAttributes = {
16 | /**
17 | * An attribute indicating the destructive style.
18 | */
19 | destructive?: boolean;
20 | /**
21 | * An attribute indicating the disabled style.
22 | */
23 | disabled?: boolean;
24 | /**
25 | * An attribute indicating the hidden style.
26 | */
27 | hidden?: boolean;
28 | /**
29 | * (iOS16+ only)
30 | * @platform iOS
31 | * An attribute indicating that the menu should remain presented after firing.
32 | */
33 | keepsMenuPresented?: boolean;
34 | };
35 |
36 | /**
37 | * The state of the action.
38 | * - off: A constant indicating the menu element is in the “off” state.
39 | * - on: A constant indicating the menu element is in the “on” state.
40 | * - mixed: A constant indicating the menu element is in the “mixed” state.
41 | */
42 | type MenuState = "off" | "on" | "mixed";
43 |
44 | export type MenuAction = {
45 | /**
46 | * Identifier of the menu action.
47 | * The value set in this id will be returned when menu is selected.
48 | */
49 | id?: string;
50 | /**
51 | * The action's title.
52 | */
53 | title: string;
54 | /**
55 | * (Android only)
56 | * The action's title color.
57 | * @platform Android
58 | */
59 | titleColor?: number | ColorValue;
60 | /**
61 | * (iOS14+ only)
62 | * An elaborated title that explains the purpose of the action.
63 | * @platform iOS
64 | */
65 | subtitle?: string;
66 | /**
67 | * The attributes indicating the style of the action.
68 | */
69 | attributes?: MenuAttributes;
70 | /**
71 | * (iOS14+ only)
72 | * The state of the action.
73 | * @platform iOS
74 | */
75 | state?: MenuState;
76 | /**
77 | * (Android and iOS13+ only)
78 | * - The action's image.
79 | * - Allows icon name included in project or system (Android) resources drawables and
80 | * in SF Symbol (iOS)
81 | * @example // (iOS)
82 | * image="plus"
83 | * @example // (Android)
84 | * image="ic_menu_add"
85 | * - TODO: Allow images other than those included in SF Symbol and resources drawables
86 | */
87 | image?: string;
88 | /**
89 | * (Android and iOS13+ only)
90 | * - The action's image color.
91 | */
92 | imageColor?: number | ColorValue;
93 | /**
94 | * (Android and iOS14+ only)
95 | * - Actions to be displayed in the sub menu
96 | * - On Android it does not support nesting next sub menus in sub menu item
97 | */
98 | subactions?: MenuAction[];
99 | /**
100 | * Whether subactions should be inline (separated by divider) or nested (sub menu)
101 | */
102 | displayInline?: boolean;
103 | /**
104 | * (iOS 16+ only)
105 | * The preferred size of this menu's child elements.
106 | * @platform iOS
107 | */
108 | preferredElementSize?: "small" | "medium" | "large";
109 | };
110 |
111 | type MenuComponentPropsBase = {
112 | style?: StyleProp;
113 | /**
114 | * Callback function that will be called when selecting a menu item.
115 | * It will contain id of the given action.
116 | */
117 | onPressAction?: ({ nativeEvent }: NativeActionEvent) => void;
118 | /**
119 | * Callback function that will be called when the menu closes.
120 | */
121 | onCloseMenu?: () => void;
122 | /**
123 | * Callback function that will be called when the menu opens.
124 | */
125 | onOpenMenu?: () => void;
126 | /**
127 | * Actions to be displayed in the menu.
128 | */
129 | actions: MenuAction[];
130 | /**
131 | * The title of the menu.
132 | */
133 | title?: string;
134 |
135 | /**
136 | * (Android API 23+)
137 | * Boolean value determines whether popup menu should be anchored
138 | * to right corner of parent view - default value is `false`
139 | * @platform Android
140 | */
141 | isAnchoredToRight?: boolean;
142 | /**
143 | * Determines if menu should open after long press or on normal press
144 | *
145 | * @default false
146 | */
147 | shouldOpenOnLongPress?: boolean;
148 | /**
149 | * Overrides theme variant of menu to light mode, dark mode or system theme
150 | * (Only support iOS for now)
151 | *
152 | * @platform iOS
153 | */
154 | themeVariant?: string;
155 | /**
156 | * Custom OpenSpace hitSlop prop. Works like touchable hitslop.
157 | * @platform iOS
158 | */
159 | hitSlop?: {
160 | top: number;
161 | bottom: number;
162 | left: number;
163 | right: number;
164 | };
165 | /**
166 | * Test ID for testing purposes
167 | */
168 | testID?: string;
169 | };
170 |
171 | export type MenuComponentProps =
172 | React.PropsWithChildren;
173 |
174 | export type MenuComponentRef = {
175 | show: () => void;
176 | };
177 |
178 | export type ProcessedMenuAction = Omit<
179 | MenuAction,
180 | "imageColor" | "titleColor" | "subactions"
181 | > & {
182 | imageColor: ReturnType;
183 | titleColor: ReturnType;
184 | subactions?: ProcessedMenuAction[];
185 | };
186 |
187 | export type NativeMenuComponentProps = {
188 | style?: StyleProp;
189 | onPressAction?: ({ nativeEvent }: NativeActionEvent) => void;
190 | onCloseMenu?: () => void;
191 | onOpenMenu?: () => void;
192 | actions: ProcessedMenuAction[];
193 | actionsHash: string;
194 | title?: string;
195 | hitSlop?: MenuComponentProps["hitSlop"];
196 | testID?: string;
197 | ref?: React.ForwardedRef;
198 | };
199 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | // Function to calculate a simple hash code for a string
2 | function hashCode(str: string): number {
3 | let hash = 0;
4 | for (let i = 0; i < str.length; i++) {
5 | const char = str.charCodeAt(i);
6 | // eslint-disable-next-line no-bitwise
7 | hash = (hash << 5) - hash + char;
8 | }
9 | return hash;
10 | }
11 |
12 | // Function to create a string hash from a TypeScript object
13 | export function objectHash(obj: Record[]): string {
14 | if (!obj) {
15 | return "";
16 | }
17 | // Convert the object to a JSON string
18 | const jsonString = JSON.stringify(obj);
19 |
20 | // Calculate the hash code of the JSON string
21 | const hash = hashCode(jsonString);
22 |
23 | // Convert the hash code to a string (optional)
24 | return String(hash);
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@react-native-menu/menu": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react-jsx",
12 | "lib": ["esnext"],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitReturns": true,
17 | "noImplicitUseStrict": false,
18 | "noStrictGenericChecks": false,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "resolveJsonModule": true,
22 | "skipLibCheck": true,
23 | "strict": true,
24 | "target": "esnext"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------