@oxyhq/bloom 0.51.0 → 0.52.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/lib/commonjs/frosted-icon-button/shared.js +11 -1
- package/lib/commonjs/frosted-icon-button/shared.js.map +1 -1
- package/lib/commonjs/tab-bar/TabBarBase.js +30 -5
- package/lib/commonjs/tab-bar/TabBarBase.js.map +1 -1
- package/lib/commonjs/tab-bar/glyph.js +9 -4
- package/lib/commonjs/tab-bar/glyph.js.map +1 -1
- package/lib/commonjs/tab-bar/glyph.native.js +7 -2
- package/lib/commonjs/tab-bar/glyph.native.js.map +1 -1
- package/lib/commonjs/tab-bar/index.js +7 -0
- package/lib/commonjs/tab-bar/index.js.map +1 -1
- package/lib/commonjs/tab-bar/index.web.js +7 -0
- package/lib/commonjs/tab-bar/index.web.js.map +1 -1
- package/lib/commonjs/tab-bar/shared.js +32 -1
- package/lib/commonjs/tab-bar/shared.js.map +1 -1
- package/lib/commonjs/tab-bar/surface-solid.js +43 -0
- package/lib/commonjs/tab-bar/surface-solid.js.map +1 -0
- package/lib/commonjs/tab-bar/surface.js +5 -42
- package/lib/commonjs/tab-bar/surface.js.map +1 -1
- package/lib/commonjs/tab-bar/surface.native.js +65 -8
- package/lib/commonjs/tab-bar/surface.native.js.map +1 -1
- package/lib/commonjs/tab-bar/use-reserved-space.js +50 -0
- package/lib/commonjs/tab-bar/use-reserved-space.js.map +1 -0
- package/lib/module/frosted-icon-button/shared.js +11 -1
- package/lib/module/frosted-icon-button/shared.js.map +1 -1
- package/lib/module/tab-bar/TabBarBase.js +31 -6
- package/lib/module/tab-bar/TabBarBase.js.map +1 -1
- package/lib/module/tab-bar/glyph.js +9 -4
- package/lib/module/tab-bar/glyph.js.map +1 -1
- package/lib/module/tab-bar/glyph.native.js +7 -2
- package/lib/module/tab-bar/glyph.native.js.map +1 -1
- package/lib/module/tab-bar/index.js +1 -0
- package/lib/module/tab-bar/index.js.map +1 -1
- package/lib/module/tab-bar/index.web.js +1 -0
- package/lib/module/tab-bar/index.web.js.map +1 -1
- package/lib/module/tab-bar/shared.js +30 -0
- package/lib/module/tab-bar/shared.js.map +1 -1
- package/lib/module/tab-bar/surface-solid.js +37 -0
- package/lib/module/tab-bar/surface-solid.js.map +1 -0
- package/lib/module/tab-bar/surface.js +10 -23
- package/lib/module/tab-bar/surface.js.map +1 -1
- package/lib/module/tab-bar/surface.native.js +68 -9
- package/lib/module/tab-bar/surface.native.js.map +1 -1
- package/lib/module/tab-bar/use-reserved-space.js +46 -0
- package/lib/module/tab-bar/use-reserved-space.js.map +1 -0
- package/lib/typescript/commonjs/benefit-list/types.d.ts +2 -1
- package/lib/typescript/commonjs/benefit-list/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/fab/types.d.ts +4 -2
- package/lib/typescript/commonjs/fab/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts +11 -1
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/types.d.ts +2 -1
- package/lib/typescript/commonjs/frosted-icon-button/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/TabBarBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/glyph.d.ts +1 -1
- package/lib/typescript/commonjs/tab-bar/glyph.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/glyph.native.d.ts +1 -1
- package/lib/typescript/commonjs/tab-bar/glyph.native.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/index.d.ts +1 -0
- package/lib/typescript/commonjs/tab-bar/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/index.web.d.ts +1 -0
- package/lib/typescript/commonjs/tab-bar/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/shared.d.ts +25 -0
- package/lib/typescript/commonjs/tab-bar/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/surface-solid.d.ts +6 -0
- package/lib/typescript/commonjs/tab-bar/surface-solid.d.ts.map +1 -0
- package/lib/typescript/commonjs/tab-bar/surface.d.ts +28 -5
- package/lib/typescript/commonjs/tab-bar/surface.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/surface.native.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/types.d.ts +43 -4
- package/lib/typescript/commonjs/tab-bar/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/use-reserved-space.d.ts +25 -0
- package/lib/typescript/commonjs/tab-bar/use-reserved-space.d.ts.map +1 -0
- package/lib/typescript/module/benefit-list/types.d.ts +2 -1
- package/lib/typescript/module/benefit-list/types.d.ts.map +1 -1
- package/lib/typescript/module/fab/types.d.ts +4 -2
- package/lib/typescript/module/fab/types.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/shared.d.ts +11 -1
- package/lib/typescript/module/frosted-icon-button/shared.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/types.d.ts +2 -1
- package/lib/typescript/module/frosted-icon-button/types.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/TabBarBase.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/glyph.d.ts +1 -1
- package/lib/typescript/module/tab-bar/glyph.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/glyph.native.d.ts +1 -1
- package/lib/typescript/module/tab-bar/glyph.native.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/index.d.ts +1 -0
- package/lib/typescript/module/tab-bar/index.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/index.web.d.ts +1 -0
- package/lib/typescript/module/tab-bar/index.web.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/shared.d.ts +25 -0
- package/lib/typescript/module/tab-bar/shared.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/surface-solid.d.ts +6 -0
- package/lib/typescript/module/tab-bar/surface-solid.d.ts.map +1 -0
- package/lib/typescript/module/tab-bar/surface.d.ts +28 -5
- package/lib/typescript/module/tab-bar/surface.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/surface.native.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/types.d.ts +43 -4
- package/lib/typescript/module/tab-bar/types.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/use-reserved-space.d.ts +25 -0
- package/lib/typescript/module/tab-bar/use-reserved-space.d.ts.map +1 -0
- package/package.json +2 -8
- package/src/__tests__/TabBar.test.tsx +82 -2
- package/src/__tests__/TabBarGlassFallback.test.tsx +234 -0
- package/src/__tests__/TabBarLongPress.test.tsx +258 -0
- package/src/__tests__/TabBarReservedSpace.test.tsx +150 -0
- package/src/__tests__/TabBarWebFork.test.ts +47 -0
- package/src/__tests__/icon-references.test.ts +243 -0
- package/src/benefit-list/types.ts +2 -1
- package/src/fab/types.ts +4 -2
- package/src/frosted-icon-button/shared.ts +11 -1
- package/src/frosted-icon-button/types.ts +2 -1
- package/src/tab-bar/TabBarBase.tsx +39 -5
- package/src/tab-bar/glyph.native.tsx +5 -2
- package/src/tab-bar/glyph.tsx +8 -4
- package/src/tab-bar/index.ts +1 -0
- package/src/tab-bar/index.web.tsx +1 -0
- package/src/tab-bar/shared.ts +38 -0
- package/src/tab-bar/surface-solid.tsx +40 -0
- package/src/tab-bar/surface.native.tsx +77 -15
- package/src/tab-bar/surface.tsx +10 -29
- package/src/tab-bar/types.ts +43 -4
- package/src/tab-bar/use-reserved-space.ts +44 -0
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Ported from expo-glass-tabs v0.1.1 — src/glass-tab-bar.tsx
|
|
3
3
|
* (MIT © 2026 David Mokos). See the top-level NOTICE.
|
|
4
4
|
*
|
|
5
|
-
* NATIVE variant of the tab bar's capsule surface — real liquid glass
|
|
5
|
+
* NATIVE variant of the tab bar's capsule surface — real liquid glass, degrading
|
|
6
|
+
* to the solid surface whenever the glass is not actually usable.
|
|
6
7
|
*
|
|
7
8
|
* `expo-glass-effect` is imported STATICALLY and this file is the only place it
|
|
8
9
|
* appears: Metro selects `.native` on iOS/Android, so the import never reaches
|
|
@@ -14,18 +15,88 @@
|
|
|
14
15
|
*
|
|
15
16
|
* `isLiquidGlassAvailable()` is false on Android and pre-iOS-26, where the
|
|
16
17
|
* package's `GlassView` has no material to render; those platforms get the same
|
|
17
|
-
* opaque fallback the neutral variant paints.
|
|
18
|
+
* opaque fallback the neutral variant paints. "Installed" is not "usable"
|
|
19
|
+
* though, and BOTH remaining hazards sit at MODULE scope, which is why neither
|
|
20
|
+
* can be handled by a branch inside the component:
|
|
21
|
+
*
|
|
22
|
+
* 1. THE NATIVE MODULE. On iOS `isLiquidGlassAvailable()` resolves
|
|
23
|
+
* `ExpoGlassEffect` through `requireNativeModule`, which THROWS
|
|
24
|
+
* `Cannot find native module …` by contract when that module is not in the
|
|
25
|
+
* binary. For these apps it is a normal deploy state rather than a
|
|
26
|
+
* misconfiguration: an OTA JS update can land on a binary built before the
|
|
27
|
+
* package was linked, while the JS side always resolves because expo-router
|
|
28
|
+
* depends on it. Unguarded, the throw escapes render and takes down the whole
|
|
29
|
+
* screen the bar is on.
|
|
30
|
+
* 2. THE EXPORTS. `Animated.createAnimatedComponent(GlassView)` runs at IMPORT
|
|
31
|
+
* time, so a tree that resolves the specifier to something without
|
|
32
|
+
* `GlassView` hands React an `undefined` element type before any render
|
|
33
|
+
* happens.
|
|
34
|
+
*
|
|
35
|
+
* `isGlassEffectAPIAvailable()` is upstream's own third condition: on some iOS 26
|
|
36
|
+
* betas `UIGlassEffect` is missing and touching `GlassView` crashes NATIVELY,
|
|
37
|
+
* below JS (expo/expo#40911). It first shipped in expo-glass-effect 0.1.9, which is
|
|
38
|
+
* exactly why the peer floor is `>=0.1.9`: this file imports the symbol STATICALLY,
|
|
39
|
+
* and a floor predating it would be the same incoherence as declaring a
|
|
40
|
+
* statically-imported package optional. The `typeof` guard below is still not
|
|
41
|
+
* redundant — a peer range only WARNS, so a consumer can resolve an older copy
|
|
42
|
+
* anyway. A missing probe therefore means "not applicable", never "unavailable";
|
|
43
|
+
* reading it the other way would delete the glass look from every working device.
|
|
44
|
+
*
|
|
45
|
+
* Everything that fails routes to the SAME solid surface Android already paints —
|
|
46
|
+
* the intended fallback path, not a new one.
|
|
18
47
|
*/
|
|
19
|
-
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
|
48
|
+
import { GlassView, isGlassEffectAPIAvailable, isLiquidGlassAvailable } from 'expo-glass-effect';
|
|
20
49
|
import { StyleSheet } from 'react-native';
|
|
21
50
|
import Animated from 'react-native-reanimated';
|
|
22
51
|
|
|
23
52
|
import type { TabBarSurfaceProps } from './shared';
|
|
53
|
+
import { SolidTabBarSurface } from './surface-solid';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The animated glass view, or `null` when the package did not deliver the two
|
|
57
|
+
* exports this file needs. Built once, and guarded HERE rather than in the
|
|
58
|
+
* component because `createAnimatedComponent` runs on import (hazard 2 above).
|
|
59
|
+
*/
|
|
60
|
+
const AnimatedGlassView =
|
|
61
|
+
Boolean(GlassView) && typeof isLiquidGlassAvailable === 'function'
|
|
62
|
+
? Animated.createAnimatedComponent(GlassView)
|
|
63
|
+
: null;
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
/** Memo for {@link glassIsUsable} — the answer cannot change within a session. */
|
|
66
|
+
let glassUsable: boolean | undefined;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether this build can really render glass. Probed once: the native lookup
|
|
70
|
+
* behind it is not free, and on the failing path it throws.
|
|
71
|
+
*
|
|
72
|
+
* The `catch` reports rather than swallows. A bar that silently painted itself
|
|
73
|
+
* opaque on every iOS device would be far more expensive to find than one warning
|
|
74
|
+
* line, and reaching this branch always means a real, fixable packaging mistake.
|
|
75
|
+
* Warning exactly once falls out of the memo — the body runs at most one time.
|
|
76
|
+
*/
|
|
77
|
+
function glassIsUsable(): boolean {
|
|
78
|
+
if (glassUsable !== undefined) return glassUsable;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
glassUsable =
|
|
82
|
+
isLiquidGlassAvailable() &&
|
|
83
|
+
(typeof isGlassEffectAPIAvailable !== 'function' || isGlassEffectAPIAvailable());
|
|
84
|
+
} catch (error) {
|
|
85
|
+
glassUsable = false;
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.warn(
|
|
88
|
+
'[Bloom] TabBar: expo-glass-effect could not report liquid-glass availability, ' +
|
|
89
|
+
'so the bar is painting its solid surface. Install expo-glass-effect and ' +
|
|
90
|
+
'rebuild the native app to get the glass capsule.',
|
|
91
|
+
error,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return glassUsable;
|
|
96
|
+
}
|
|
26
97
|
|
|
27
98
|
export function TabBarSurface({ theme, style }: TabBarSurfaceProps) {
|
|
28
|
-
if (
|
|
99
|
+
if (AnimatedGlassView !== null && glassIsUsable()) {
|
|
29
100
|
return (
|
|
30
101
|
<AnimatedGlassView
|
|
31
102
|
glassEffectStyle="regular"
|
|
@@ -39,16 +110,7 @@ export function TabBarSurface({ theme, style }: TabBarSurfaceProps) {
|
|
|
39
110
|
);
|
|
40
111
|
}
|
|
41
112
|
|
|
42
|
-
return
|
|
43
|
-
<Animated.View
|
|
44
|
-
style={[
|
|
45
|
-
StyleSheet.absoluteFill,
|
|
46
|
-
styles.surface,
|
|
47
|
-
{ backgroundColor: theme.solidFallback },
|
|
48
|
-
style,
|
|
49
|
-
]}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
113
|
+
return <SolidTabBarSurface theme={theme} style={style} />;
|
|
52
114
|
}
|
|
53
115
|
|
|
54
116
|
TabBarSurface.displayName = 'TabBarSurface';
|
package/src/tab-bar/surface.tsx
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Ported from expo-glass-tabs v0.1.1 — src/glass-tab-bar.tsx
|
|
3
3
|
* (MIT © 2026 David Mokos). See the top-level NOTICE.
|
|
4
4
|
*
|
|
5
|
-
* NEUTRAL default variant of the tab bar's capsule surface:
|
|
6
|
-
* view filled with the theme's solid fallback.
|
|
5
|
+
* NEUTRAL default variant of the tab bar's capsule surface: the non-glass one.
|
|
7
6
|
*
|
|
8
7
|
* This is the file resolved by everything that does NOT do React Native's
|
|
9
8
|
* platform-extension resolution — a consumer's `tsc` (which follows Bloom's
|
|
@@ -14,34 +13,16 @@
|
|
|
14
13
|
* resolution outright. Same three-way split, for the same reason, as
|
|
15
14
|
* `theme/native-root-vars{,.native,.web}.ts`.
|
|
16
15
|
*
|
|
16
|
+
* The surface itself lives in `surface-solid.tsx`, because `surface.native.tsx`
|
|
17
|
+
* renders it as its own fallback and cannot import this file to get it — Metro
|
|
18
|
+
* resolves `./surface` back to `surface.native.tsx`, i.e. to itself. This module's
|
|
19
|
+
* job is to give that one implementation the name every entry module imports.
|
|
20
|
+
*
|
|
17
21
|
* The variants — all three implement `TabBarSurfaceProps` exactly, so the entry
|
|
18
22
|
* modules can swap them freely:
|
|
19
|
-
* - `surface.native.tsx` — iOS/Android (Metro), real `UIGlassEffect` glass
|
|
23
|
+
* - `surface.native.tsx` — iOS/Android (Metro), real `UIGlassEffect` glass, with
|
|
24
|
+
* the solid surface as its fallback;
|
|
20
25
|
* - `surface.web.tsx` — web, a CSS `backdrop-filter`;
|
|
21
|
-
* - this file — everywhere else, the opaque fallback.
|
|
26
|
+
* - this file — everywhere else, the opaque fallback alone.
|
|
22
27
|
*/
|
|
23
|
-
|
|
24
|
-
import Animated from 'react-native-reanimated';
|
|
25
|
-
|
|
26
|
-
import type { TabBarSurfaceProps } from './shared';
|
|
27
|
-
|
|
28
|
-
export function TabBarSurface({ theme, style }: TabBarSurfaceProps) {
|
|
29
|
-
return (
|
|
30
|
-
<Animated.View
|
|
31
|
-
style={[
|
|
32
|
-
StyleSheet.absoluteFill,
|
|
33
|
-
styles.surface,
|
|
34
|
-
{ backgroundColor: theme.solidFallback },
|
|
35
|
-
style,
|
|
36
|
-
]}
|
|
37
|
-
/>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
TabBarSurface.displayName = 'TabBarSurface';
|
|
42
|
-
|
|
43
|
-
const styles = StyleSheet.create({
|
|
44
|
-
surface: {
|
|
45
|
-
borderCurve: 'continuous',
|
|
46
|
-
},
|
|
47
|
-
});
|
|
28
|
+
export { SolidTabBarSurface as TabBarSurface } from './surface-solid';
|
package/src/tab-bar/types.ts
CHANGED
|
@@ -19,13 +19,38 @@ export type TabBarItem = {
|
|
|
19
19
|
/** Text under the icon; fades out and is clipped when the bar minimizes. */
|
|
20
20
|
label: string;
|
|
21
21
|
/**
|
|
22
|
-
* The glyph, as a Bloom icon element (`<Icons.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* The glyph, as a Bloom icon element (`<Icons.Home_Stroke2_Corner0_Rounded />`)
|
|
23
|
+
* or any node. Every `@oxyhq/bloom/icons` export carries its full style and
|
|
24
|
+
* corner suffix; a shortened `Home` is not an export, and a name that does not
|
|
25
|
+
* exist is simply `undefined`, which React then rejects as an invalid element
|
|
26
|
+
* type. This is the primary icon API — same convention as `Tabs`/`TabsTrigger`
|
|
27
|
+
* and `FrostedIconButton`. The bar renders it TWICE (an inactive layer with the
|
|
25
28
|
* active layer crossfading on top), injecting the layer's tint as the icon's
|
|
26
|
-
* `fill` unless the element already sets one.
|
|
29
|
+
* `fill` unless the element already sets one. See {@link TabBarItem.activeIcon}
|
|
30
|
+
* for a set whose selected state is a different shape rather than a tint.
|
|
27
31
|
*/
|
|
28
32
|
icon: ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* Optional second node rendered by the ACTIVE crossfade layer instead of
|
|
35
|
+
* `icon`; without it that layer renders `icon` again, which is the original
|
|
36
|
+
* behaviour.
|
|
37
|
+
*
|
|
38
|
+
* For icon sets that express selection by SHAPE rather than by tint — an
|
|
39
|
+
* outline glyph that becomes a filled one, with a different path — a tint
|
|
40
|
+
* crossfade cannot say what the set says, because both layers would draw the
|
|
41
|
+
* same shape in two colors. Supplying the filled variant here makes the
|
|
42
|
+
* existing crossfade swap NODES, so the outline dissolves into the fill.
|
|
43
|
+
*
|
|
44
|
+
* It is also the escape hatch for an icon that ignores the injected tint: the
|
|
45
|
+
* bar tints a glyph by cloning it with a `fill` prop (see `applyIconColor`),
|
|
46
|
+
* so an icon set that paints from a `color` prop instead never lights up, with
|
|
47
|
+
* no error. Pre-color both nodes yourself and the crossfade still works.
|
|
48
|
+
*
|
|
49
|
+
* On iOS an item carrying `sfSymbol` renders the symbol on both layers and
|
|
50
|
+
* ignores this — a symbol is tinted natively, so the tint crossfade is the
|
|
51
|
+
* right expression there.
|
|
52
|
+
*/
|
|
53
|
+
activeIcon?: ReactNode;
|
|
29
54
|
/**
|
|
30
55
|
* Optional SF Symbol name, used INSTEAD of `icon` on iOS only. A pure
|
|
31
56
|
* enhancement handled solely in `glyph.native.tsx`; the neutral and web
|
|
@@ -60,6 +85,20 @@ export type TabBarProps = ViewProps & {
|
|
|
60
85
|
* or by keyboard/assistive-technology activation of a `TabBarButton`.
|
|
61
86
|
*/
|
|
62
87
|
onIndexChange?: (index: number) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Called with the index under the finger when a tab is pressed and HELD.
|
|
90
|
+
*
|
|
91
|
+
* This is the only way to reach a long press on the bar: its gesture detector
|
|
92
|
+
* consumes the touches (that is what makes scrubbing possible), so a button's
|
|
93
|
+
* own `onLongPress` never fires for a real finger. The gesture is added to the
|
|
94
|
+
* bar's gesture race only when this prop is supplied — holding still for half
|
|
95
|
+
* a second on a bar without it keeps behaving exactly as before, and can still
|
|
96
|
+
* start a scrub.
|
|
97
|
+
*
|
|
98
|
+
* Long-pressing does NOT move the highlight or select the tab; it is a
|
|
99
|
+
* secondary action (an account switcher on the profile tab, say).
|
|
100
|
+
*/
|
|
101
|
+
onIndexLongPress?: (index: number) => void;
|
|
63
102
|
/** Partial override of the theme resolved from Bloom's color tokens. */
|
|
64
103
|
theme?: Partial<TabBarTheme>;
|
|
65
104
|
/**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much room a screen must leave at the bottom for the floating bar.
|
|
3
|
+
*
|
|
4
|
+
* The bar is `position: absolute` over the content, so nothing reserves space
|
|
5
|
+
* for it automatically — every scrollable screen under a tab layout has to pad
|
|
6
|
+
* its own content out from under the pill. That number is built from the bar's
|
|
7
|
+
* private geometry (its expanded height and the gap it keeps from the window's
|
|
8
|
+
* bottom edge), which is exactly why it is exported as a hook: hardcoding it in
|
|
9
|
+
* an app silently drifts the moment the bar changes by a pixel.
|
|
10
|
+
*
|
|
11
|
+
* Platform-neutral, like the rest of the family — `react-native-safe-area-context`
|
|
12
|
+
* is already a required Bloom peer and is what the bar itself measures with.
|
|
13
|
+
*/
|
|
14
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
15
|
+
|
|
16
|
+
import { EXPANDED_HEIGHT, tabBarBottomGap } from './shared';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The vertical space the tab bar occupies above the bottom of the window, in
|
|
20
|
+
* px. Use it directly as the bottom padding (or bottom offset) of anything that
|
|
21
|
+
* would otherwise sit under the bar:
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const reserved = useTabBarReservedSpace();
|
|
25
|
+
*
|
|
26
|
+
* <Animated.ScrollView contentContainerStyle={{ paddingBottom: reserved }} />
|
|
27
|
+
* <Fab style={{ position: 'absolute', right: 16, bottom: reserved + 12 }} />
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* The value ALREADY INCLUDES the bottom safe-area inset — the bar folds the
|
|
31
|
+
* inset into its own bottom gap, so it is measured from the true window edge,
|
|
32
|
+
* not from the safe area. Never write `reserved + insets.bottom`: that counts
|
|
33
|
+
* the home indicator twice and strands a visible band of dead space under every
|
|
34
|
+
* list (a 76px bar reserving 110px on an iOS device or PWA). It is the whole
|
|
35
|
+
* number; nothing needs adding to it.
|
|
36
|
+
*
|
|
37
|
+
* It is the EXPANDED height, deliberately. The bar minimizes while scrolling
|
|
38
|
+
* down and re-expands on the way up, so reserving the minimized height would
|
|
39
|
+
* hide the end of a list the moment the user scrolled back to it.
|
|
40
|
+
*/
|
|
41
|
+
export function useTabBarReservedSpace(): number {
|
|
42
|
+
const insets = useSafeAreaInsets();
|
|
43
|
+
return tabBarBottomGap(insets.bottom) + EXPANDED_HEIGHT;
|
|
44
|
+
}
|