{"id":51101002,"url":"https://github.com/thegruber/react-native-halo-menu","last_synced_at":"2026-06-24T10:32:12.668Z","repository":{"id":364372607,"uuid":"1267625370","full_name":"thegruber/react-native-halo-menu","owner":"thegruber","description":"Pinterest-style hold menu for React Native — long-press, drag, release. Radial action menu powered by Reanimated 4 and Gesture Handler, zero native code.","archived":false,"fork":false,"pushed_at":"2026-06-12T18:46:14.000Z","size":11967,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T20:20:47.891Z","etag":null,"topics":["android","context-menu","expo","gesture-handler","ios","long-press","quick-actions","radial-menu","react-native","reanimated","typescript","worklets"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-native-halo-menu","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thegruber.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-12T18:00:27.000Z","updated_at":"2026-06-12T19:43:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thegruber/react-native-halo-menu","commit_stats":null,"previous_names":["thegruber/react-native-halo-menu"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/thegruber/react-native-halo-menu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thegruber%2Freact-native-halo-menu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thegruber%2Freact-native-halo-menu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thegruber%2Freact-native-halo-menu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thegruber%2Freact-native-halo-menu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thegruber","download_url":"https://codeload.github.com/thegruber/react-native-halo-menu/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thegruber%2Freact-native-halo-menu/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34726701,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","context-menu","expo","gesture-handler","ios","long-press","quick-actions","radial-menu","react-native","reanimated","typescript","worklets"],"created_at":"2026-06-24T10:32:09.290Z","updated_at":"2026-06-24T10:32:12.652Z","avatar_url":"https://github.com/thegruber.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-native-halo-menu\n\n[![CI](https://github.com/thegruber/react-native-halo-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/thegruber/react-native-halo-menu/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/react-native-halo-menu.svg)](https://www.npmjs.com/package/react-native-halo-menu)\n[![npm downloads](https://img.shields.io/npm/dm/react-native-halo-menu.svg)](https://www.npmjs.com/package/react-native-halo-menu)\n[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![Expo](https://img.shields.io/badge/Expo-compatible-4630EB.svg)](https://expo.dev)\n\n**Pinterest-style hold menu for React Native: long-press, drag, release.** A radial action menu powered by Reanimated 4 and Gesture Handler. Zero native code, compatible with Expo and bare React Native.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/thegruber/react-native-halo-menu/main/docs/assets/halo-menu-showcase.webp\" width=\"300\" alt=\"Long-press a card: it lifts above a blurred backdrop while radial action buttons fan out around the finger; dragging onto a button selects it.\" /\u003e\n  \u003cbr /\u003e\n  \u003csub\u003e60\u0026nbsp;fps, captured on a real iPhone. \u003ca href=\"https://github.com/thegruber/react-native-halo-menu/raw/main/docs/assets/halo-menu-showcase.mp4\"\u003eFull-quality MP4\u003c/a\u003e.\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003e Long-press a card → it lifts and tilts above a dimmed backdrop while an arc of action buttons fans out around your finger → drag onto a button → release to trigger. One continuous gesture.\n\n- 🫳 **One gesture**: open, browse, and select without lifting your finger\n- 🪂 **Levitating preview**: the pressed element lifts, tilts toward the screen edge, and casts a real shadow while everything behind dims\n- 🏎️ **Everything on the UI thread**: per-frame finger tracking, hit-testing, and button placement run as Reanimated worklets\n- 🎨 **Themeable without forking**: colors, motion, layout, backdrop, icons, and shadows are all injection points with sensible defaults\n- 📳 **Bring your own haptics**: `onOpen` and `onHover` callbacks take `expo-haptics` or anything else\n- ♿ **Accessible**: actions double as native [accessibility actions](#accessibility) for screen readers, and every animation respects OS Reduce Motion\n- 🧪 **Testable**: ships a Jest mock at `react-native-halo-menu/mock` with working accessibility actions\n\n## Installation\n\n```sh\nnpm install react-native-halo-menu\n# or\npnpm add react-native-halo-menu\n```\n\nPeer dependencies (you almost certainly have them already):\n\n```sh\nnpx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context\n```\n\n| Requirement                    | Version / note                                                  |\n| ------------------------------ | --------------------------------------------------------------- |\n| react-native                   | \u003e= 0.81, New Architecture (inherited from Reanimated 4)         |\n| react-native-reanimated        | \u003e= 4.0                                                          |\n| react-native-worklets          | matching your Reanimated minor                                  |\n| react-native-gesture-handler   | \u003e= 2.16 \u003c 3.0 (RNGH 2 builder API)                              |\n| react-native-safe-area-context | \u003e= 4.0; `SafeAreaProvider` recommended (falls back to 0 insets) |\n| Runtime dependencies           | none; host libraries stay as peers                              |\n| Native code                    | none in this package                                            |\n| Expo                           | Expo Go when the host SDK includes compatible peers             |\n| Bare React Native              | supported when RNGH/Reanimated/Worklets are set up              |\n\nYour app must be wrapped in `GestureHandlerRootView` (Expo Router templates already do this).\nExpo projects get the Reanimated/Worklets Babel setup from `babel-preset-expo`. Bare React Native\napps need the worklets Babel plugin (see the Reanimated install docs for your version):\n\n```js\n// babel.config.js (bare React Native)\nmodule.exports = {\n  presets: [\"module:@react-native/babel-preset\"],\n  plugins: [\"react-native-worklets/plugin\"], // must be last\n};\n```\n\nOptional Expo helpers live behind a subpath and are never imported by the core entry:\n\n```sh\nnpx expo install expo-blur\n```\n\nUse `expo-blur` only if you import `react-native-halo-menu/expo`; use any haptics library by\npassing callbacks to the provider.\n\n## Quickstart\n\nWrap your app once in `HaloMenuProvider`, inside your `GestureHandlerRootView` and above your\nnavigator, the same pattern as `@gorhom/bottom-sheet`'s `BottomSheetModalProvider`. The provider\nrenders the halo overlay (backdrop, lifted preview, radial buttons) as a full-screen\n`pointerEvents=\"none\"` layer at the root, so the pan gesture never leaves the trigger.\n\n```tsx\n// 1. Mount the provider once, near the root (inside GestureHandlerRootView,\n//    and inside SafeAreaProvider if your app has one).\nimport { HaloMenuProvider } from \"react-native-halo-menu\";\n\nexport function App() {\n  return (\n    \u003cHaloMenuProvider\u003e\n      \u003cScreens /\u003e\n    \u003c/HaloMenuProvider\u003e\n  );\n}\n\n// 2. Wrap anything long-pressable in a trigger.\nimport { HaloMenuTrigger, HaloMenuPreviewFrame } from \"react-native-halo-menu\";\n\nfunction Card({ item }) {\n  return (\n    \u003cHaloMenuTrigger\n      id={item.id}\n      actions={[\n        { key: \"share\", title: \"Share\", onPress: () =\u003e share(item) },\n        { key: \"save\", title: \"Save\", onPress: () =\u003e save(item) },\n        { key: \"delete\", title: \"Delete\", destructive: true, onPress: () =\u003e remove(item) },\n      ]}\n      renderPreview={({ width, height }) =\u003e (\n        \u003cHaloMenuPreviewFrame width={width} height={height} borderRadius={16}\u003e\n          \u003cCardContent item={item} /\u003e\n        \u003c/HaloMenuPreviewFrame\u003e\n      )}\n      accessible\n      accessibilityRole=\"button\"\n      accessibilityLabel={item.title}\n      accessibilityHint=\"Shows quick actions\"\n    \u003e\n      \u003cCardContent item={item} /\u003e\n    \u003c/HaloMenuTrigger\u003e\n  );\n}\n```\n\nThat's it. Long-press the card and drag.\n\n`renderPreview` is optional: omit it and the trigger re-renders its children inside a default\n`HaloMenuPreviewFrame`. Pass your own renderer to control the corner radius, inset, or content.\n\n## Customization\n\nEverything app-specific is an injection point on the provider:\n\n```tsx\n\u003cHaloMenuProvider\n  colorScheme=\"dark\"                          // defaults to the OS scheme\n  colors={{ foreground: \"#fff\", surface: \"#1c1c1e\", destructive: \"#ff453a\" }}\n  motion={{ longPressDuration: 250, liftScale: 1.2 }}\n  layout={{\n    buttonSize: 54,\n    iconSize: 24,\n    radius: 92,\n    hitRadius: 42,\n    arcGapDegrees: 48,\n  }}\n  appearance={{\n    buttonShadowOpacity: 0.14,                // 0 disables default button shadow\n    previewShadowOpacity: 0.28,               // 0 disables default preview shadow\n    showOriginDot: true,\n    originDotOpacity: 0.22,\n  }}\n  haptics={{\n    onOpen: () =\u003e Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success),\n    onHover: () =\u003e Haptics.selectionAsync(),\n  }}\n  renderBackdrop={({ visible, isDarkMode }) =\u003e \u003cMyBlurBackdrop visible={visible} /\u003e}\n  labelTextStyle={{ fontFamily: \"SpaceMono-Bold\" }}\n  suppressActivationWhen={navTransitioning}   // SharedValue gate during transitions\n\u003e\n```\n\n`colors`, `renderBackdrop`, `labelTextStyle`, and `haptics` are reactive. `motion`, `layout`, and\n`appearance` are **latched at mount**: the never-recreated gesture closure captures them once, so\nruntime changes to those three are intentionally ignored. SharedValue props\n(`suppressActivationWhen`, the trigger's `disabledWhen` / `fallbackWidth` / `fallbackHeight`) are\nlatched by identity: mutate their `.value`, don't swap in a new SharedValue.\n\nPer-action icons are a render prop, so any icon system works:\n\n```tsx\n{\n  key: \"share\",\n  title: \"Share\",\n  onPress: share,\n  renderIcon: ({ size, color }) =\u003e \u003cFeather name=\"share\" size={size} color={color} /\u003e,\n}\n```\n\n### Optional Expo blur\n\nThe core package uses a solid dim by default. For Expo apps that want frosted glass, install\n`expo-blur` and import the optional helper:\n\n```tsx\nimport { HaloMenuProvider } from \"react-native-halo-menu\";\nimport { HaloBlurBackdrop } from \"react-native-halo-menu/expo\";\n\n\u003cHaloMenuProvider\n  renderBackdrop={(props) =\u003e (\n    \u003cHaloBlurBackdrop\n      {...props}\n      intensity={50}\n      tint={props.isDarkMode ? \"systemMaterialDark\" : \"systemMaterialLight\"}\n      overlayColor={props.isDarkMode ? \"rgba(0,0,0,0.24)\" : \"rgba(255,255,255,0.18)\"}\n      blurReductionFactor={3}\n    /\u003e\n  )}\n\u003e\n  \u003cScreens /\u003e\n\u003c/HaloMenuProvider\u003e;\n```\n\n`HaloBlurBackdrop` forwards Expo Blur props such as `blurMethod`, `blurTarget`, and\n`blurReductionFactor`, while keeping `expo-blur` out of the main entry point.\n\n### Escape hatch: `useHaloMenuTrigger`\n\nWhen the measured view must differ from the gesture host (hero layouts, FlashList cells with\nrecycled dimensions, deferred actions), use the hook directly:\n\n```tsx\nconst { panGesture, animatedRef, menuActiveRef } = useHaloMenuTrigger({\n  id: item.id,\n  actions,\n  renderPreview,\n  fallbackWidth,\n  fallbackHeight, // SharedValues for recycled cells\n  disabledWhen: isMultiSelectMode, // SharedValue gate\n  interceptAction: (action) =\u003e action.key === \"select\", // defer until close\n  onCloseComplete: fireDeferredAction,\n  hideOnUnmount: true,\n});\n```\n\n### Imperative handle\n\n```tsx\nconst { visible, hide } = useHaloMenu(); // visible is a SharedValue\u003cboolean\u003e\n```\n\n## API\n\n| Export                                                | Kind      | Purpose                                   |\n| ----------------------------------------------------- | --------- | ----------------------------------------- |\n| `HaloMenuProvider`                                    | component | Root overlay + configuration              |\n| `HaloMenuTrigger`                                     | component | Long-press wrapper (the 90% case)         |\n| `useHaloMenuTrigger`                                  | hook      | Full-control escape hatch                 |\n| `HaloMenuPreviewFrame`                                | component | Lift/tilt/shadow frame for previews       |\n| `useHaloMenu`                                         | hook      | `{ visible, hide }`                       |\n| `getHaloMenuAccessibilityProps`                       | function  | Screen-reader action props for hook users |\n| `DEFAULT_MOTION`                                      | constant  | The default motion values                 |\n| `DEFAULT_LAYOUT`                                      | constant  | The default geometry values               |\n| `DEFAULT_APPEARANCE`                                  | constant  | The default visual values                 |\n| `HaloBlurBackdrop` from `react-native-halo-menu/expo` | component | Optional Expo blur backdrop               |\n| `HaloAction`, `HaloMenuColors`, `HaloMenuMotion`, …   | types     | Full TypeScript surface                   |\n\nUp to **5 actions** render per menu (the arc gets ambiguous beyond that); extras are dropped with a dev warning.\n\n### Provider props\n\n| Prop                        | Purpose                                                                           |\n| --------------------------- | --------------------------------------------------------------------------------- |\n| `colors`                    | Override foreground, surface, destructive, and selected-icon colors               |\n| `colorScheme`               | Force `\"light\"` / `\"dark\"`; defaults to the OS scheme                             |\n| `motion`                    | Long-press duration, open/close timing, lift scale, tilt, stagger                 |\n| `layout`                    | Button size, icon size, radius, hit radius, arc spacing, edge behavior            |\n| `appearance`                | Button/preview styles, shadow strength, origin-dot styling                        |\n| `haptics`                   | Optional sync/async `onOpen` / `onHover` callbacks; no haptics library is bundled |\n| `renderBackdrop`            | Replace the default solid backdrop with blur, gradients, or custom views          |\n| `labelTextStyle`            | Override the floating hover-label typography                                      |\n| `suppressActivationWhen`    | SharedValue gate for navigation transitions or disabled app states                |\n| `overlayContainerComponent` | Wrap the overlay, e.g. iOS `FullWindowOverlay` above native modals                |\n| `onWarn`                    | Replace dev warnings with your logger                                             |\n\nHaptic callbacks are fail-soft: thrown errors and rejected promises are reported once per provider\nthrough `onWarn`, then ignored so an optional native integration cannot interrupt the gesture.\nThe example app does not enable haptics by default because haptics require a native module in the\nhost dev build; add callbacks in your app after installing and rebuilding your haptics dependency.\n\n### Trigger props\n\n| Prop               | Purpose                                                                  |\n| ------------------ | ------------------------------------------------------------------------ |\n| `id`               | Stable trigger id; recommended for virtualized/recycled list cells       |\n| `actions`          | Up to 5 `HaloAction` items                                               |\n| `renderPreview`    | Optional; defaults to children inside a `HaloMenuPreviewFrame`           |\n| View props         | All `ViewProps` forward to the measured wrapper (`style`, `testID`, ...) |\n| `accessible` props | Native accessibility action fallback for each halo action                |\n| Hook options       | `disabledWhen`, `fallbackWidth`, `interceptAction`, lifecycle hooks      |\n\n### Preview frame props\n\n| Prop                     | Purpose                                                        |\n| ------------------------ | -------------------------------------------------------------- |\n| `width` / `height`       | Measured trigger size; forward them from `renderPreview`       |\n| `inset`                  | Padding between the measured bounds and the clipped preview    |\n| `borderRadius`           | Corner radius of the clipped preview (default 32)              |\n| `style` / `contentStyle` | Styles for the positioned wrapper / clipped content            |\n| `shadowOpacity`          | Per-frame override of the provider's preview shadow multiplier |\n\n### Action shape\n\n```ts\ntype HaloAction = {\n  key: string;\n  title: string;\n  destructive?: boolean;\n  onPress: () =\u003e void | Promise\u003cvoid\u003e;\n  renderIcon?: (props: { size: number; color: string; selected: boolean }) =\u003e ReactNode;\n};\n```\n\n### Package entry points\n\n| Import                                | Use when                                              |\n| ------------------------------------- | ----------------------------------------------------- |\n| `react-native-halo-menu`              | Core provider, trigger, hooks, frame, types, defaults |\n| `react-native-halo-menu/expo`         | Optional Expo helpers such as `HaloBlurBackdrop`      |\n| `react-native-halo-menu/mock`         | Jest mocks for app tests                              |\n| `react-native-halo-menu/package.json` | Tooling that needs package metadata                   |\n\n## Accessibility\n\nA hold-drag-release gesture is inherently invisible to assistive technology, so the package\nships an explicit fallback instead of pretending otherwise:\n\n- **Screen readers (VoiceOver / TalkBack).** Mark the trigger `accessible` with an\n  `accessibilityLabel`, and every halo action is exposed as a [native accessibility\n  action](https://reactnative.dev/docs/accessibility#accessibility-actions), so users pick them\n  from the rotor/actions menu without performing the gesture. This is opt-in because making the\n  wrapper accessible flattens its children for screen readers; enable it per trigger as shown in\n  the Quickstart. `useHaloMenuTrigger` users get the same mapping from\n  `getHaloMenuAccessibilityProps(actions, { interceptAction })`; spread the result onto the\n  measured view.\n- **Reduced motion.** Every animation passes `ReduceMotion.System`, so the OS setting disables\n  the lift/stagger/fade transitions.\n- **Motor access.** The gesture itself requires holding and dragging with one finger. There is\n  no sticky-selection mode yet; the accessibility actions above are the alternative path, and\n  `motion.longPressDuration` / `layout.hitRadius` can be tuned to make the gesture more forgiving.\n- **The overlay is hidden from the accessibility tree** (`importantForAccessibility`),\n  since selection happens under the user's finger; nothing in the menu needs AT focus.\n\n## Testing\n\n```js\n// jest.setup.js\njest.mock(\"react-native-halo-menu\", () =\u003e require(\"react-native-halo-menu/mock\"));\n\n// If you import the optional Expo helpers, mock the subpath too:\njest.mock(\"react-native-halo-menu/expo\", () =\u003e ({ HaloBlurBackdrop: () =\u003e null }));\n```\n\nThe mocked `HaloMenuTrigger` renders a plain `View` that keeps `style`, `testID`, and\naccessibility props, including working accessibility actions, so you can drive menu actions\nin tests without the gesture:\n\n```tsx\nfireEvent(screen.getByTestId(\"card-1\"), \"accessibilityAction\", {\n  nativeEvent: { actionName: \"halo-menu:share\" },\n});\n```\n\nUsing the bare `react-native` Jest preset (not `jest-expo`)? This package ships untranspiled\nESM, so add it to your transform allowlist:\n\n```js\ntransformIgnorePatterns: [\n  \"node_modules/(?!(react-native-halo-menu|(jest-)?react-native|@react-native(-community)?)/)\",\n],\n```\n\n## Troubleshooting\n\n### Gestures do not start\n\nMake sure the app is wrapped with `GestureHandlerRootView` as close to the app root as possible.\nFor native modals, wrap the modal content in its own `GestureHandlerRootView`.\n\n### Failed to create a worklet\n\nThis means the consumer app is not transforming worklets. Expo apps should use\n`babel-preset-expo`; bare React Native apps should follow the Reanimated/Worklets Babel setup for\ntheir installed version. Also stop Metro and restart with a cleared cache after changing Babel\nconfig.\n\n### Metro bundles `lib/module/index.js` while running the example\n\nMetro is running from the library root instead of `example/`. Stop all Metro servers and restart\nfrom the example:\n\n```sh\npkill -f \"expo start\"\npnpm --dir example start:dev-client -- --tunnel --clear\n```\n\n## Known limitations\n\nThe overlay renders in the root view hierarchy. Triggers inside a **native modal**\n(RN `\u003cModal\u003e`, Expo Router `presentation: \"modal\"` / `formSheet` routes) will lift _below_\nthat modal. On iOS you can render the overlay above native modals with react-native-screens'\n`FullWindowOverlay`:\n\n```tsx\nimport { FullWindowOverlay } from \"react-native-screens\";\n\nconst OverlayHost = ({ children }) =\u003e\n  Platform.OS === \"ios\" ? \u003cFullWindowOverlay\u003e{children}\u003c/FullWindowOverlay\u003e : \u003c\u003e{children}\u003c/\u003e;\n\n\u003cHaloMenuProvider overlayContainerComponent={OverlayHost}\u003e\n```\n\n(Alternatively, mount a second `HaloMenuProvider` inside the modal's content, with its own\n`GestureHandlerRootView`, per the Gesture Handler docs.)\n\n## When to use something else\n\nIf you want the **native platform context menu** (UIMenu / Material), use\n[zeego](https://zeego.dev) or `@react-native-menu/menu`. They're excellent and complementary.\nIf you're coming from\n[react-native-hold-menu](https://github.com/enesozturk/react-native-hold-menu) (a list-style\nhold menu, currently unmaintained and on Reanimated 2), this package covers the same\nhold-to-act gesture with a radial drag-to-select layout on the current Reanimated 4 stack.\nThis package is for the **gesture-layer** menu: touch-first power actions on cards and grids\nwhere you want full visual control and a single fluid gesture.\n\n## Example app\n\nA runnable showcase lives in [`example/`](./example):\n\n```sh\npnpm install\npnpm --dir example start   # then open in Expo Go or a dev build\n```\n\nTo run it on a physical iPhone with the current SDK, use a development build:\n\n```sh\ncd example\nnpx eas device:create\nnpx eas build --platform ios --profile development\npnpm start:dev-client -- --tunnel\n```\n\nInstall the EAS build from the link on your iPhone, open the installed app, then connect it to\nthe dev server. Public App Store Expo Go can lag the latest Expo SDK; development builds are the\nrecommended real-device path for validating gestures and blur. Haptics can be validated in a host\napp after installing and rebuilding the native haptics dependency.\n\nUse `pnpm --dir example start --lan` instead of `--tunnel` when local network discovery works\nreliably; `--tunnel` is slower but more dependable across restricted Wi-Fi networks. If Metro\nlogs that it bundled `lib/module/index.js`, see\n[Troubleshooting](#metro-bundles-libmoduleindexjs-while-running-the-example).\n\n## Published package contents\n\nThe npm package ships only runtime/library artifacts and consumer-facing support files:\n\n- `lib/` compiled JavaScript and TypeScript declarations\n- `src/` TypeScript source for the `source` export condition used by modern RN tooling\n- `mock.js` / `mock.d.ts` for Jest\n- `llms.txt`, `README.md`, `CHANGELOG.md`, `LICENSE`, and `package.json`\n\nIt does **not** ship runtime dependencies, the example app, CI config, generated native projects,\ntests, or local build artifacts. `pnpm run package:files:check` enforces this before release.\n\n## Roadmap\n\n- Expo Snack once the public Expo Go runtime matches the demo dependencies\n- Real-device interaction matrix for iOS and Android releases\n- Gesture Handler 3 hook-API build after Expo SDK support and real-device validation\n\n## License\n\nMIT © Adrian Gruber\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthegruber%2Freact-native-halo-menu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthegruber%2Freact-native-halo-menu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthegruber%2Freact-native-halo-menu/lists"}