Skip to content

Commit 22edb52

Browse files
committed
refactor: add package exports
1 parent 024e002 commit 22edb52

File tree

8 files changed

+93
-74
lines changed

8 files changed

+93
-74
lines changed

INSTALL.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Installation & Setup
2+
3+
Version >= 12 requires React Native 0.73 / Expo 50 or newer. Use version 11 if you're on older version of RN / Expo.
4+
5+
Version >= 11 requires React Native 0.71 / Expo 48 or newer. Use version 10 if you're on older version of RN / Expo.
6+
7+
0. In your `tsconfig.json`, make sure you have [`"moduleResolution": "nodenext"`](https://www.typescriptlang.org/tsconfig#moduleResolution) set. This is required for TS to see the typings exported via [package.json `exports`](https://reactnative.dev/blog/2023/06/21/package-exports-support).
8+
9+
1. add [`unstable_enablePackageExports`](https://metrobundler.dev/docs/configuration/#unstable_enablepackageexports-experimental)) to your metro config (in `metro.config.js`). This field will default to `true` in a future version of RN so you can remove it and not worry about it later. This allows us to do some bundle size savings.
10+
11+
```js
12+
// if you use Expo:
13+
const config = getDefaultConfig(__dirname);
14+
// unstable_enablePackageExports: true,
15+
config.resolver.unstable_enablePackageExports = true;
16+
module.exports = config;
17+
18+
// if you use bare React Native:
19+
const config = {
20+
resolver: {
21+
unstable_enablePackageExports: true,
22+
},
23+
};
24+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
25+
```
26+
27+
2. `yarn add react-navigation-header-buttons`
28+
29+
3. Wrap your root component in a `HeaderButtons` Provider and pass the `stackType` prop (`'native' | 'js'`), as seen in [example's App.tsx](https://github.com/vonovak/react-navigation-header-buttons/blob/master/example/src/App.tsx).
30+
31+
There are 3 providers to choose from - you'll get an actionable warning if you don't use the right one. They are:
32+
33+
- `HeaderButtonsProvider` - the recommended one, which assumes you will use `overflowMenuPressHandlerDropdownMenu` on Android but not iOS (because that's the default behavior that the library ships with). Internally, this translates to `HeaderButtonsProviderDropdownMenu` on Android and `HeaderButtonsProviderPlain` on iOS.
34+
- `HeaderButtonsProviderPlain` - use it if you're not planning to use `overflowMenuPressHandlerDropdownMenu` at all. It will shave a few kB off your bundle and Hermes won't have to parse some code that would not run in the end.
35+
- `HeaderButtonsProviderDropdownMenu` - use it if you're planning to use `overflowMenuPressHandlerDropdownMenu` on all platforms.
36+
37+
Importing: `import { your_chosen_provider } from 'react-navigation-header-buttons/your_chosen_provider'`.
38+
39+
> [!IMPORTANT]
40+
> The Provider must be placed as a descendant of `NavigationContainer`, otherwise this library will not receive the correct theme from React Navigation.

README.md

+7-29
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,22 @@ This package will help you render buttons in the navigation bar and handle the s
66

77
✅ Works great with icons from `@expo/vector-icons` / `react-native-vector-icons` or any other icon library
88

9-
✅ Supports both [JS](https://reactnavigation.org/docs/stack-navigator) and [native](https://reactnavigation.org/docs/native-stack-navigator/) stack
9+
✅ Supports Expo Router, and both [JS](https://reactnavigation.org/docs/stack-navigator) and [native](https://reactnavigation.org/docs/native-stack-navigator/) stack
1010

1111
✅ Beautiful overflow menus for items that don't fit into the navbar
1212

1313
[Recipes](#recipes) and examples included
1414

1515
✅ Written in TS
1616

17-
✅ Test suite for easy maintenance
18-
17+
✅ Test suite included (mostly good only for the maintainer, but hey, not bad to know it's there)
1918

2019
<!--
2120
#### Library status
2221
2322
Mature: the library is stable and feature-complete. It won't be updated often not because it's abandoned, but because it doesn't need to be.
2423
-->
2524

26-
2725
#### Demo App
2826

2927
Contains many examples in the [example folder](https://github.com/vonovak/react-navigation-header-buttons/tree/master/example/src/screens). I highly recommend you check it out to get a better idea of the api.
@@ -75,8 +73,6 @@ export function UsageWithIcons({ navigation }) {
7573
React.useLayoutEffect(() => {
7674
navigation.setOptions({
7775
title: 'Demo',
78-
// in your app, you can extract the arrow function into a separate component
79-
// to avoid creating a new one every time
8076
headerRight: () => (
8177
<HeaderButtons HeaderButtonComponent={MaterialHeaderButton}>
8278
<Item
@@ -103,24 +99,9 @@ export function UsageWithIcons({ navigation }) {
10399
}
104100
```
105101

106-
## Setup
107-
108-
Version >= 11 requires React Native 0.71 / Expo 48 or newer. Use version 10 if you're on older version of RN / Expo.
109-
110-
1. `yarn add react-navigation-header-buttons`
111-
112-
2. Wrap your root component in a `HeaderButtons` Provider and pass the `stackType` prop (`'native' | 'js'`), as seen in [example's App.tsx](https://github.com/vonovak/react-navigation-header-buttons/blob/master/example/src/App.tsx).
113-
114-
There are 3 providers to choose from - but don't worry about it now, you'll get an actionable warning if you don't do it right:
115-
116-
- `HeaderButtonsProvider` - the default, which assumes you will use `overflowMenuPressHandlerDropdownMenu` on Android but not iOS (because that's the default behavior that the library ships with).
117-
- `HeaderButtonsProviderPlain` - use it if you're not planning to use `overflowMenuPressHandlerDropdownMenu`. It will shave a few kB off your bundle and Hermes won't have to parse the code that would not run in the end.
118-
- `HeaderButtonsProviderDropdownMenu` - use it if you're planning to use `overflowMenuPressHandlerDropdownMenu` on all platforms.
119-
120-
Importing: `import { your_chosen_provider } from 'react-navigation-header-buttons/your_chosen_provider'`.
102+
## Installation & Setup
121103

122-
> [!IMPORTANT]
123-
> The Provider must be placed as a descendant of `NavigationContainer`, otherwise this library will not receive the correct theme from React Navigation.
104+
See [Installation & Setup](INSTALL.md)
124105

125106
## Usage
126107

@@ -173,7 +154,7 @@ The most important prop is `onPress` which defines what kind of overflow menu we
173154
The package exports common handlers you can use, but you can provide your own too (via the `onPress` prop):
174155

175156
| exported handler | description |
176-
| -------------------------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
157+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177158
| `overflowMenuPressHandlerActionSheet` | This is iOS-only: it displays overflow items in an `ActionSheetIOS` |
178159
| `overflowMenuPressHandlerPopupMenu` | This is Android-only: it displays overflow items using `UIManager.showPopupMenu` |
179160
| `overflowMenuPressHandlerDropdownMenu` | Can be used on iOS, Android and Web. Displays overflow items in a material popup adapted from [react-native-paper](https://callstack.github.io/react-native-paper/docs/components/Menu), credit for an amazing job goes to them. This `Menu` is bundled in this library (no dependency on `react-native-paper`) but only `require`d if you actually use it. |
@@ -184,7 +165,7 @@ You can also use the [react-native-menu](https://github.com/react-native-menu/me
184165
`OverflowMenu` accepts:
185166

186167
| prop and type | description | note |
187-
| -------------------------------------------- | ----------------------------------------------------------- |-------------------------------------------------------------------------------------------------------------------------|
168+
| -------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
188169
| OverflowIcon: ReactElement \| ComponentType | React element or component for the overflow icon | if you provide a component, it will receive `color` prop as seen in example above |
189170
| style?: ViewStyle | optional styles for overflow button | there are some default styles set, as seen in `OverflowButton.tsx` |
190171
| onPress?: (OnOverflowMenuPressParams) => any | function that is called when overflow menu is pressed. | This will override the default handler. Note the default handler offers (limited) customization. See more in "Recipes". |
@@ -195,11 +176,8 @@ You can also use the [react-native-menu](https://github.com/react-native-menu/me
195176
| preset?: 'tabHeader' \| 'stackHeader' | | see [props of headerbuttons](#headerbuttons) |
196177
| other props | props passed to the nested `PlatformPressable` | pass eg. `pressColor` to control ripple color on Android |
197178

198-
199-
200179
> [!NOTE]
201180
> There are important limitations on what can be passed as children to `OverflowMenu`. Please read below:
202-
>
203181
204182
Children passed to `OverflowMenu` should be
205183

@@ -303,7 +281,7 @@ The default handler for overflow menu on iOS is `overflowMenuPressHandlerActionS
303281

304282
One of the usual things you may want to do is override the cancel button label on iOS - see [example](example/src/screens/UsageWithOverflow.tsx).
305283

306-
You can also use the [react-native-menu](https://github.com/react-native-menu/menu) to show the overflow menu, as seen in the example app.
284+
You can also use the [react-native-menu](https://github.com/react-native-menu/menu) (or similar) to show the overflow menu, as seen in the example app.
307285

308286
#### Using custom text transforms
309287

example/metro.config.js

-22
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,6 @@ const config = {
2222
// So we block them at the root, and alias them to the versions in example's node_modules
2323
resolver: {
2424
...defaultConfig.resolver,
25-
// "exports": {
26-
// ".": {
27-
// "require": {
28-
// "default": "./lib/commonjs/index.js"
29-
// },
30-
// "import": {
31-
// "default": "./lib/module/index.js"
32-
// },
33-
// "react-native": "./src/index.ts"
34-
// },
35-
// "./menu": {
36-
// "require": {
37-
// "default": "./lib/commonjs/overflowMenu/vendor/index.js"
38-
// },
39-
// "import": {
40-
// "default": "./lib/module/overflowMenu/vendor/index.js",
41-
// "types": "./lib/typescript/overflowMenu/vendor/index.d.ts"
42-
// },
43-
// "react-native": "./src/index/menu/index.ts"
44-
// }
45-
// },
46-
// unstable_enablePackageExports: true,
4725

4826
blockList: exclusionList(
4927
modules.map(

example/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "example",
33
"version": "1.0.0",
44
"scripts": {
5-
"start": "expo start --dev-client -c",
5+
"start": "EXPO_METRO_UNSTABLE_ERRORS=1 expo start --dev-client -c",
66
"android": "expo run:android",
77
"ios": "expo run:ios",
88
"web": "expo start --web",

package.json

+40-14
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,46 @@
22
"name": "react-navigation-header-buttons",
33
"version": "11.2.1",
44
"description": "Easily render header buttons for react-navigation",
5-
"main": "lib/commonjs/index",
6-
"module": "lib/module/index",
7-
"types": "lib/typescript/index.d.ts",
8-
"react-native": "src/index",
95
"source": "src/index",
6+
"exports": {
7+
".": {
8+
"require": "./lib/commonjs/index.js",
9+
"import": "./lib/module/index.js",
10+
"react-native": "./src/index.ts",
11+
"types": "./lib/typescript/index.d.ts",
12+
"default": "./lib/module/index.js"
13+
},
14+
"./HeaderButtonsProvider": {
15+
"require": "./lib/commonjs/index/HeaderButtonsProvider.js",
16+
"import": "./lib/module/index/HeaderButtonsProvider.js",
17+
"types": "./lib/typescript/index/HeaderButtonsProvider.d.ts",
18+
"react-native": "./src/index/HeaderButtonsProvider.tsx",
19+
"default": "./lib/module/index/HeaderButtonsProvider.js"
20+
},
21+
"./HeaderButtonsProviderDropdownMenu": {
22+
"require": "./lib/commonjs/index/HeaderButtonsProviderDropdownMenu.js",
23+
"import": "./lib/module/index/HeaderButtonsProviderDropdownMenu.js",
24+
"types": "./lib/typescript/index/HeaderButtonsProviderDropdownMenu.d.ts",
25+
"react-native": "./src/index/HeaderButtonsProviderDropdownMenu.tsx",
26+
"default": "./lib/module/index/HeaderButtonsProviderDropdownMenu.js"
27+
},
28+
"./HeaderButtonsProviderPlain": {
29+
"require": "./lib/commonjs/index/HeaderButtonsProviderPlain.js",
30+
"import": "./lib/module/index/HeaderButtonsProviderPlain.js",
31+
"types": "./lib/typescript/index/HeaderButtonsProviderPlain.d.ts",
32+
"react-native": "./src/index/HeaderButtonsProviderPlain.tsx",
33+
"default": "./lib/module/index/HeaderButtonsProviderPlain.js"
34+
}
35+
},
36+
"scripts": {
37+
"test": "jest",
38+
"typecheck": "tsc --noEmit",
39+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
40+
"prepack": "bob build",
41+
"release": "yarn prepack && release-it",
42+
"example": "yarn --cwd example",
43+
"bootstrap": "yarn example && yarn install"
44+
},
1045
"files": [
1146
"src",
1247
"lib",
@@ -26,15 +61,6 @@
2661
"!**/__mocks__",
2762
"!**/.*"
2863
],
29-
"scripts": {
30-
"test": "jest",
31-
"typecheck": "tsc --noEmit",
32-
"lint": "eslint \"**/*.{js,ts,tsx}\"",
33-
"prepack": "bob build",
34-
"release": "yarn prepack && release-it",
35-
"example": "yarn --cwd example",
36-
"bootstrap": "yarn example && yarn install"
37-
},
3864
"keywords": [
3965
"react-native",
4066
"ios",
@@ -80,7 +106,7 @@
80106
"@react-navigation/elements": ">=1",
81107
"@react-navigation/native": ">=6",
82108
"react": "*",
83-
"react-native": ">=0.72.0"
109+
"react-native": ">=0.73.0"
84110
},
85111
"engines": {
86112
"node": ">= 20.0.0"

src/index/readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The reason this is called "index" is that (it seems) the transform step of metro bundler looks for the sources and if I rename this, resolution of 'react-navigation-header-buttons/HeaderButtonsProvider' fails in the example project.

src/overflowMenu/__tests__/OverflowMenuProvider.test.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import { HeaderButtonsProviderDropdownMenu } from '../HeaderButtonsProviderDropd
1010
import { HeaderButtonsProviderPlain } from '../HeaderButtonsProviderPlain';
1111
import * as fs from 'node:fs';
1212
import * as path from 'node:path';
13-
import { exec } from 'child_process';
14-
import { promisify } from 'util';
15-
const execAsync = promisify(exec);
13+
import * as child_process from 'node:child_process';
1614

1715
describe('HeaderButtonsProvider renders', () => {
1816
it('only the child when menu is not shown', () => {
@@ -134,11 +132,9 @@ describe('HeaderButtonsProvider renders', () => {
134132
it('should match the expected modules snapshot', async () => {
135133
const cwd = process.cwd();
136134
const examplePath = `${cwd}/example/`;
137-
const iosPromise = execAsync(`cd ${examplePath} && yarn requires-ios`);
138-
const androidPromise = execAsync(
139-
`cd ${examplePath} && yarn requires-android`
135+
child_process.execSync(
136+
`cd ${examplePath} && yarn requires-ios && yarn requires-android`
140137
);
141-
await Promise.all([iosPromise, androidPromise]);
142138

143139
const outputIos = fs.readFileSync(
144140
path.join(examplePath, `requires-ios.txt`),

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"dom"
1818
],
1919
"module": "esnext",
20-
"moduleResolution": "node",
20+
"moduleResolution": "nodenext",
2121
"noFallthroughCasesInSwitch": true,
2222
"noImplicitReturns": true,
2323
"noImplicitUseStrict": false,

0 commit comments

Comments
 (0)