@27appstudio/react-native-bottom-toolbar 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/module/react-navigation/index.js +29 -8
- package/lib/module/react-navigation/index.js.map +1 -1
- package/lib/typescript/src/react-navigation/index.d.ts +4 -3
- package/lib/typescript/src/react-navigation/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/react-navigation/index.ts +33 -8
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ function ExpenseScreen({
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
The hook shows the toolbar once the push has landed (or right away on the stack's first screen), hides it when the screen loses focus, and brings it back when a modal over the screen closes.
|
|
73
|
+
The hook shows the toolbar once the push has landed (or right away on the stack's first screen), hides it when the screen loses focus, and brings it back when a modal over the screen closes. New `items` or `options` update a toolbar already on screen, and an empty array hides it, so memoize them (`useMemo`) rather than building a new array on every render.
|
|
74
74
|
|
|
75
75
|
### Without React Navigation
|
|
76
76
|
|
|
@@ -124,7 +124,7 @@ Called for every button tap, in addition to that button's own `onPress`.
|
|
|
124
124
|
|
|
125
125
|
### `useBottomToolbar(navigation, items, options?)`
|
|
126
126
|
|
|
127
|
-
Imported from `/react-navigation`. `items`
|
|
127
|
+
Imported from `/react-navigation`. New `items` or `options` update the toolbar while the screen is on top (an empty array hides it); they never resubscribe the navigation listeners. Memoize them, or every render sends them to the native side again.
|
|
128
128
|
|
|
129
129
|
## Platform notes
|
|
130
130
|
|
|
@@ -10,9 +10,10 @@ import { hide, show } from "../index.js";
|
|
|
10
10
|
*
|
|
11
11
|
* The toolbar belongs to the whole stack: it is shown once the push has landed
|
|
12
12
|
* (`transitionEnd`), or at once on the stack's first screen; again on
|
|
13
|
-
* returning from a modal (`focus`); and hidden on `blur`.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* returning from a modal (`focus`); and hidden on `blur`. While the screen is
|
|
14
|
+
* on top, new `items` or `options` update the toolbar (an empty array hides
|
|
15
|
+
* it), so memoize them (`useMemo`): a new array on every render would send it
|
|
16
|
+
* to the native side every time.
|
|
16
17
|
*/
|
|
17
18
|
export function useBottomToolbar(navigation, items, options) {
|
|
18
19
|
const latest = useRef({
|
|
@@ -23,24 +24,31 @@ export function useBottomToolbar(navigation, items, options) {
|
|
|
23
24
|
items,
|
|
24
25
|
options
|
|
25
26
|
};
|
|
27
|
+
// Whether the push has landed, so `show` reaches this screen and not the one
|
|
28
|
+
// below; and what was last sent, so an update only goes out when it changed.
|
|
29
|
+
const arrived = useRef(false);
|
|
30
|
+
const shown = useRef(undefined);
|
|
26
31
|
useEffect(() => {
|
|
27
32
|
// Back from a pop, both focus and transitionEnd show: the latter comes after
|
|
28
33
|
// the popped screen's unmount, whose `hide` would otherwise win.
|
|
29
|
-
const showLatest = () =>
|
|
34
|
+
const showLatest = () => {
|
|
35
|
+
shown.current = latest.current;
|
|
36
|
+
show(latest.current.items, latest.current.options);
|
|
37
|
+
};
|
|
30
38
|
// A pushed screen mounts before its push starts, so showing now would put
|
|
31
39
|
// the items on the screen below; the first screen has no push to wait for.
|
|
32
|
-
|
|
33
|
-
if (arrived) {
|
|
40
|
+
arrived.current = navigation.isFocused() && navigation.getState().index === 0;
|
|
41
|
+
if (arrived.current) {
|
|
34
42
|
showLatest();
|
|
35
43
|
}
|
|
36
44
|
const offTransitionEnd = navigation.addListener('transitionEnd', event => {
|
|
37
45
|
if (!event.data.closing) {
|
|
38
|
-
arrived = true;
|
|
46
|
+
arrived.current = true;
|
|
39
47
|
showLatest();
|
|
40
48
|
}
|
|
41
49
|
});
|
|
42
50
|
const offFocus = navigation.addListener('focus', () => {
|
|
43
|
-
if (arrived) {
|
|
51
|
+
if (arrived.current) {
|
|
44
52
|
showLatest();
|
|
45
53
|
}
|
|
46
54
|
});
|
|
@@ -52,5 +60,18 @@ export function useBottomToolbar(navigation, items, options) {
|
|
|
52
60
|
hide();
|
|
53
61
|
};
|
|
54
62
|
}, [navigation]);
|
|
63
|
+
|
|
64
|
+
// New items or options on a screen whose toolbar is up: update it in place.
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const last = shown.current;
|
|
67
|
+
if (!arrived.current || !navigation.isFocused() || last && last.items === items && last.options === options) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
shown.current = {
|
|
71
|
+
items,
|
|
72
|
+
options
|
|
73
|
+
};
|
|
74
|
+
show(items, options);
|
|
75
|
+
}, [items, navigation, options]);
|
|
55
76
|
}
|
|
56
77
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useRef","hide","show","useBottomToolbar","navigation","items","options","latest","current","
|
|
1
|
+
{"version":3,"names":["useEffect","useRef","hide","show","useBottomToolbar","navigation","items","options","latest","current","arrived","shown","undefined","showLatest","isFocused","getState","index","offTransitionEnd","addListener","event","data","closing","offFocus","offBlur","last"],"sourceRoot":"../../../src","sources":["react-navigation/index.ts"],"mappings":";;AAIA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEzC,SAASC,IAAI,EAAEC,IAAI,QAAQ,aAAU;;AAErC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BC,UAAoD,EACpDC,KAAoB,EACpBC,OAAwB,EACxB;EACA,MAAMC,MAAM,GAAGP,MAAM,CAAC;IAAEK,KAAK;IAAEC;EAAQ,CAAC,CAAC;EACzCC,MAAM,CAACC,OAAO,GAAG;IAAEH,KAAK;IAAEC;EAAQ,CAAC;EACnC;EACA;EACA,MAAMG,OAAO,GAAGT,MAAM,CAAC,KAAK,CAAC;EAC7B,MAAMU,KAAK,GAAGV,MAAM,CAElBW,SAAS,CAAC;EAEZZ,SAAS,CAAC,MAAM;IACd;IACA;IACA,MAAMa,UAAU,GAAGA,CAAA,KAAM;MACvBF,KAAK,CAACF,OAAO,GAAGD,MAAM,CAACC,OAAO;MAC9BN,IAAI,CAACK,MAAM,CAACC,OAAO,CAACH,KAAK,EAAEE,MAAM,CAACC,OAAO,CAACF,OAAO,CAAC;IACpD,CAAC;IACD;IACA;IACAG,OAAO,CAACD,OAAO,GACbJ,UAAU,CAACS,SAAS,CAAC,CAAC,IAAIT,UAAU,CAACU,QAAQ,CAAC,CAAC,CAACC,KAAK,KAAK,CAAC;IAC7D,IAAIN,OAAO,CAACD,OAAO,EAAE;MACnBI,UAAU,CAAC,CAAC;IACd;IAEA,MAAMI,gBAAgB,GAAGZ,UAAU,CAACa,WAAW,CAC7C,eAAe,EACdC,KAAK,IAAK;MACT,IAAI,CAACA,KAAK,CAACC,IAAI,CAACC,OAAO,EAAE;QACvBX,OAAO,CAACD,OAAO,GAAG,IAAI;QACtBI,UAAU,CAAC,CAAC;MACd;IACF,CACF,CAAC;IACD,MAAMS,QAAQ,GAAGjB,UAAU,CAACa,WAAW,CAAC,OAAO,EAAE,MAAM;MACrD,IAAIR,OAAO,CAACD,OAAO,EAAE;QACnBI,UAAU,CAAC,CAAC;MACd;IACF,CAAC,CAAC;IACF,MAAMU,OAAO,GAAGlB,UAAU,CAACa,WAAW,CAAC,MAAM,EAAEhB,IAAI,CAAC;IAEpD,OAAO,MAAM;MACXe,gBAAgB,CAAC,CAAC;MAClBK,QAAQ,CAAC,CAAC;MACVC,OAAO,CAAC,CAAC;MACTrB,IAAI,CAAC,CAAC;IACR,CAAC;EACH,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;;EAEhB;EACAL,SAAS,CAAC,MAAM;IACd,MAAMwB,IAAI,GAAGb,KAAK,CAACF,OAAO;IAC1B,IACE,CAACC,OAAO,CAACD,OAAO,IAChB,CAACJ,UAAU,CAACS,SAAS,CAAC,CAAC,IACtBU,IAAI,IAAIA,IAAI,CAAClB,KAAK,KAAKA,KAAK,IAAIkB,IAAI,CAACjB,OAAO,KAAKA,OAAQ,EAC1D;MACA;IACF;IACAI,KAAK,CAACF,OAAO,GAAG;MAAEH,KAAK;MAAEC;IAAQ,CAAC;IAClCJ,IAAI,CAACG,KAAK,EAAEC,OAAO,CAAC;EACtB,CAAC,EAAE,CAACD,KAAK,EAAED,UAAU,EAAEE,OAAO,CAAC,CAAC;AAClC","ignoreList":[]}
|
|
@@ -8,9 +8,10 @@ import type { ToolbarItem, ToolbarOptions } from '../types.js';
|
|
|
8
8
|
*
|
|
9
9
|
* The toolbar belongs to the whole stack: it is shown once the push has landed
|
|
10
10
|
* (`transitionEnd`), or at once on the stack's first screen; again on
|
|
11
|
-
* returning from a modal (`focus`); and hidden on `blur`.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* returning from a modal (`focus`); and hidden on `blur`. While the screen is
|
|
12
|
+
* on top, new `items` or `options` update the toolbar (an empty array hides
|
|
13
|
+
* it), so memoize them (`useMemo`): a new array on every render would send it
|
|
14
|
+
* to the native side every time.
|
|
14
15
|
*/
|
|
15
16
|
export declare function useBottomToolbar(navigation: NativeStackNavigationProp<ParamListBase>, items: ToolbarItem[], options?: ToolbarOptions): void;
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AAM5D
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/react-navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAU,CAAC;AAM5D;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,yBAAyB,CAAC,aAAa,CAAC,EACpD,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE,cAAc,QA+DzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@27appstudio/react-native-bottom-toolbar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Native bottom toolbar for React Native: UIKit's toolbar on iOS (Liquid Glass on iOS 26) and a Material 3 floating toolbar on Android.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -13,9 +13,10 @@ import { hide, show } from '../index';
|
|
|
13
13
|
*
|
|
14
14
|
* The toolbar belongs to the whole stack: it is shown once the push has landed
|
|
15
15
|
* (`transitionEnd`), or at once on the stack's first screen; again on
|
|
16
|
-
* returning from a modal (`focus`); and hidden on `blur`.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* returning from a modal (`focus`); and hidden on `blur`. While the screen is
|
|
17
|
+
* on top, new `items` or `options` update the toolbar (an empty array hides
|
|
18
|
+
* it), so memoize them (`useMemo`): a new array on every render would send it
|
|
19
|
+
* to the native side every time.
|
|
19
20
|
*/
|
|
20
21
|
export function useBottomToolbar(
|
|
21
22
|
navigation: NativeStackNavigationProp<ParamListBase>,
|
|
@@ -24,15 +25,25 @@ export function useBottomToolbar(
|
|
|
24
25
|
) {
|
|
25
26
|
const latest = useRef({ items, options });
|
|
26
27
|
latest.current = { items, options };
|
|
28
|
+
// Whether the push has landed, so `show` reaches this screen and not the one
|
|
29
|
+
// below; and what was last sent, so an update only goes out when it changed.
|
|
30
|
+
const arrived = useRef(false);
|
|
31
|
+
const shown = useRef<
|
|
32
|
+
{ items: ToolbarItem[]; options?: ToolbarOptions } | undefined
|
|
33
|
+
>(undefined);
|
|
27
34
|
|
|
28
35
|
useEffect(() => {
|
|
29
36
|
// Back from a pop, both focus and transitionEnd show: the latter comes after
|
|
30
37
|
// the popped screen's unmount, whose `hide` would otherwise win.
|
|
31
|
-
const showLatest = () =>
|
|
38
|
+
const showLatest = () => {
|
|
39
|
+
shown.current = latest.current;
|
|
40
|
+
show(latest.current.items, latest.current.options);
|
|
41
|
+
};
|
|
32
42
|
// A pushed screen mounts before its push starts, so showing now would put
|
|
33
43
|
// the items on the screen below; the first screen has no push to wait for.
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
arrived.current =
|
|
45
|
+
navigation.isFocused() && navigation.getState().index === 0;
|
|
46
|
+
if (arrived.current) {
|
|
36
47
|
showLatest();
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -40,13 +51,13 @@ export function useBottomToolbar(
|
|
|
40
51
|
'transitionEnd',
|
|
41
52
|
(event) => {
|
|
42
53
|
if (!event.data.closing) {
|
|
43
|
-
arrived = true;
|
|
54
|
+
arrived.current = true;
|
|
44
55
|
showLatest();
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
);
|
|
48
59
|
const offFocus = navigation.addListener('focus', () => {
|
|
49
|
-
if (arrived) {
|
|
60
|
+
if (arrived.current) {
|
|
50
61
|
showLatest();
|
|
51
62
|
}
|
|
52
63
|
});
|
|
@@ -59,4 +70,18 @@ export function useBottomToolbar(
|
|
|
59
70
|
hide();
|
|
60
71
|
};
|
|
61
72
|
}, [navigation]);
|
|
73
|
+
|
|
74
|
+
// New items or options on a screen whose toolbar is up: update it in place.
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const last = shown.current;
|
|
77
|
+
if (
|
|
78
|
+
!arrived.current ||
|
|
79
|
+
!navigation.isFocused() ||
|
|
80
|
+
(last && last.items === items && last.options === options)
|
|
81
|
+
) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
shown.current = { items, options };
|
|
85
|
+
show(items, options);
|
|
86
|
+
}, [items, navigation, options]);
|
|
62
87
|
}
|