@oxyhq/bloom 0.51.1 → 0.52.1
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/bottom-sheet/BottomSheetBase.js +7 -4
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/connection-dots/ConnectionDots.web.js.map +1 -1
- package/lib/commonjs/design-tokens/shadows.js +4 -0
- package/lib/commonjs/design-tokens/shadows.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +26 -10
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/frosted-icon-button/shared.js +10 -0
- package/lib/commonjs/frosted-icon-button/shared.js.map +1 -1
- package/lib/commonjs/index.js +66 -66
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +90 -90
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/loading/Loading.web.js.map +1 -1
- package/lib/commonjs/loading/SpinnerIcon.web.js +10 -6
- package/lib/commonjs/loading/SpinnerIcon.web.js.map +1 -1
- package/lib/commonjs/motion/index.js +1 -1
- package/lib/commonjs/motion/index.web.js +32 -0
- package/lib/commonjs/motion/index.web.js.map +1 -0
- package/lib/commonjs/motion/motion.js +10 -2
- package/lib/commonjs/motion/motion.js.map +1 -1
- package/lib/commonjs/motion/motion.web.js +137 -0
- package/lib/commonjs/motion/motion.web.js.map +1 -0
- package/lib/commonjs/progressive-blur/index.web.js.map +1 -1
- package/lib/commonjs/styles/web-view-style.js +33 -0
- package/lib/commonjs/styles/web-view-style.js.map +1 -1
- package/lib/commonjs/tab-bar/TabBarBase.js +46 -7
- 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.web.js.map +1 -1
- package/lib/commonjs/tab-bar/use-footprint.js +56 -0
- package/lib/commonjs/tab-bar/use-footprint.js.map +1 -0
- package/lib/commonjs/toast/ToastRow.js +7 -3
- package/lib/commonjs/toast/ToastRow.js.map +1 -1
- package/lib/commonjs/toast/ToastSwipeHandler.js +29 -13
- package/lib/commonjs/toast/ToastSwipeHandler.js.map +1 -1
- package/lib/commonjs/toast/constants.js +29 -1
- package/lib/commonjs/toast/constants.js.map +1 -1
- package/lib/commonjs/toast/position-utils.js +9 -4
- package/lib/commonjs/toast/position-utils.js.map +1 -1
- package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js +12 -9
- package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +7 -4
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/connection-dots/ConnectionDots.web.js.map +1 -1
- package/lib/module/design-tokens/shadows.js +4 -0
- package/lib/module/design-tokens/shadows.js.map +1 -1
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +26 -10
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/frosted-icon-button/shared.js +10 -0
- package/lib/module/frosted-icon-button/shared.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/loading/Loading.web.js.map +1 -1
- package/lib/module/loading/SpinnerIcon.web.js +10 -6
- package/lib/module/loading/SpinnerIcon.web.js.map +1 -1
- package/lib/module/motion/index.js +1 -1
- package/lib/module/motion/index.js.map +1 -1
- package/lib/module/motion/index.web.js +10 -0
- package/lib/module/motion/index.web.js.map +1 -0
- package/lib/module/motion/motion.js +10 -2
- package/lib/module/motion/motion.js.map +1 -1
- package/lib/module/motion/motion.web.js +133 -0
- package/lib/module/motion/motion.web.js.map +1 -0
- package/lib/module/progressive-blur/index.web.js.map +1 -1
- package/lib/module/styles/web-view-style.js +33 -0
- package/lib/module/styles/web-view-style.js.map +1 -1
- package/lib/module/tab-bar/TabBarBase.js +47 -8
- 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.web.js.map +1 -1
- package/lib/module/tab-bar/use-footprint.js +52 -0
- package/lib/module/tab-bar/use-footprint.js.map +1 -0
- package/lib/module/toast/ToastRow.js +8 -4
- package/lib/module/toast/ToastRow.js.map +1 -1
- package/lib/module/toast/ToastSwipeHandler.js +29 -13
- package/lib/module/toast/ToastSwipeHandler.js.map +1 -1
- package/lib/module/toast/constants.js +28 -1
- package/lib/module/toast/constants.js.map +1 -1
- package/lib/module/toast/position-utils.js +9 -4
- package/lib/module/toast/position-utils.js.map +1 -1
- package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js +12 -9
- package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/shadows.d.ts +4 -0
- package/lib/typescript/commonjs/design-tokens/shadows.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts +10 -0
- package/lib/typescript/commonjs/frosted-icon-button/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -2
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/loading/Loading.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/loading/SpinnerIcon.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/index.web.d.ts +9 -0
- package/lib/typescript/commonjs/motion/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/motion.d.ts +14 -6
- package/lib/typescript/commonjs/motion/motion.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/motion.web.d.ts +79 -0
- package/lib/typescript/commonjs/motion/motion.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/progressive-blur/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/styles/web-view-style.d.ts +49 -0
- package/lib/typescript/commonjs/styles/web-view-style.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.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/types.d.ts +37 -1
- package/lib/typescript/commonjs/tab-bar/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/use-footprint.d.ts +31 -0
- package/lib/typescript/commonjs/tab-bar/use-footprint.d.ts.map +1 -0
- package/lib/typescript/commonjs/toast/ToastRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/ToastSwipeHandler.d.ts +12 -6
- package/lib/typescript/commonjs/toast/ToastSwipeHandler.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/constants.d.ts +26 -8
- package/lib/typescript/commonjs/toast/constants.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/position-utils.d.ts +8 -3
- package/lib/typescript/commonjs/toast/position-utils.d.ts.map +1 -1
- package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/connection-dots/ConnectionDots.web.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/shadows.d.ts +4 -0
- package/lib/typescript/module/design-tokens/shadows.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/frosted-icon-button/shared.d.ts +10 -0
- package/lib/typescript/module/frosted-icon-button/shared.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -2
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/loading/Loading.web.d.ts.map +1 -1
- package/lib/typescript/module/loading/SpinnerIcon.web.d.ts.map +1 -1
- package/lib/typescript/module/motion/index.web.d.ts +9 -0
- package/lib/typescript/module/motion/index.web.d.ts.map +1 -0
- package/lib/typescript/module/motion/motion.d.ts +14 -6
- package/lib/typescript/module/motion/motion.d.ts.map +1 -1
- package/lib/typescript/module/motion/motion.web.d.ts +79 -0
- package/lib/typescript/module/motion/motion.web.d.ts.map +1 -0
- package/lib/typescript/module/progressive-blur/index.web.d.ts.map +1 -1
- package/lib/typescript/module/styles/web-view-style.d.ts +49 -0
- package/lib/typescript/module/styles/web-view-style.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.web.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/types.d.ts +37 -1
- package/lib/typescript/module/tab-bar/types.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/use-footprint.d.ts +31 -0
- package/lib/typescript/module/tab-bar/use-footprint.d.ts.map +1 -0
- package/lib/typescript/module/toast/ToastRow.d.ts.map +1 -1
- package/lib/typescript/module/toast/ToastSwipeHandler.d.ts +12 -6
- package/lib/typescript/module/toast/ToastSwipeHandler.d.ts.map +1 -1
- package/lib/typescript/module/toast/constants.d.ts +26 -8
- package/lib/typescript/module/toast/constants.d.ts.map +1 -1
- package/lib/typescript/module/toast/position-utils.d.ts +8 -3
- package/lib/typescript/module/toast/position-utils.d.ts.map +1 -1
- package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__tests__/Motion.test.tsx +31 -3
- package/src/__tests__/MotionWebFork.test.ts +140 -0
- package/src/__tests__/TabBar.test.tsx +82 -2
- package/src/__tests__/TabBarAnimatedShorthand.test.ts +135 -0
- package/src/__tests__/TabBarFootprint.test.tsx +172 -0
- package/src/__tests__/TabBarLongPress.test.tsx +258 -0
- package/src/__tests__/TabBarWebFork.test.ts +1 -0
- package/src/__tests__/ToastHostWebFork.test.ts +6 -2
- package/src/__tests__/Toaster.test.tsx +62 -1
- package/src/__tests__/toast-position-utils.test.ts +23 -7
- package/src/__tests__/web-css-style.test.ts +139 -0
- package/src/bottom-sheet/BottomSheetBase.tsx +11 -7
- package/src/connection-dots/ConnectionDots.web.tsx +4 -3
- package/src/design-tokens/shadows.ts +5 -1
- package/src/dialog/Dialog.tsx +1 -1
- package/src/dialog/Dialog.web.tsx +41 -26
- package/src/frosted-icon-button/shared.ts +10 -0
- package/src/index.web.ts +2 -2
- package/src/loading/Loading.web.tsx +7 -5
- package/src/loading/SpinnerIcon.web.tsx +12 -9
- package/src/motion/index.web.ts +8 -0
- package/src/motion/motion.ts +14 -6
- package/src/motion/motion.web.ts +99 -0
- package/src/progressive-blur/index.web.tsx +4 -3
- package/src/styles/web-view-style.ts +50 -0
- package/src/tab-bar/TabBarBase.tsx +54 -15
- 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.web.tsx +4 -3
- package/src/tab-bar/types.ts +37 -1
- package/src/tab-bar/use-footprint.ts +50 -0
- package/src/toast/ToastRow.tsx +8 -3
- package/src/toast/ToastSwipeHandler.tsx +30 -12
- package/src/toast/constants.ts +28 -0
- package/src/toast/position-utils.ts +10 -5
- package/src/zoomable-image-gallery/ZoomableImageGallery.tsx +9 -8
package/src/tab-bar/glyph.tsx
CHANGED
|
@@ -2,8 +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
|
-
* NEUTRAL default variant of a tab glyph: renders the item's own `icon` node
|
|
6
|
-
* tinted for this crossfade layer.
|
|
5
|
+
* NEUTRAL default variant of a tab glyph: renders the item's own `icon` node —
|
|
6
|
+
* or its `activeIcon` on the active layer — tinted for this crossfade layer.
|
|
7
7
|
*
|
|
8
8
|
* `item.icon` is the primary icon API for the whole family — a Bloom icon
|
|
9
9
|
* element, exactly like `TabsTrigger` and `FrostedIconButton` take. `sfSymbol`
|
|
@@ -17,13 +17,17 @@ import { View } from 'react-native';
|
|
|
17
17
|
import { applyIconColor } from '../frosted-icon-button/shared';
|
|
18
18
|
import type { TabBarGlyphProps } from './shared';
|
|
19
19
|
|
|
20
|
-
export function TabBarGlyph({ item, tint, size }: TabBarGlyphProps) {
|
|
20
|
+
export function TabBarGlyph({ item, tint, size, active }: TabBarGlyphProps) {
|
|
21
21
|
// The tint is injected as the icon's `fill` FALLBACK — an icon that sets its
|
|
22
22
|
// own `fill` (a brand mark, say) keeps it, so the crossfade layers simply
|
|
23
23
|
// render it twice at full opacity and it never flickers.
|
|
24
|
+
//
|
|
25
|
+
// `activeIcon` is what makes the crossfade able to swap NODES: an icon set
|
|
26
|
+
// whose selected state is a different SHAPE (outline → filled) cannot be
|
|
27
|
+
// expressed by tinting one node two ways. Absent, the layer renders `icon`.
|
|
24
28
|
return (
|
|
25
29
|
<View style={{ height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
26
|
-
{applyIconColor(item.icon, tint)}
|
|
30
|
+
{applyIconColor(active ? (item.activeIcon ?? item.icon) : item.icon, tint)}
|
|
27
31
|
</View>
|
|
28
32
|
);
|
|
29
33
|
}
|
package/src/tab-bar/index.ts
CHANGED
|
@@ -29,4 +29,5 @@ export {
|
|
|
29
29
|
useTabBarMinimized,
|
|
30
30
|
} from './minimize-context';
|
|
31
31
|
export type { MinimizeState } from './minimize-context';
|
|
32
|
+
export { useTabBarFootprint } from './use-footprint';
|
|
32
33
|
export type { TabBarButtonProps, TabBarItem, TabBarProps, TabBarTheme } from './types';
|
|
@@ -31,4 +31,5 @@ export {
|
|
|
31
31
|
useTabBarMinimized,
|
|
32
32
|
} from './minimize-context';
|
|
33
33
|
export type { MinimizeState } from './minimize-context';
|
|
34
|
+
export { useTabBarFootprint } from './use-footprint';
|
|
34
35
|
export type { TabBarButtonProps, TabBarItem, TabBarProps, TabBarTheme } from './types';
|
package/src/tab-bar/shared.ts
CHANGED
|
@@ -51,6 +51,27 @@ export const BLUR_BLEED = 44;
|
|
|
51
51
|
/** Label type size. Small by design — the icon carries the meaning. */
|
|
52
52
|
export const LABEL_FONT_SIZE = 9.5;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* How much of the bottom safe-area inset the bar's own gap absorbs. The pill
|
|
56
|
+
* already floats clear of the content, so sitting the FULL inset below it
|
|
57
|
+
* strands it in the middle of the screen on a home-indicator device.
|
|
58
|
+
*/
|
|
59
|
+
const SAFE_AREA_ABSORBED = 16;
|
|
60
|
+
/** Gap floor, for a device (or a browser) reporting no bottom inset at all. */
|
|
61
|
+
const MIN_BOTTOM_GAP = 12;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Vertical gap between the bottom of the pill and the bottom of the window.
|
|
65
|
+
*
|
|
66
|
+
* The safe-area inset is folded in HERE, which is why anything accounting for
|
|
67
|
+
* the bar in its own layout must go through `useTabBarFootprint` (which derives
|
|
68
|
+
* its number from this same function) rather than adding `insets.bottom` to a
|
|
69
|
+
* hardcoded height — that double-counts the inset.
|
|
70
|
+
*/
|
|
71
|
+
export function tabBarBottomGap(bottomInset: number): number {
|
|
72
|
+
return Math.max(bottomInset - SAFE_AREA_ABSORBED, MIN_BOTTOM_GAP);
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
/**
|
|
55
76
|
* Slide spring: interruptible by design — rapid tab-hopping retargets with
|
|
56
77
|
* preserved velocity. Slight under-damping gives the pill a tiny settle, safe
|
|
@@ -58,6 +79,15 @@ export const LABEL_FONT_SIZE = 9.5;
|
|
|
58
79
|
*/
|
|
59
80
|
export const SLIDE_SPRING = { duration: 420, dampingRatio: 0.82 };
|
|
60
81
|
|
|
82
|
+
/**
|
|
83
|
+
* How long a press must be held before `onIndexLongPress` fires.
|
|
84
|
+
*
|
|
85
|
+
* Comfortably above the tap gesture's own 400ms ceiling: the two can never both
|
|
86
|
+
* be candidates at the same instant, so arming the long press cannot cost the
|
|
87
|
+
* bar a tap. Matches the platform long-press feel (iOS ~0.5s).
|
|
88
|
+
*/
|
|
89
|
+
export const LONG_PRESS_MIN_DURATION = 500;
|
|
90
|
+
|
|
61
91
|
// ---------------------------------------------------------------------------
|
|
62
92
|
// Theme
|
|
63
93
|
// ---------------------------------------------------------------------------
|
|
@@ -156,4 +186,12 @@ export interface TabBarGlyphProps {
|
|
|
156
186
|
tint: string;
|
|
157
187
|
/** Rendered icon size in px ({@link ICON_SIZE}). */
|
|
158
188
|
size: number;
|
|
189
|
+
/**
|
|
190
|
+
* Which crossfade layer this is: `false` for the inactive one underneath,
|
|
191
|
+
* `true` for the one fading in on top. The active layer renders
|
|
192
|
+
* `item.activeIcon` when the item has one, so a set that expresses selection
|
|
193
|
+
* by shape (outline → filled) crossfades between two different nodes rather
|
|
194
|
+
* than between two tints of the same node.
|
|
195
|
+
*/
|
|
196
|
+
active: boolean;
|
|
159
197
|
}
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
* Browsers without `backdrop-filter` simply skip it and are left with the
|
|
20
20
|
* translucent tint — a graceful degradation that needs no `@supports` rule.
|
|
21
21
|
*/
|
|
22
|
-
import { StyleSheet
|
|
22
|
+
import { StyleSheet } from 'react-native';
|
|
23
23
|
import Animated from 'react-native-reanimated';
|
|
24
24
|
|
|
25
|
+
import type { WebCssStyle } from '../styles/web-view-style';
|
|
25
26
|
import type { TabBarSurfaceProps } from './shared';
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -35,10 +36,10 @@ const GLASS_FILTER = `blur(${GLASS_BLUR_PX}px) saturate(180%)`;
|
|
|
35
36
|
|
|
36
37
|
// Web-only CSS, unknown to React Native's `ViewStyle`. Both spellings are set:
|
|
37
38
|
// Safari still needs the prefixed property for `backdrop-filter`.
|
|
38
|
-
const glassFilterStyle = {
|
|
39
|
+
const glassFilterStyle: WebCssStyle = {
|
|
39
40
|
backdropFilter: GLASS_FILTER,
|
|
40
41
|
WebkitBackdropFilter: GLASS_FILTER,
|
|
41
|
-
}
|
|
42
|
+
};
|
|
42
43
|
|
|
43
44
|
export function TabBarSurface({ theme, style }: TabBarSurfaceProps) {
|
|
44
45
|
return (
|
package/src/tab-bar/types.ts
CHANGED
|
@@ -26,9 +26,31 @@ export type TabBarItem = {
|
|
|
26
26
|
* type. This is the primary icon API — same convention as `Tabs`/`TabsTrigger`
|
|
27
27
|
* and `FrostedIconButton`. The bar renders it TWICE (an inactive layer with the
|
|
28
28
|
* active layer crossfading on top), injecting the layer's tint as the icon's
|
|
29
|
-
* `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.
|
|
30
31
|
*/
|
|
31
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;
|
|
32
54
|
/**
|
|
33
55
|
* Optional SF Symbol name, used INSTEAD of `icon` on iOS only. A pure
|
|
34
56
|
* enhancement handled solely in `glyph.native.tsx`; the neutral and web
|
|
@@ -63,6 +85,20 @@ export type TabBarProps = ViewProps & {
|
|
|
63
85
|
* or by keyboard/assistive-technology activation of a `TabBarButton`.
|
|
64
86
|
*/
|
|
65
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;
|
|
66
102
|
/** Partial override of the theme resolved from Bloom's color tokens. */
|
|
67
103
|
theme?: Partial<TabBarTheme>;
|
|
68
104
|
/**
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much room the floating bar physically takes at the bottom of the window.
|
|
3
|
+
*
|
|
4
|
+
* The bar is `position: absolute` over the content, so nothing accounts for it
|
|
5
|
+
* automatically — every screen under a tab layout has to keep its own content
|
|
6
|
+
* out from under the pill. That number is built from the bar's private geometry
|
|
7
|
+
* (its expanded height and the gap it holds off the window's bottom edge),
|
|
8
|
+
* which is exactly why it is exported as a hook: hardcoding it in an app
|
|
9
|
+
* 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: its own height plus its own bottom offset, and nothing else.
|
|
21
|
+
*
|
|
22
|
+
* It is deliberately the raw footprint, with no clearance folded in. Clearance
|
|
23
|
+
* is a consumer decision and it differs per call site — a scroll view wants the
|
|
24
|
+
* footprint plus a breathing gap so the last row clears the pill, while a
|
|
25
|
+
* floating action button anchored by `bottom` already supplies its own gap and
|
|
26
|
+
* wants the footprint bare. Baking a margin in here would place that FAB too
|
|
27
|
+
* high and make one word mean two things inside a single file. Add your own:
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const footprint = useTabBarFootprint();
|
|
31
|
+
*
|
|
32
|
+
* <Animated.ScrollView contentContainerStyle={{ paddingBottom: footprint + 12 }} />
|
|
33
|
+
* <Fab style={{ position: 'absolute', right: 16, bottom: footprint }} />
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* The value ALREADY INCLUDES the bottom safe-area inset — the bar folds the
|
|
37
|
+
* inset into its own gap, so this is measured from the true window edge, not
|
|
38
|
+
* from the safe area. Never write `footprint + insets.bottom`: that counts the
|
|
39
|
+
* home indicator twice and strands a visible band of dead space under every
|
|
40
|
+
* list (114px accounted for a 76px bar on an iOS device or PWA). Whatever you
|
|
41
|
+
* add on top is your own clearance — never the inset a second time.
|
|
42
|
+
*
|
|
43
|
+
* It is the EXPANDED height, deliberately. The bar minimizes while scrolling
|
|
44
|
+
* down and re-expands on the way up, so measuring the minimized height would
|
|
45
|
+
* hide the end of a list the moment the user scrolled back to it.
|
|
46
|
+
*/
|
|
47
|
+
export function useTabBarFootprint(): number {
|
|
48
|
+
const insets = useSafeAreaInsets();
|
|
49
|
+
return tabBarBottomGap(insets.bottom) + EXPANDED_HEIGHT;
|
|
50
|
+
}
|
package/src/toast/ToastRow.tsx
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
CLOSE_BUTTON_HIT_AREA,
|
|
56
56
|
ENTERING_ANIMATION_DURATION,
|
|
57
57
|
STACKING_ANIMATION_DURATION,
|
|
58
|
+
TOAST_MAX_ROW_WIDTH,
|
|
58
59
|
toastDefaults,
|
|
59
60
|
} from './constants';
|
|
60
61
|
import { useDynamicToastContext, useToastContext } from './context';
|
|
@@ -224,6 +225,8 @@ export const ToastRow = React.forwardRef<ToastRef, ToastRowProps>(
|
|
|
224
225
|
});
|
|
225
226
|
|
|
226
227
|
const { width: screenWidth } = useWindowDimensions();
|
|
228
|
+
// The row is `width: '100%'` up to the cap, so this is its real width.
|
|
229
|
+
const rowWidth = Math.min(screenWidth, TOAST_MAX_ROW_WIDTH);
|
|
227
230
|
|
|
228
231
|
const stackScaleX = useAnimatedTarget(
|
|
229
232
|
calculateStackScaleX({
|
|
@@ -233,7 +236,7 @@ export const ToastRow = React.forwardRef<ToastRef, ToastRowProps>(
|
|
|
233
236
|
position,
|
|
234
237
|
isExpanded,
|
|
235
238
|
stackGap,
|
|
236
|
-
|
|
239
|
+
rowWidth,
|
|
237
240
|
}),
|
|
238
241
|
{ duration: STACKING_ANIMATION_DURATION, easing: easeOutQuartFn },
|
|
239
242
|
);
|
|
@@ -356,7 +359,9 @@ export const ToastRow = React.forwardRef<ToastRef, ToastRowProps>(
|
|
|
356
359
|
if (enableStacking && numberOfToasts > 1 && position !== 'center') {
|
|
357
360
|
// On Android the RNGH tap wins the race against the nested Pressable,
|
|
358
361
|
// so a press in the close-button strip has to dismiss explicitly.
|
|
359
|
-
|
|
362
|
+
// `x` is relative to the row, which is capped — measuring the strip
|
|
363
|
+
// from the window edge would put it outside the row on a wide viewport.
|
|
364
|
+
if (x > rowWidth - CLOSE_BUTTON_HIT_AREA) {
|
|
360
365
|
if (isExpanded && closeButton && dismissible) {
|
|
361
366
|
onDismiss(id);
|
|
362
367
|
}
|
|
@@ -370,7 +375,7 @@ export const ToastRow = React.forwardRef<ToastRef, ToastRowProps>(
|
|
|
370
375
|
enableStacking,
|
|
371
376
|
numberOfToasts,
|
|
372
377
|
position,
|
|
373
|
-
|
|
378
|
+
rowWidth,
|
|
374
379
|
isExpanded,
|
|
375
380
|
closeButton,
|
|
376
381
|
dismissible,
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
* Derived from sonner-native v0.26.4 — src/gestures.tsx
|
|
3
3
|
* (MIT © Gunnar Torfi Steinarsson). See the top-level NOTICE.
|
|
4
4
|
*
|
|
5
|
-
* W2 — the animated style reads `translate`, `direction`, `position`,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* W2 — the animated style reads `translate`, `direction`, `position`, `rowWidth`
|
|
6
|
+
* and `isAndroid`; ALL of them are in the dependency array. On web without the
|
|
7
|
+
* worklets babel plugin reanimated re-runs the mapper on its deps rather than on
|
|
8
|
+
* auto-tracked reads, so a missing dep freezes the swipe transform at its first
|
|
9
|
+
* frame (upstream omits `position` and `isAndroid`, so a toast that changes
|
|
10
|
+
* position mid-life swipes the wrong way).
|
|
11
|
+
*
|
|
12
|
+
* W12 — this wrapper is THE row box, so it is where `TOAST_MAX_ROW_WIDTH` caps
|
|
13
|
+
* and centres the toast. Capping here rather than on `ToastContent`'s card
|
|
14
|
+
* covers `toast.custom` JSX and `unstyled` rows too, which never reach that
|
|
15
|
+
* card. Every distance the gesture measures is a fraction of the capped
|
|
16
|
+
* `rowWidth`, not of the window — see the constant.
|
|
11
17
|
*/
|
|
12
18
|
import * as React from 'react';
|
|
13
19
|
import {
|
|
@@ -28,10 +34,11 @@ import Animated, {
|
|
|
28
34
|
} from 'react-native-reanimated';
|
|
29
35
|
|
|
30
36
|
import { easeInOutCircFn } from './animations';
|
|
37
|
+
import { TOAST_MAX_ROW_WIDTH } from './constants';
|
|
31
38
|
import { useToastContext } from './context';
|
|
32
39
|
import type { ToastPosition, ToastProps } from './types';
|
|
33
40
|
|
|
34
|
-
/** Fraction of the
|
|
41
|
+
/** Fraction of the row a horizontal swipe must cross to dismiss. */
|
|
35
42
|
const HORIZONTAL_DISMISS_FRACTION = 0.25;
|
|
36
43
|
/** Below this the gesture reads as a tap, so the row springs back. */
|
|
37
44
|
const TAP_SLOP = 16;
|
|
@@ -66,6 +73,8 @@ export const ToastSwipeHandler: React.FC<
|
|
|
66
73
|
onPress,
|
|
67
74
|
}) => {
|
|
68
75
|
const { width: windowWidth } = useWindowDimensions();
|
|
76
|
+
// The row is `width: '100%'` up to the cap, so this is its real width.
|
|
77
|
+
const rowWidth = Math.min(windowWidth, TOAST_MAX_ROW_WIDTH);
|
|
69
78
|
const translate = useSharedValue(0);
|
|
70
79
|
const { swipeToDismissDirection: direction, position: positionCtx } =
|
|
71
80
|
useToastContext();
|
|
@@ -137,7 +146,7 @@ export const ToastSwipeHandler: React.FC<
|
|
|
137
146
|
if (direction === 'left') {
|
|
138
147
|
if (Math.abs(translate.value) < TAP_SLOP) {
|
|
139
148
|
springBack();
|
|
140
|
-
} else if (translate.value < -
|
|
149
|
+
} else if (translate.value < -rowWidth * HORIZONTAL_DISMISS_FRACTION) {
|
|
141
150
|
dismiss();
|
|
142
151
|
} else {
|
|
143
152
|
springBack();
|
|
@@ -175,11 +184,11 @@ export const ToastSwipeHandler: React.FC<
|
|
|
175
184
|
? 1
|
|
176
185
|
: interpolate(
|
|
177
186
|
translate.value,
|
|
178
|
-
[0, direction === 'left' ? -
|
|
187
|
+
[0, direction === 'left' ? -rowWidth : -VERTICAL_FADE_DISTANCE],
|
|
179
188
|
[1, 0],
|
|
180
189
|
),
|
|
181
190
|
};
|
|
182
|
-
}, [direction, translate,
|
|
191
|
+
}, [direction, translate, rowWidth, position, isAndroid]);
|
|
183
192
|
|
|
184
193
|
return (
|
|
185
194
|
<GestureDetector gesture={Gesture.Race(tap, pan)}>
|
|
@@ -187,7 +196,7 @@ export const ToastSwipeHandler: React.FC<
|
|
|
187
196
|
style={[
|
|
188
197
|
animatedStyle,
|
|
189
198
|
unstyled ? undefined : styles.centered,
|
|
190
|
-
styles.
|
|
199
|
+
styles.rowBox,
|
|
191
200
|
style,
|
|
192
201
|
]}
|
|
193
202
|
// W9 — on web `LinearTransition`'s easing degrades to CSS `ease` because
|
|
@@ -205,7 +214,16 @@ export const ToastSwipeHandler: React.FC<
|
|
|
205
214
|
|
|
206
215
|
const styles = StyleSheet.create({
|
|
207
216
|
centered: { justifyContent: 'center' },
|
|
208
|
-
|
|
217
|
+
/**
|
|
218
|
+
* `alignSelf` is load-bearing: the parent anchor stretches its children, but a
|
|
219
|
+
* child with a definite cross size (`width: '100%'` capped by `maxWidth`) is
|
|
220
|
+
* not stretched — it would sit against the left edge without this.
|
|
221
|
+
*/
|
|
222
|
+
rowBox: {
|
|
223
|
+
width: '100%',
|
|
224
|
+
maxWidth: TOAST_MAX_ROW_WIDTH,
|
|
225
|
+
alignSelf: 'center',
|
|
226
|
+
},
|
|
209
227
|
});
|
|
210
228
|
|
|
211
229
|
/**
|
package/src/toast/constants.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `toast-store.ts` never has to import a Reanimated-dependent module: the store
|
|
7
7
|
* stays pure JS and its suite needs zero React Native mocks.
|
|
8
8
|
*/
|
|
9
|
+
import { space } from '../styles/tokens';
|
|
9
10
|
import type {
|
|
10
11
|
AutoWiggle,
|
|
11
12
|
ToastPosition,
|
|
@@ -21,6 +22,33 @@ export const OUTSIDE_PRESS_PADDING = 20;
|
|
|
21
22
|
/** However deep a stack gets, a buried row never narrows past this scale. */
|
|
22
23
|
export const MIN_STACK_SCALE_X = 0.8;
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* W12 — HOW WIDE A TOAST ROW IS ALLOWED TO GET.
|
|
27
|
+
*
|
|
28
|
+
* sonner-native has no cap: the row is `width: '100%'` and the card inside it is
|
|
29
|
+
* inset by `marginHorizontal: space.lg`, so the card is always
|
|
30
|
+
* screen-width-minus-32. That is correct for the platform it was written for and
|
|
31
|
+
* wrong everywhere else — on a 1280px desktop viewport it produces a ~1248px
|
|
32
|
+
* card, which reads as a page banner rather than a toast. sonner (web) instead
|
|
33
|
+
* fixes the card at `--width: 356px`.
|
|
34
|
+
*
|
|
35
|
+
* The cap therefore sizes the ROW BOX, which carries the card's two `space.lg`
|
|
36
|
+
* gutters, so the visible card lands on sonner's 356px on any viewport wide
|
|
37
|
+
* enough to reach it. `maxWidth` needs no platform fork because it degrades by
|
|
38
|
+
* itself: below a 388px viewport the row is still screen-width and the card is
|
|
39
|
+
* still screen-width-minus-32, exactly as before, so phone portrait — the case
|
|
40
|
+
* the mobile sizing was right for — is untouched. Above it (desktop, tablet,
|
|
41
|
+
* phone landscape) the row caps and centres.
|
|
42
|
+
*
|
|
43
|
+
* `ToastSwipeHandler`'s dismiss threshold and `calculateStackScaleX`'s depth cue
|
|
44
|
+
* are fractions of this row width, NOT of the window: at 1280px a
|
|
45
|
+
* window-relative threshold would demand a 320px drag from a 388px card, and a
|
|
46
|
+
* window-relative squeeze would shrink a buried row by 4px instead of 16px.
|
|
47
|
+
* A consumer who overrides the cap through `toastOptions.toastContainerStyle`
|
|
48
|
+
* moves the card only; both of those keep measuring against this default.
|
|
49
|
+
*/
|
|
50
|
+
export const TOAST_MAX_ROW_WIDTH = 356 + space.lg * 2;
|
|
51
|
+
|
|
24
52
|
/** Enter animation length; also the auto-close timer's head start and the overlay teardown delay. */
|
|
25
53
|
export const ENTERING_ANIMATION_DURATION = 300;
|
|
26
54
|
export const STACKING_ANIMATION_DURATION = 600;
|
|
@@ -34,7 +34,12 @@ export const getOrderedToastIds = (
|
|
|
34
34
|
*
|
|
35
35
|
* Scale rather than width: changing the real width would rewrap the text. Returns
|
|
36
36
|
* 1 (no squeeze) for a single row, an expanded stack, stacking turned off, or
|
|
37
|
-
* before the
|
|
37
|
+
* before the row width is known.
|
|
38
|
+
*
|
|
39
|
+
* `rowWidth` is the row's OWN width, not the window's. Upstream can use the
|
|
40
|
+
* screen width because its row is always full-bleed; Bloom's is capped at
|
|
41
|
+
* `TOAST_MAX_ROW_WIDTH`, and dividing a fixed `stackGap` inset by a 1280px window
|
|
42
|
+
* would shrink a buried row by 4 visible pixels instead of the intended 16.
|
|
38
43
|
*/
|
|
39
44
|
export const calculateStackScaleX = ({
|
|
40
45
|
index,
|
|
@@ -43,7 +48,7 @@ export const calculateStackScaleX = ({
|
|
|
43
48
|
position,
|
|
44
49
|
isExpanded,
|
|
45
50
|
stackGap,
|
|
46
|
-
|
|
51
|
+
rowWidth,
|
|
47
52
|
}: {
|
|
48
53
|
index: number;
|
|
49
54
|
numberOfToasts: number;
|
|
@@ -51,16 +56,16 @@ export const calculateStackScaleX = ({
|
|
|
51
56
|
position: ToastPosition;
|
|
52
57
|
isExpanded: boolean;
|
|
53
58
|
stackGap: number;
|
|
54
|
-
|
|
59
|
+
rowWidth: number;
|
|
55
60
|
}): number => {
|
|
56
|
-
if (!enableStacking || numberOfToasts <= 1 || isExpanded ||
|
|
61
|
+
if (!enableStacking || numberOfToasts <= 1 || isExpanded || rowWidth <= 0) {
|
|
57
62
|
return 1;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
const distanceFromFront =
|
|
61
66
|
position === 'top-center' ? index : numberOfToasts - index - 1;
|
|
62
67
|
const narrowAmount = stackGap * distanceFromFront * 2;
|
|
63
|
-
return Math.max(MIN_STACK_SCALE_X, 1 - narrowAmount /
|
|
68
|
+
return Math.max(MIN_STACK_SCALE_X, 1 - narrowAmount / rowWidth);
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
const heightOf = (
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
ArrowOutOfBox_Stroke2_Corner0_Rounded,
|
|
37
37
|
} from '../icons';
|
|
38
38
|
import { Z_INDEX } from '../styles/z-index';
|
|
39
|
-
import { WEB_POSITION_FIXED } from '../styles/web-view-style';
|
|
39
|
+
import { WEB_POSITION_FIXED, type WebCssStyle } from '../styles/web-view-style';
|
|
40
40
|
import {
|
|
41
41
|
getAspectRatio,
|
|
42
42
|
fetchAspectRatio,
|
|
@@ -76,14 +76,15 @@ const AnimatedImage = Animated.createAnimatedComponent(Image);
|
|
|
76
76
|
|
|
77
77
|
// Web-only interaction hints (no-ops on native): the zoom surfaces are
|
|
78
78
|
// tap-to-dismiss (`cursor: pointer`) and must not select text or drag the
|
|
79
|
-
// image. `Platform.select` keeps them off native
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// image. `Platform.select` keeps them off native. `cursor` is a real
|
|
80
|
+
// `ViewStyle` key on RN 0.83; `userSelect` is declared on `TextStyle` only,
|
|
81
|
+
// which is what `WebCssStyle` covers.
|
|
82
|
+
const WEB_POINTER: WebCssStyle = { userSelect: 'none', cursor: 'pointer' };
|
|
83
|
+
const WEB_USER_SELECT_NONE: WebCssStyle = { userSelect: 'none' };
|
|
84
|
+
|
|
85
|
+
const webPointerStyle = Platform.select({ web: WEB_POINTER, default: undefined });
|
|
85
86
|
const webUserSelectNoneStyle = Platform.select({
|
|
86
|
-
web:
|
|
87
|
+
web: WEB_USER_SELECT_NONE,
|
|
87
88
|
default: undefined,
|
|
88
89
|
});
|
|
89
90
|
|