@27appstudio/react-native-bottom-toolbar 0.2.0 → 0.3.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 +15 -0
- package/android/src/main/java/com/bottomtoolbar/BottomToolbarModule.kt +20 -0
- package/ios/BottomToolbar.mm +28 -0
- package/lib/module/NativeBottomToolbar.js.map +1 -1
- package/lib/module/index.js +54 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeBottomToolbar.d.ts +5 -0
- package/lib/typescript/src/NativeBottomToolbar.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeBottomToolbar.ts +5 -0
- package/src/index.tsx +52 -1
package/README.md
CHANGED
|
@@ -112,6 +112,21 @@ When `items` contains no space, iOS puts the buttons at the trailing edge. When
|
|
|
112
112
|
|
|
113
113
|
`options.tintColor` sets the icon and title color. iOS defaults to the navigation bar's tint (matching the header buttons); Android defaults to the Material 3 theme.
|
|
114
114
|
|
|
115
|
+
### `getHeight(): Promise<number>` and `useBottomToolbarHeight(): number`
|
|
116
|
+
|
|
117
|
+
The room the toolbar takes at the bottom, in points, to keep a list's last row clear of it (its touches take the whole band, not just the buttons):
|
|
118
|
+
|
|
119
|
+
- iOS: the band the bar adds over the content above the bottom safe area, measured from the screen's safe area (iOS 26 floats the bar inside a full-screen frame, so the bar's own frame says nothing). 0 where the bar doesn't sit under the content, such as a foldable's side column. While hidden, the band last measured, else the bar's own height.
|
|
120
|
+
- Android: the floating button or toolbar's height plus its margin, above the navigation bar.
|
|
121
|
+
|
|
122
|
+
`useBottomToolbarHeight()` keeps it up to date: it measures again once a `show` has settled and when the window changes size (rotation, a foldable opening). It reads 0 until the first answer.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
const toolbarHeight = useBottomToolbarHeight();
|
|
126
|
+
// ...
|
|
127
|
+
<FlatList ListFooterComponent={<View style={{ height: toolbarHeight }} />} />;
|
|
128
|
+
```
|
|
129
|
+
|
|
115
130
|
### `hide()`
|
|
116
131
|
|
|
117
132
|
### `addPressListener(listener: (id: string) => void): () => void`
|
|
@@ -15,6 +15,7 @@ import androidx.core.view.ViewCompat
|
|
|
15
15
|
import androidx.core.view.WindowInsetsCompat
|
|
16
16
|
import androidx.core.view.updateLayoutParams
|
|
17
17
|
import java.lang.ref.WeakReference
|
|
18
|
+
import com.facebook.react.bridge.Promise
|
|
18
19
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
19
20
|
import com.facebook.react.bridge.ReadableArray
|
|
20
21
|
import com.facebook.react.bridge.ReadableMap
|
|
@@ -70,6 +71,23 @@ class BottomToolbarModule(reactContext: ReactApplicationContext) :
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The room at the bottom, in dp, above the navigation bar: the shown view's height
|
|
76
|
+
* plus its margin, else a FAB's (the Material 3 size), which a one-button `show` adds.
|
|
77
|
+
*/
|
|
78
|
+
override fun getHeight(promise: Promise) {
|
|
79
|
+
UiThreadUtil.runOnUiThread {
|
|
80
|
+
val view = shown?.get()
|
|
81
|
+
val density = reactApplicationContext.resources.displayMetrics.density
|
|
82
|
+
val height = if (view != null && view.height > 0) {
|
|
83
|
+
view.height / density + MARGIN_DP
|
|
84
|
+
} else {
|
|
85
|
+
FAB_SIZE_DP + MARGIN_DP
|
|
86
|
+
}
|
|
87
|
+
promise.resolve(height.toDouble())
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
override fun hide() {
|
|
74
92
|
UiThreadUtil.runOnUiThread { removeShown(animated = true) }
|
|
75
93
|
}
|
|
@@ -191,5 +209,7 @@ class BottomToolbarModule(reactContext: ReactApplicationContext) :
|
|
|
191
209
|
private const val EXIT_DURATION = 200L
|
|
192
210
|
private const val ENTER_OFFSET_DP = 24f
|
|
193
211
|
private const val MARGIN_DP = 16f
|
|
212
|
+
// Material 3's regular FAB.
|
|
213
|
+
private const val FAB_SIZE_DP = 56f
|
|
194
214
|
}
|
|
195
215
|
}
|
package/ios/BottomToolbar.mm
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* hides it on leave.
|
|
13
13
|
*/
|
|
14
14
|
@implementation BottomToolbar {
|
|
15
|
+
/** The band last measured while the bar was shown, in points. */
|
|
16
|
+
NSNumber *_measuredHeight;
|
|
15
17
|
__weak UIViewController *_owner;
|
|
16
18
|
/** The navigation bar's tint, as last read undimmed. */
|
|
17
19
|
UIColor *_barTint;
|
|
@@ -137,6 +139,32 @@ static UINavigationController *VisibleNavigationController(void)
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
- (void)getHeight:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
143
|
+
{
|
|
144
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
145
|
+
UINavigationController *navigation =
|
|
146
|
+
self->_owner.navigationController ?: VisibleNavigationController();
|
|
147
|
+
UIToolbar *toolbar = navigation.toolbar;
|
|
148
|
+
UIView *top = navigation.topViewController.viewIfLoaded;
|
|
149
|
+
if (toolbar == nil || top == nil) {
|
|
150
|
+
resolve(self->_measuredHeight ?: @0);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!navigation.toolbarHidden) {
|
|
154
|
+
// UIKit adds the bar's band to the screen's bottom safe area: measured there, as
|
|
155
|
+
// the bar's own frame is the whole screen on iOS 26 (it floats inside it). 0 where
|
|
156
|
+
// the bar doesn't sit under the content, e.g. a foldable's side column.
|
|
157
|
+
CGFloat band = top.safeAreaInsets.bottom - navigation.view.safeAreaInsets.bottom;
|
|
158
|
+
self->_measuredHeight = @(MAX(0, band));
|
|
159
|
+
resolve(self->_measuredHeight);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Hidden: the band last measured, else the bar's own height (without iOS 26's gap
|
|
163
|
+
// under it, which only shows once the bar does).
|
|
164
|
+
resolve(self->_measuredHeight ?: @([toolbar sizeThatFits:navigation.view.bounds.size].height));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
- (void)hide
|
|
141
169
|
{
|
|
142
170
|
// The owner is kept (weakly) for the next `show`: a modal over it hides the toolbar,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBottomToolbar.ts"],"mappings":";;AAEA,SAASA,mBAAmB,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBottomToolbar.ts"],"mappings":";;AAEA,SAASA,mBAAmB,QAAQ,cAAc;AAkClD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,eAAe,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { processColor, useWindowDimensions } from 'react-native';
|
|
4
5
|
import NativeBottomToolbar from "./NativeBottomToolbar.js";
|
|
5
6
|
/** False where the native module is missing: web, Expo Go, an unlinked build. */
|
|
6
7
|
export const isAvailable = NativeBottomToolbar != null;
|
|
@@ -39,14 +40,66 @@ export function show(items, options = {}) {
|
|
|
39
40
|
} : item), typeof tintColor === 'number' ? {
|
|
40
41
|
tintColor
|
|
41
42
|
} : {});
|
|
43
|
+
// Once the bar has slid in (and any push has landed), its band is final.
|
|
44
|
+
setTimeout(refreshHeight, SETTLE_MS);
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/** Longer than UIKit's toolbar and push animations. */
|
|
48
|
+
const SETTLE_MS = 600;
|
|
49
|
+
|
|
44
50
|
/** Hides the toolbar. */
|
|
45
51
|
export function hide() {
|
|
46
52
|
buttons = [];
|
|
47
53
|
NativeBottomToolbar?.hide();
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The room the toolbar takes at the bottom of the screen, in points: on iOS the
|
|
58
|
+
* band it adds over the content above the bottom safe area (so not the home
|
|
59
|
+
* indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
|
|
60
|
+
* column), on Android above the navigation bar. While hidden, the size it last
|
|
61
|
+
* took, so a list can keep that much room under its last row without jumping when
|
|
62
|
+
* the toolbar comes and goes. 0 without the native module.
|
|
63
|
+
*/
|
|
64
|
+
export function getHeight() {
|
|
65
|
+
return NativeBottomToolbar?.getHeight() ?? Promise.resolve(0);
|
|
66
|
+
}
|
|
67
|
+
let lastHeight = 0;
|
|
68
|
+
const heightListeners = new Set();
|
|
69
|
+
|
|
70
|
+
/** Measures the band again and tells `useBottomToolbarHeight` users if it changed. */
|
|
71
|
+
function refreshHeight() {
|
|
72
|
+
getHeight().then(height => {
|
|
73
|
+
if (height !== lastHeight) {
|
|
74
|
+
lastHeight = height;
|
|
75
|
+
heightListeners.forEach(listener => listener(height));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `getHeight`, kept up to date: measured again once a `show` has settled (the band
|
|
82
|
+
* is only exact once the bar is on screen) and when the window changes size (a
|
|
83
|
+
* rotation, a foldable opening). 0 until the first answer.
|
|
84
|
+
*/
|
|
85
|
+
export function useBottomToolbarHeight() {
|
|
86
|
+
const [height, setHeight] = useState(lastHeight);
|
|
87
|
+
const {
|
|
88
|
+
height: windowHeight,
|
|
89
|
+
width: windowWidth
|
|
90
|
+
} = useWindowDimensions();
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
heightListeners.add(setHeight);
|
|
93
|
+
return () => {
|
|
94
|
+
heightListeners.delete(setHeight);
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
refreshHeight();
|
|
99
|
+
}, [windowHeight, windowWidth]);
|
|
100
|
+
return height;
|
|
101
|
+
}
|
|
102
|
+
|
|
50
103
|
/**
|
|
51
104
|
* Calls `listener` with the id of each tapped button, in addition to that
|
|
52
105
|
* button's own `onPress`. Returns the unsubscribe function.
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["processColor","NativeBottomToolbar","isAvailable","buttons","show","items","options","some","isButton","hide","filter","tintColor","map","item","accessibilityLabel","title","onPress","undefined","type","
|
|
1
|
+
{"version":3,"names":["useEffect","useState","processColor","useWindowDimensions","NativeBottomToolbar","isAvailable","buttons","show","items","options","some","isButton","hide","filter","tintColor","map","item","accessibilityLabel","title","onPress","undefined","type","setTimeout","refreshHeight","SETTLE_MS","getHeight","Promise","resolve","lastHeight","heightListeners","Set","then","height","forEach","listener","useBottomToolbarHeight","setHeight","windowHeight","width","windowWidth","add","delete","addPressListener","subscription","onItemPress","remove","id","find","button"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -19,6 +19,11 @@ type NativeToolbarOptions = {
|
|
|
19
19
|
tintColor?: number;
|
|
20
20
|
};
|
|
21
21
|
export interface Spec extends TurboModule {
|
|
22
|
+
/**
|
|
23
|
+
* The room the toolbar takes at the bottom, in points: above the bottom safe area on
|
|
24
|
+
* iOS, above the navigation bar on Android. Its size when shown, even while hidden.
|
|
25
|
+
*/
|
|
26
|
+
getHeight(): Promise<number>;
|
|
22
27
|
hide(): void;
|
|
23
28
|
readonly onItemPress: CodegenTypes.EventEmitter<string>;
|
|
24
29
|
show(items: NativeToolbarItem[], options: NativeToolbarOptions): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeBottomToolbar.d.ts","sourceRoot":"","sources":["../../../src/NativeBottomToolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAI9D,KAAK,iBAAiB,GAAG;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvE;;AAED,wBAA8D"}
|
|
1
|
+
{"version":3,"file":"NativeBottomToolbar.d.ts","sourceRoot":"","sources":["../../../src/NativeBottomToolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAI9D,KAAK,iBAAiB,GAAG;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvE;;AAED,wBAA8D"}
|
|
@@ -20,6 +20,21 @@ export declare const isAvailable: boolean;
|
|
|
20
20
|
export declare function show(items: ToolbarItem[], options?: ToolbarOptions): void;
|
|
21
21
|
/** Hides the toolbar. */
|
|
22
22
|
export declare function hide(): void;
|
|
23
|
+
/**
|
|
24
|
+
* The room the toolbar takes at the bottom of the screen, in points: on iOS the
|
|
25
|
+
* band it adds over the content above the bottom safe area (so not the home
|
|
26
|
+
* indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
|
|
27
|
+
* column), on Android above the navigation bar. While hidden, the size it last
|
|
28
|
+
* took, so a list can keep that much room under its last row without jumping when
|
|
29
|
+
* the toolbar comes and goes. 0 without the native module.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getHeight(): Promise<number>;
|
|
32
|
+
/**
|
|
33
|
+
* `getHeight`, kept up to date: measured again once a `show` has settled (the band
|
|
34
|
+
* is only exact once the bar is on screen) and when the window changes size (a
|
|
35
|
+
* rotation, a foldable opening). 0 until the first answer.
|
|
36
|
+
*/
|
|
37
|
+
export declare function useBottomToolbarHeight(): number;
|
|
23
38
|
/**
|
|
24
39
|
* Calls `listener` with the id of each tapped button, in addition to that
|
|
25
40
|
* button's own `onPress`. Returns the unsubscribe function.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,cAAc,EAAE,MAAM,YAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,WAAW,EAAE,cAAc,EAAE,MAAM,YAAS,CAAC;AAO1E,YAAY,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,GACb,MAAM,YAAS,CAAC;AAEjB,iFAAiF;AACjF,eAAO,MAAM,WAAW,SAA8B,CAAC;AAIvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,cAAmB,QAyBtE;AAKD,yBAAyB;AACzB,wBAAgB,IAAI,SAGnB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3C;AAeD;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAa/C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,0BAG9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@27appstudio/react-native-bottom-toolbar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -24,6 +24,11 @@ type NativeToolbarOptions = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export interface Spec extends TurboModule {
|
|
27
|
+
/**
|
|
28
|
+
* The room the toolbar takes at the bottom, in points: above the bottom safe area on
|
|
29
|
+
* iOS, above the navigation bar on Android. Its size when shown, even while hidden.
|
|
30
|
+
*/
|
|
31
|
+
getHeight(): Promise<number>;
|
|
27
32
|
hide(): void;
|
|
28
33
|
readonly onItemPress: CodegenTypes.EventEmitter<string>;
|
|
29
34
|
show(items: NativeToolbarItem[], options: NativeToolbarOptions): void;
|
package/src/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ToolbarButton, ToolbarItem, ToolbarOptions } from './types';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { processColor, useWindowDimensions } from 'react-native';
|
|
4
5
|
|
|
5
6
|
import NativeBottomToolbar from './NativeBottomToolbar';
|
|
6
7
|
|
|
@@ -54,14 +55,64 @@ export function show(items: ToolbarItem[], options: ToolbarOptions = {}) {
|
|
|
54
55
|
),
|
|
55
56
|
typeof tintColor === 'number' ? { tintColor } : {}
|
|
56
57
|
);
|
|
58
|
+
// Once the bar has slid in (and any push has landed), its band is final.
|
|
59
|
+
setTimeout(refreshHeight, SETTLE_MS);
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
/** Longer than UIKit's toolbar and push animations. */
|
|
63
|
+
const SETTLE_MS = 600;
|
|
64
|
+
|
|
59
65
|
/** Hides the toolbar. */
|
|
60
66
|
export function hide() {
|
|
61
67
|
buttons = [];
|
|
62
68
|
NativeBottomToolbar?.hide();
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The room the toolbar takes at the bottom of the screen, in points: on iOS the
|
|
73
|
+
* band it adds over the content above the bottom safe area (so not the home
|
|
74
|
+
* indicator; 0 where the bar doesn't sit under the content, e.g. a foldable's side
|
|
75
|
+
* column), on Android above the navigation bar. While hidden, the size it last
|
|
76
|
+
* took, so a list can keep that much room under its last row without jumping when
|
|
77
|
+
* the toolbar comes and goes. 0 without the native module.
|
|
78
|
+
*/
|
|
79
|
+
export function getHeight(): Promise<number> {
|
|
80
|
+
return NativeBottomToolbar?.getHeight() ?? Promise.resolve(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let lastHeight = 0;
|
|
84
|
+
const heightListeners = new Set<(height: number) => void>();
|
|
85
|
+
|
|
86
|
+
/** Measures the band again and tells `useBottomToolbarHeight` users if it changed. */
|
|
87
|
+
function refreshHeight() {
|
|
88
|
+
getHeight().then((height) => {
|
|
89
|
+
if (height !== lastHeight) {
|
|
90
|
+
lastHeight = height;
|
|
91
|
+
heightListeners.forEach((listener) => listener(height));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `getHeight`, kept up to date: measured again once a `show` has settled (the band
|
|
98
|
+
* is only exact once the bar is on screen) and when the window changes size (a
|
|
99
|
+
* rotation, a foldable opening). 0 until the first answer.
|
|
100
|
+
*/
|
|
101
|
+
export function useBottomToolbarHeight(): number {
|
|
102
|
+
const [height, setHeight] = useState(lastHeight);
|
|
103
|
+
const { height: windowHeight, width: windowWidth } = useWindowDimensions();
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
heightListeners.add(setHeight);
|
|
106
|
+
return () => {
|
|
107
|
+
heightListeners.delete(setHeight);
|
|
108
|
+
};
|
|
109
|
+
}, []);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
refreshHeight();
|
|
112
|
+
}, [windowHeight, windowWidth]);
|
|
113
|
+
return height;
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
/**
|
|
66
117
|
* Calls `listener` with the id of each tapped button, in addition to that
|
|
67
118
|
* button's own `onPress`. Returns the unsubscribe function.
|