@oxyhq/bloom 0.52.0 → 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/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 +18 -4
- package/lib/commonjs/tab-bar/TabBarBase.js.map +1 -1
- package/lib/commonjs/tab-bar/index.js +5 -5
- package/lib/commonjs/tab-bar/index.js.map +1 -1
- package/lib/commonjs/tab-bar/index.web.js +5 -5
- package/lib/commonjs/tab-bar/index.web.js.map +1 -1
- package/lib/commonjs/tab-bar/shared.js +3 -3
- 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/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 +18 -4
- package/lib/module/tab-bar/TabBarBase.js.map +1 -1
- package/lib/module/tab-bar/index.js +1 -1
- package/lib/module/tab-bar/index.js.map +1 -1
- package/lib/module/tab-bar/index.web.js +1 -1
- package/lib/module/tab-bar/index.web.js.map +1 -1
- package/lib/module/tab-bar/shared.js +3 -3
- 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/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/index.d.ts +1 -1
- package/lib/typescript/commonjs/tab-bar/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/index.web.d.ts +1 -1
- package/lib/typescript/commonjs/tab-bar/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/shared.d.ts +3 -3
- package/lib/typescript/commonjs/tab-bar/surface.web.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/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/index.d.ts +1 -1
- package/lib/typescript/module/tab-bar/index.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/index.web.d.ts +1 -1
- package/lib/typescript/module/tab-bar/index.web.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/shared.d.ts +3 -3
- package/lib/typescript/module/tab-bar/surface.web.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__/TabBarAnimatedShorthand.test.ts +135 -0
- package/src/__tests__/TabBarFootprint.test.tsx +172 -0
- package/src/__tests__/TabBarWebFork.test.ts +1 -1
- 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/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 +17 -12
- package/src/tab-bar/index.ts +1 -1
- package/src/tab-bar/index.web.tsx +1 -1
- package/src/tab-bar/shared.ts +3 -3
- package/src/tab-bar/surface.web.tsx +4 -3
- 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/lib/commonjs/tab-bar/use-reserved-space.js +0 -50
- package/lib/commonjs/tab-bar/use-reserved-space.js.map +0 -1
- package/lib/module/tab-bar/use-reserved-space.js +0 -46
- package/lib/module/tab-bar/use-reserved-space.js.map +0 -1
- package/lib/typescript/commonjs/tab-bar/use-reserved-space.d.ts +0 -25
- package/lib/typescript/commonjs/tab-bar/use-reserved-space.d.ts.map +0 -1
- package/lib/typescript/module/tab-bar/use-reserved-space.d.ts +0 -25
- package/lib/typescript/module/tab-bar/use-reserved-space.d.ts.map +0 -1
- package/src/__tests__/TabBarReservedSpace.test.tsx +0 -150
- package/src/tab-bar/use-reserved-space.ts +0 -44
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { memo, useEffect, useState } from 'react';
|
|
2
2
|
import { View, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import type { WebCssStyle } from '../styles/web-view-style';
|
|
4
5
|
import { useTheme } from '../theme/use-theme';
|
|
5
6
|
import { ConnectionDotsBase } from './ConnectionDotsBase';
|
|
6
7
|
import type { ConnectionDotsProps } from './types';
|
|
@@ -88,14 +89,14 @@ const ConnectionDotsComponent: React.FC<ConnectionDotsProps> = ({
|
|
|
88
89
|
borderRadius: dotSize / 2,
|
|
89
90
|
backgroundColor: color,
|
|
90
91
|
};
|
|
91
|
-
const animated:
|
|
92
|
+
const animated: WebCssStyle = reduce
|
|
92
93
|
? { opacity: 1 }
|
|
93
|
-
:
|
|
94
|
+
: {
|
|
94
95
|
// `animation` / `animationDelay` are real CSS props on web;
|
|
95
96
|
// react-native-web passes them through to the DOM node.
|
|
96
97
|
animation: `${WAVE_ANIMATION_NAME} ${WAVE_DURATION_MS}ms linear infinite`,
|
|
97
98
|
animationDelay: `${(i / count) * WAVE_DURATION_MS}ms`,
|
|
98
|
-
}
|
|
99
|
+
};
|
|
99
100
|
return (
|
|
100
101
|
<View
|
|
101
102
|
key={i}
|
|
@@ -36,7 +36,11 @@ export const SHADOW_BOX: Record<ShadowRole, string> = {
|
|
|
36
36
|
* On WEB this returns a `{ boxShadow }` style object (so it can also be applied
|
|
37
37
|
* inline without Tailwind). The `.native.ts` fork returns RN
|
|
38
38
|
* `shadowColor/shadowOffset/shadowOpacity/shadowRadius/elevation`.
|
|
39
|
+
*
|
|
40
|
+
* No widening needed: `boxShadow` is a first-class `ViewStyle` key on RN >= 0.76
|
|
41
|
+
* and takes a CSS string, so this is not one of the web-only properties
|
|
42
|
+
* `styles/web-view-style.ts` exists for.
|
|
39
43
|
*/
|
|
40
44
|
export function bloomShadowStyle(role: ShadowRole): ViewStyle {
|
|
41
|
-
return { boxShadow: SHADOW_BOX[role] }
|
|
45
|
+
return { boxShadow: SHADOW_BOX[role] };
|
|
42
46
|
}
|
package/src/dialog/Dialog.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
|
21
21
|
|
|
22
22
|
import { Portal } from '../portal/index.web';
|
|
23
23
|
import { createOverlayZIndex } from '../styles/z-index';
|
|
24
|
-
import { WEB_POSITION_FIXED } from '../styles/web-view-style';
|
|
24
|
+
import { WEB_POSITION_FIXED, type WebCssStyle } from '../styles/web-view-style';
|
|
25
25
|
import { bloomShadowStyle } from '../design-tokens/shadows';
|
|
26
26
|
import { useTheme } from '../theme/use-theme';
|
|
27
27
|
import { Context, useDialogContext, useDialogControl } from './context';
|
|
@@ -59,6 +59,23 @@ import type {
|
|
|
59
59
|
|
|
60
60
|
const FADE_OUT_DURATION = CENTER_FADE_OUT_DURATION;
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The four CSS `animation` shorthands driving the centered card and its backdrop.
|
|
64
|
+
* `animation` has no `ViewStyle` key at all — react-native-web forwards it to the
|
|
65
|
+
* DOM node — so these are annotated `WebCssStyle` rather than cast. The
|
|
66
|
+
* `@keyframes` they name are self-injected by `useDialogCss()`.
|
|
67
|
+
*/
|
|
68
|
+
const ZOOM_FADE_IN: WebCssStyle = {
|
|
69
|
+
animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s',
|
|
70
|
+
};
|
|
71
|
+
const ZOOM_FADE_OUT: WebCssStyle = {
|
|
72
|
+
animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards`,
|
|
73
|
+
};
|
|
74
|
+
const BACKDROP_FADE_IN: WebCssStyle = { animation: 'bloomDialogFadeIn ease-out 0.15s' };
|
|
75
|
+
const BACKDROP_FADE_OUT: WebCssStyle = {
|
|
76
|
+
animation: `bloomDialogFadeOut ease-in ${FADE_OUT_DURATION}ms forwards`,
|
|
77
|
+
};
|
|
78
|
+
|
|
62
79
|
const stopPropagation = (e: { stopPropagation: () => void }) => e.stopPropagation();
|
|
63
80
|
|
|
64
81
|
const ClosingContext = createContext(false);
|
|
@@ -475,9 +492,7 @@ function DialogPanel({
|
|
|
475
492
|
...bloomShadowStyle('m'),
|
|
476
493
|
zIndex: surfaceZIndex,
|
|
477
494
|
},
|
|
478
|
-
isClosing
|
|
479
|
-
? ({ animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards` } as ViewStyle)
|
|
480
|
-
: ({ animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s' } as ViewStyle),
|
|
495
|
+
isClosing ? ZOOM_FADE_OUT : ZOOM_FADE_IN,
|
|
481
496
|
// Drives `height` (and `maxWidth`) only while a morph is in flight; at
|
|
482
497
|
// rest it resolves to `height: 'auto'` — the natural sizing above. Placed
|
|
483
498
|
// before `style` so a consumer's explicit size still wins.
|
|
@@ -625,23 +640,21 @@ function SheetSurface({
|
|
|
625
640
|
|
|
626
641
|
const visible = shown && entered;
|
|
627
642
|
|
|
628
|
-
const panelTransition = useMemo<
|
|
629
|
-
() =>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}) as ViewStyle,
|
|
643
|
+
const panelTransition = useMemo<WebCssStyle>(
|
|
644
|
+
() => ({
|
|
645
|
+
transitionProperty: 'transform, opacity',
|
|
646
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
647
|
+
transitionTimingFunction: EASE_OUT,
|
|
648
|
+
}),
|
|
635
649
|
[],
|
|
636
650
|
);
|
|
637
651
|
|
|
638
|
-
const backdropTransition = useMemo<
|
|
639
|
-
() =>
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}) as ViewStyle,
|
|
652
|
+
const backdropTransition = useMemo<WebCssStyle>(
|
|
653
|
+
() => ({
|
|
654
|
+
transitionProperty: 'opacity',
|
|
655
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
656
|
+
transitionTimingFunction: EASE_OUT,
|
|
657
|
+
}),
|
|
645
658
|
[],
|
|
646
659
|
);
|
|
647
660
|
|
|
@@ -664,7 +677,7 @@ function SheetSurface({
|
|
|
664
677
|
borderRadius: PANEL_RADIUS,
|
|
665
678
|
transform: [{ translateX: visible ? 0 : hiddenSign }],
|
|
666
679
|
opacity: visible ? 1 : 0,
|
|
667
|
-
}
|
|
680
|
+
};
|
|
668
681
|
}, [visible, placement, width, inset, viewportWidth]);
|
|
669
682
|
|
|
670
683
|
const handleBackdropPress = useCallback(() => {
|
|
@@ -798,9 +811,7 @@ function DialogBackdrop({ isClosing }: { isClosing: boolean }) {
|
|
|
798
811
|
bottom: 0,
|
|
799
812
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
800
813
|
},
|
|
801
|
-
isClosing
|
|
802
|
-
? ({ animation: `bloomDialogFadeOut ease-in ${FADE_OUT_DURATION}ms forwards` } as ViewStyle)
|
|
803
|
-
: ({ animation: 'bloomDialogFadeIn ease-out 0.15s' } as ViewStyle),
|
|
814
|
+
isClosing ? BACKDROP_FADE_OUT : BACKDROP_FADE_IN,
|
|
804
815
|
];
|
|
805
816
|
|
|
806
817
|
return <View style={style} />;
|
|
@@ -839,7 +850,11 @@ export function AutoMountedDialog({
|
|
|
839
850
|
);
|
|
840
851
|
}
|
|
841
852
|
|
|
842
|
-
|
|
853
|
+
// Annotated rather than cast: a plain object literal widens `position: 'absolute'`
|
|
854
|
+
// to `string`, which is what the three `as ViewStyle` casts here used to silence.
|
|
855
|
+
// The annotation supplies the contextual type instead, so the literals narrow and
|
|
856
|
+
// a genuine mistake still fails.
|
|
857
|
+
const sheetStyles: Record<'root' | 'backdrop' | 'panel', ViewStyle> = {
|
|
843
858
|
// `zIndex` for the root (backdrop) and panel (surface) is applied inline from
|
|
844
859
|
// the per-layer `createOverlayZIndex(layer)` in `SheetSurface`, so a side
|
|
845
860
|
// dialog stacked on top of another (surface stack) paints above it.
|
|
@@ -849,7 +864,7 @@ const sheetStyles = {
|
|
|
849
864
|
left: 0,
|
|
850
865
|
right: 0,
|
|
851
866
|
bottom: 0,
|
|
852
|
-
}
|
|
867
|
+
},
|
|
853
868
|
backdrop: {
|
|
854
869
|
position: 'absolute',
|
|
855
870
|
top: 0,
|
|
@@ -857,13 +872,13 @@ const sheetStyles = {
|
|
|
857
872
|
right: 0,
|
|
858
873
|
bottom: 0,
|
|
859
874
|
backgroundColor: '#000',
|
|
860
|
-
}
|
|
875
|
+
},
|
|
861
876
|
panel: {
|
|
862
877
|
position: 'absolute',
|
|
863
878
|
overflow: 'hidden',
|
|
864
879
|
// Elevation is applied at the usage site via `bloomShadowStyle('m')`
|
|
865
880
|
// (`boxShadow` on web); the deprecated `shadow*` props were removed.
|
|
866
|
-
}
|
|
881
|
+
},
|
|
867
882
|
};
|
|
868
883
|
|
|
869
884
|
/**
|
package/src/index.web.ts
CHANGED
|
@@ -146,8 +146,8 @@ export { SubtleHover } from './subtle-hover';
|
|
|
146
146
|
export type { SubtleHoverProps } from './subtle-hover';
|
|
147
147
|
|
|
148
148
|
// Motion presets (Reanimated enter/exit + directional screen transition)
|
|
149
|
-
export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop, ScreenTransition } from './motion';
|
|
150
|
-
export type { ScreenTransitionProps, ScreenTransitionDirection } from './motion';
|
|
149
|
+
export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop, ScreenTransition } from './motion/index.web';
|
|
150
|
+
export type { ScreenTransitionProps, ScreenTransitionDirection } from './motion/index.web';
|
|
151
151
|
export { AnimatedCheck } from './animated-check';
|
|
152
152
|
export type { AnimatedCheckProps, AnimatedCheckRef } from './animated-check';
|
|
153
153
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { memo, useMemo } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, type DimensionValue
|
|
2
|
+
import { View, Text, StyleSheet, type DimensionValue } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { WebCssStyle } from '../styles/web-view-style';
|
|
3
5
|
|
|
4
6
|
import { useTheme } from '../theme/use-theme';
|
|
5
7
|
import { animation } from '../styles/tokens';
|
|
@@ -87,17 +89,17 @@ const TopLoading: React.FC<TopLoadingProps> = ({
|
|
|
87
89
|
// reanimated's `Easing.out(Easing.cubic)` used by the native variant.
|
|
88
90
|
const easing = 'cubic-bezier(0.33, 1, 0.68, 1)';
|
|
89
91
|
|
|
90
|
-
const containerTransition = {
|
|
92
|
+
const containerTransition: WebCssStyle = {
|
|
91
93
|
transitionProperty: 'height',
|
|
92
94
|
transitionDuration: `${duration}ms`,
|
|
93
95
|
transitionTimingFunction: easing,
|
|
94
|
-
}
|
|
96
|
+
};
|
|
95
97
|
|
|
96
|
-
const innerTransition = {
|
|
98
|
+
const innerTransition: WebCssStyle = {
|
|
97
99
|
transitionProperty: 'opacity, transform',
|
|
98
100
|
transitionDuration: `${duration}ms`,
|
|
99
101
|
transitionTimingFunction: easing,
|
|
100
|
-
}
|
|
102
|
+
};
|
|
101
103
|
|
|
102
104
|
return (
|
|
103
105
|
<View
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import type { WebCssStyle } from '../styles/web-view-style';
|
|
5
|
+
|
|
4
6
|
interface SpinnerIconProps {
|
|
5
7
|
size?: number;
|
|
6
8
|
color?: string;
|
|
@@ -59,6 +61,15 @@ export const BLOOM_SPINNER_CSS = `
|
|
|
59
61
|
@keyframes ${SPIN_ANIMATION_NAME} { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
60
62
|
`;
|
|
61
63
|
|
|
64
|
+
/**
|
|
65
|
+
* `animation` is a real CSS prop on web; react-native-web passes it through to
|
|
66
|
+
* the DOM node. RN's `ViewStyle` has no `animation` key, which is what
|
|
67
|
+
* `WebCssStyle` covers.
|
|
68
|
+
*/
|
|
69
|
+
const SPIN_STYLE: WebCssStyle = {
|
|
70
|
+
animation: `${SPIN_ANIMATION_NAME} ${SPIN_DURATION_MS}ms linear infinite`,
|
|
71
|
+
};
|
|
72
|
+
|
|
62
73
|
function useKeyframes(): void {
|
|
63
74
|
useEffect(() => {
|
|
64
75
|
if (typeof document === 'undefined') return;
|
|
@@ -87,15 +98,7 @@ export const SpinnerIcon: React.FC<SpinnerIconProps> = ({
|
|
|
87
98
|
return (
|
|
88
99
|
<View
|
|
89
100
|
{...(className ? ({ className } as Record<string, string>) : {})}
|
|
90
|
-
|
|
91
|
-
// through to the DOM node. Typed as a cast because RN's ViewStyle has no
|
|
92
|
-
// `animation` key.
|
|
93
|
-
style={[
|
|
94
|
-
styles.container,
|
|
95
|
-
{ width: size, height: size },
|
|
96
|
-
{ animation: `${SPIN_ANIMATION_NAME} ${SPIN_DURATION_MS}ms linear infinite` } as ViewStyle,
|
|
97
|
-
style,
|
|
98
|
-
]}
|
|
101
|
+
style={[styles.container, { width: size, height: size }, SPIN_STYLE, style]}
|
|
99
102
|
>
|
|
100
103
|
{BLADES.map(({ angle, opacity }) => (
|
|
101
104
|
<View
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web variant of the `motion` barrel. Only the three layout-animation presets
|
|
3
|
+
* fork — see `motion.web.ts` for why. `ScreenTransition` is universal: it already
|
|
4
|
+
* branches on `Platform.OS` internally.
|
|
5
|
+
*/
|
|
6
|
+
export { ScaleAndFadeIn, ScaleAndFadeOut, ShrinkAndPop } from './motion.web';
|
|
7
|
+
export { ScreenTransition } from './ScreenTransition';
|
|
8
|
+
export type { ScreenTransitionProps, ScreenTransitionDirection } from './ScreenTransition';
|
package/src/motion/motion.ts
CHANGED
|
@@ -2,17 +2,25 @@ import {
|
|
|
2
2
|
withDelay,
|
|
3
3
|
withSequence,
|
|
4
4
|
withTiming,
|
|
5
|
-
type
|
|
5
|
+
type EntryOrExitLayoutType,
|
|
6
6
|
} from 'react-native-reanimated';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Reanimated
|
|
10
|
-
*
|
|
9
|
+
* Reanimated layout-animation presets. Each is a value you hand to an
|
|
10
|
+
* `Animated.View`'s `entering` / `exiting` prop:
|
|
11
11
|
*
|
|
12
12
|
* ```tsx
|
|
13
13
|
* <Animated.View entering={ScaleAndFadeIn} exiting={ScaleAndFadeOut} />
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
|
+
* THIS IS THE NATIVE FILE. Custom worklet builders animate correctly on native
|
|
17
|
+
* and do NOTHING on web, where reanimated resolves an animation by preset name —
|
|
18
|
+
* `motion.web.ts` reimplements all three against the mechanisms web actually
|
|
19
|
+
* supports and documents why the two platforms cannot share one. The exported
|
|
20
|
+
* TYPE is deliberately reanimated's own "thing you can pass to entering/exiting"
|
|
21
|
+
* union rather than the concrete builder shape, so the two files present one
|
|
22
|
+
* contract and no consumer can depend on the mechanism.
|
|
23
|
+
*
|
|
16
24
|
* Ported from Bluesky's custom-animation set. The `'worklet'` directive is
|
|
17
25
|
* preserved through Bloom's build; the consuming app's Metro + reanimated Babel
|
|
18
26
|
* plugin transforms it so the animation runs on the UI thread (same path the
|
|
@@ -20,7 +28,7 @@ import {
|
|
|
20
28
|
*/
|
|
21
29
|
|
|
22
30
|
/** Entering: fade in while growing from 70% to full size. */
|
|
23
|
-
export const ScaleAndFadeIn:
|
|
31
|
+
export const ScaleAndFadeIn: EntryOrExitLayoutType = () => {
|
|
24
32
|
'worklet';
|
|
25
33
|
return {
|
|
26
34
|
animations: {
|
|
@@ -35,7 +43,7 @@ export const ScaleAndFadeIn: () => LayoutAnimation = () => {
|
|
|
35
43
|
};
|
|
36
44
|
|
|
37
45
|
/** Exiting counterpart to {@link ScaleAndFadeIn}: fade out while shrinking to 70%. */
|
|
38
|
-
export const ScaleAndFadeOut:
|
|
46
|
+
export const ScaleAndFadeOut: EntryOrExitLayoutType = () => {
|
|
39
47
|
'worklet';
|
|
40
48
|
return {
|
|
41
49
|
animations: {
|
|
@@ -53,7 +61,7 @@ export const ScaleAndFadeOut: () => LayoutAnimation = () => {
|
|
|
53
61
|
* Exiting with a brief "pop": the scale dips to 70%, overshoots to 110%, and the
|
|
54
62
|
* element fades out. Good for confirming a tapped or removed item.
|
|
55
63
|
*/
|
|
56
|
-
export const ShrinkAndPop:
|
|
64
|
+
export const ShrinkAndPop: EntryOrExitLayoutType = () => {
|
|
57
65
|
'worklet';
|
|
58
66
|
return {
|
|
59
67
|
animations: {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WEB FORK of `motion.ts`. Same three names, same contract ("a value you hand to
|
|
3
|
+
* an `Animated.View`'s `entering` / `exiting`"), different mechanism — because on
|
|
4
|
+
* web the native mechanism does nothing at all and the obvious replacement is
|
|
5
|
+
* worse than nothing.
|
|
6
|
+
*
|
|
7
|
+
* WHY THE DEFAULT FILE CANNOT SERVE WEB. `motion.ts` exports custom
|
|
8
|
+
* `EntryExitAnimationFunction` worklet builders. Reanimated's web layout-animation
|
|
9
|
+
* manager resolves an animation by NAME: `tryGetAnimationConfig` reads
|
|
10
|
+
* `config.presetName` off the function, a custom builder has none, and
|
|
11
|
+
* `checkUndefinedAnimationFail` then bails to `makeElementVisible(element, 0)`.
|
|
12
|
+
* Measured in Chrome 5.x/reanimated 4.5.0: the card mounts at `transform: none`,
|
|
13
|
+
* `opacity: 1` and stays there for every sampled frame of both directions, with
|
|
14
|
+
* one `[Reanimated] Couldn't load entering/exiting animation` warning per element.
|
|
15
|
+
* Native is unaffected — it runs the builder directly, with no name lookup.
|
|
16
|
+
*
|
|
17
|
+
* WHY THE ENTER AND THE EXITS USE DIFFERENT MECHANISMS. Rebuilding all three as
|
|
18
|
+
* `Keyframe`s is the obvious fix and is only HALF right, because reanimated's two
|
|
19
|
+
* web directions differ in one decisive argument:
|
|
20
|
+
*
|
|
21
|
+
* - `exiting` -> `handleExitingAnimation` calls `setElementAnimation` on a
|
|
22
|
+
* throwaway clone with `shouldSavePosition` FALSE. The custom keyframe's
|
|
23
|
+
* `duration x 5` cleanup only tears that clone down, which is what it is for.
|
|
24
|
+
* A `Keyframe` here is safe AND exact: it compiles to a real CSS `@keyframes`
|
|
25
|
+
* rule, so the full scale + opacity shape survives.
|
|
26
|
+
* - `entering` -> `chooseAction` calls `setElementAnimation` on the REAL element
|
|
27
|
+
* with `shouldSavePosition` TRUE, and since a custom keyframe's generated
|
|
28
|
+
* `REA-ENTERING-n` name is absent from reanimated's built-in `Animations`
|
|
29
|
+
* map, `scheduleAnimationCleanup` fires
|
|
30
|
+
* `setElementPosition` at `duration x 5` — `position: absolute`, a frozen
|
|
31
|
+
* `top`/`left`/`width`/`height` snapshot, `transform` cleared. The animation
|
|
32
|
+
* plays and the element is then pinned, silently, one and a half seconds
|
|
33
|
+
* later. That is failure mode (C) in `~/Oxy/AGENTS.md` and it is strictly
|
|
34
|
+
* worse than the inert animation it would replace.
|
|
35
|
+
*
|
|
36
|
+
* So the enter uses a PREDEFINED builder, whose `presetName` IS in `Animations`
|
|
37
|
+
* and therefore skips `scheduleAnimationCleanup` entirely. Predefined builders
|
|
38
|
+
* cannot combine a fade with a scale, so the web enter keeps the fade and drops
|
|
39
|
+
* the 0.7 -> 1 growth. `ScreenTransition` in this same folder already made that
|
|
40
|
+
* trade for the same reason ("on web the slide is intentionally dropped").
|
|
41
|
+
*
|
|
42
|
+
* The imperative shared-value drive (failure mode (A)'s remedy, and what
|
|
43
|
+
* `toast/ToastRow.tsx` uses for its own enter) is the third option and is not
|
|
44
|
+
* available here: these are exported VALUES, not components, so there is no
|
|
45
|
+
* render to hang a `useSharedValue` off — and it could not express an exit at
|
|
46
|
+
* all, since keeping an unmounting element alive is exactly what `exiting` is for.
|
|
47
|
+
*
|
|
48
|
+
* WEB EASING IS LINEAR. `Keyframe` has no `.easing()` modifier, and a per-stop
|
|
49
|
+
* easing only survives `convertAnimationObjectToKeyframes` when it resolves to one
|
|
50
|
+
* of reanimated's seven `WebEasings` names (all of them ease-IN curves), while
|
|
51
|
+
* `.easing(Easing.bezier(...))` needs the `__closure` the worklets babel plugin
|
|
52
|
+
* adds — absent in every Oxy web build — and degrades to linear WITH a warning.
|
|
53
|
+
* `ShrinkAndPop` therefore carries an extra midpoint stop so its piecewise-linear
|
|
54
|
+
* web curve tracks the native `withTiming` segments closely rather than cutting
|
|
55
|
+
* the corner.
|
|
56
|
+
*/
|
|
57
|
+
import {
|
|
58
|
+
FadeIn,
|
|
59
|
+
Keyframe,
|
|
60
|
+
type EntryOrExitLayoutType,
|
|
61
|
+
} from 'react-native-reanimated';
|
|
62
|
+
|
|
63
|
+
/** `withTiming`'s default, which is what the native builders animate over. */
|
|
64
|
+
const DEFAULT_DURATION = 300;
|
|
65
|
+
/** `ShrinkAndPop`: 75ms shrink + 150ms overshoot, with the fade ending with it. */
|
|
66
|
+
const POP_DURATION = 250;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Entering: fade in.
|
|
70
|
+
*
|
|
71
|
+
* The scale is dropped on web — see the header. `FadeIn`'s own default duration
|
|
72
|
+
* is already 300ms, matching the native builder, and the bare class is exported
|
|
73
|
+
* rather than a `.duration()` instance so nothing shares a mutable builder.
|
|
74
|
+
*/
|
|
75
|
+
export const ScaleAndFadeIn: EntryOrExitLayoutType = FadeIn;
|
|
76
|
+
|
|
77
|
+
/** Exiting counterpart to {@link ScaleAndFadeIn}: fade out while shrinking to 70%. */
|
|
78
|
+
export const ScaleAndFadeOut: EntryOrExitLayoutType = new Keyframe({
|
|
79
|
+
from: { opacity: 1, transform: [{ scale: 1 }] },
|
|
80
|
+
to: { opacity: 0, transform: [{ scale: 0.7 }] },
|
|
81
|
+
}).duration(DEFAULT_DURATION);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Exiting with a brief "pop": the scale dips to 70%, overshoots to 110%, and the
|
|
85
|
+
* element fades out.
|
|
86
|
+
*
|
|
87
|
+
* Stops are the native timeline sampled at its own breakpoints, so every property
|
|
88
|
+
* is declared at every stop and nothing depends on CSS's cross-stop interpolation:
|
|
89
|
+
* scale reaches 0.7 at 75ms (30%) and 1.1 at 225ms (90%); opacity holds until
|
|
90
|
+
* 125ms (50%) and then falls to 0 by 250ms. The 50% stop's 0.8333 scale and the
|
|
91
|
+
* 90% stop's 0.2 opacity are the native values at those instants.
|
|
92
|
+
*/
|
|
93
|
+
export const ShrinkAndPop: EntryOrExitLayoutType = new Keyframe({
|
|
94
|
+
0: { opacity: 1, transform: [{ scale: 1 }] },
|
|
95
|
+
30: { opacity: 1, transform: [{ scale: 0.7 }] },
|
|
96
|
+
50: { opacity: 1, transform: [{ scale: 0.8333 }] },
|
|
97
|
+
90: { opacity: 0.2, transform: [{ scale: 1.1 }] },
|
|
98
|
+
100: { opacity: 0, transform: [{ scale: 1.1 }] },
|
|
99
|
+
}).duration(POP_DURATION);
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Ported from expo-glass-tabs v0.1.1 — src/progressive-blur.tsx
|
|
3
3
|
* (MIT © 2026 David Mokos). See the top-level NOTICE.
|
|
4
4
|
*/
|
|
5
|
-
import { View
|
|
5
|
+
import { View } from 'react-native';
|
|
6
6
|
|
|
7
|
+
import type { WebCssStyle } from '../styles/web-view-style';
|
|
7
8
|
import { useTheme } from '../theme/use-theme';
|
|
8
9
|
import {
|
|
9
10
|
buildMaskGradient,
|
|
@@ -50,14 +51,14 @@ export function ProgressiveBlur({
|
|
|
50
51
|
// Web-only CSS, unknown to React Native's `ViewStyle`. Both spellings are set
|
|
51
52
|
// for `backdrop-filter` and `mask-image`: Safari still needs the prefixed
|
|
52
53
|
// form of each.
|
|
53
|
-
const webStyle = {
|
|
54
|
+
const webStyle: WebCssStyle = {
|
|
54
55
|
pointerEvents: 'none',
|
|
55
56
|
backdropFilter: filter,
|
|
56
57
|
WebkitBackdropFilter: filter,
|
|
57
58
|
maskImage: mask,
|
|
58
59
|
WebkitMaskImage: mask,
|
|
59
60
|
backgroundImage: buildTailGradient(colors.background, direction),
|
|
60
|
-
}
|
|
61
|
+
};
|
|
61
62
|
|
|
62
63
|
return <View {...rest} style={[webStyle, style]} />;
|
|
63
64
|
}
|
|
@@ -19,3 +19,53 @@ import type { ViewStyle } from 'react-native';
|
|
|
19
19
|
* where Yoga silently ignores it.
|
|
20
20
|
*/
|
|
21
21
|
export const WEB_POSITION_FIXED = 'fixed' as ViewStyle['position'];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A `ViewStyle` that may ALSO carry CSS react-native-web forwards straight to the
|
|
25
|
+
* DOM node but React Native does not model at all.
|
|
26
|
+
*
|
|
27
|
+
* This is the OTHER shape of the same RN/RNW type gap `WEB_POSITION_FIXED`
|
|
28
|
+
* covers, and it needs a different escape hatch because the two are structurally
|
|
29
|
+
* different: `position` is a key RN DOES model, just with a narrower value union,
|
|
30
|
+
* so a value-level cast is enough and it can still be written inside an ordinary
|
|
31
|
+
* style object. The properties below have no key on `ViewStyle` at all, so the
|
|
32
|
+
* whole OBJECT has to cross the boundary.
|
|
33
|
+
*
|
|
34
|
+
* ANNOTATE, DO NOT CAST. Every `.web` fork used to spell this `{ … } as ViewStyle`
|
|
35
|
+
* inline. That is not a widening, it is SILENCING: `as` only requires the two
|
|
36
|
+
* types to be COMPARABLE, so `{ animatoin: '…' } as ViewStyle` compiles happily
|
|
37
|
+
* and the typo ships as a style key react-native-web drops on the floor.
|
|
38
|
+
* Annotating with this interface restores excess-property checking — a key that
|
|
39
|
+
* is neither a real RN style nor a listed web-only property is a compile error —
|
|
40
|
+
* and, unlike a widening helper function, it erases completely, so the emitted
|
|
41
|
+
* JavaScript is byte-identical to the cast it replaces.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const glass: WebCssStyle = { backdropFilter: 'blur(24px)' };
|
|
45
|
+
* // …then pass it anywhere a `ViewStyle` is expected.
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Only add a key here once react-native-web is confirmed to pass it through and
|
|
49
|
+
* RN has no equivalent. Several near-misses do NOT belong: RN 0.83 models
|
|
50
|
+
* `cursor`, `boxShadow`, `filter` and `pointerEvents` on `ViewStyle` directly, so
|
|
51
|
+
* those need no widening at all (`userSelect` is the trap — RN declares it on
|
|
52
|
+
* `TextStyle` only). Prefixed spellings sit alongside the standard ones because
|
|
53
|
+
* Safari still needs them for `backdrop-filter` and `mask-image`.
|
|
54
|
+
*/
|
|
55
|
+
export interface WebCssStyle extends ViewStyle {
|
|
56
|
+
/** CSS `animation` shorthand. Bloom's web forks self-inject the `@keyframes`. */
|
|
57
|
+
animation?: string;
|
|
58
|
+
animationDelay?: string;
|
|
59
|
+
transitionProperty?: string;
|
|
60
|
+
transitionDuration?: string;
|
|
61
|
+
transitionTimingFunction?: string;
|
|
62
|
+
backdropFilter?: string;
|
|
63
|
+
WebkitBackdropFilter?: string;
|
|
64
|
+
maskImage?: string;
|
|
65
|
+
WebkitMaskImage?: string;
|
|
66
|
+
backgroundImage?: string;
|
|
67
|
+
scrollbarWidth?: 'auto' | 'thin' | 'none';
|
|
68
|
+
scrollbarColor?: string;
|
|
69
|
+
/** RN declares this on `TextStyle`, not `ViewStyle`. */
|
|
70
|
+
userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all';
|
|
71
|
+
}
|
|
@@ -260,16 +260,21 @@ function TabBarBody({
|
|
|
260
260
|
[EXPANDED_HEIGHT, MINIMIZED_HEIGHT],
|
|
261
261
|
Extrapolation.CLAMP,
|
|
262
262
|
);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
263
|
+
// Revolut-style: the pill shrinks in both dimensions.
|
|
264
|
+
//
|
|
265
|
+
// Written as the two long-form margins, NEVER as `marginHorizontal`.
|
|
266
|
+
// Reanimated's web path hands a mapper's result to react-native-web's
|
|
267
|
+
// `createReactDOMStyle`, whose `STYLE_SHORT_FORM_EXPANSIONS` map covers
|
|
268
|
+
// `marginInline` but NOT React Native's `marginHorizontal` (verified absent
|
|
269
|
+
// from the whole file in RNW 0.21.2). An unexpanded shorthand is written
|
|
270
|
+
// straight onto the DOM node as `style.marginHorizontal`, which is not a CSS
|
|
271
|
+
// property, so the browser drops it silently: the bar keeps its full width
|
|
272
|
+
// while `highlightStyle` still subtracts `MINIMIZED_INSET` from `barWidth`,
|
|
273
|
+
// leaving the highlight narrower than the tab it sits under. Static styles
|
|
274
|
+
// are unaffected — they take RNW's own StyleSheet path, which does handle
|
|
275
|
+
// the shorthand — so this only bites inside a mapper.
|
|
276
|
+
const inset = interpolate(progress.value, [0, 1], [0, MINIMIZED_INSET], Extrapolation.CLAMP);
|
|
277
|
+
return { height, marginLeft: inset, marginRight: inset };
|
|
273
278
|
}, [progress]);
|
|
274
279
|
|
|
275
280
|
// The capsule shape lives on the surface itself: iOS 26 glass renders its own
|
|
@@ -317,8 +322,8 @@ function TabBarBody({
|
|
|
317
322
|
};
|
|
318
323
|
}, [progress, slideIndex, windowWidth, tabCount]);
|
|
319
324
|
|
|
320
|
-
// Shared with `
|
|
321
|
-
//
|
|
325
|
+
// Shared with `useTabBarFootprint`, so a consumer accounting for the bar in
|
|
326
|
+
// its own layout can never drift from where the bar actually sits.
|
|
322
327
|
const bottomOffset = tabBarBottomGap(insets.bottom);
|
|
323
328
|
const barContext = useMemo(
|
|
324
329
|
() => ({ slideIndex, isDragging, theme, activeIndex, selectIndex }),
|
package/src/tab-bar/index.ts
CHANGED
|
@@ -29,5 +29,5 @@ export {
|
|
|
29
29
|
useTabBarMinimized,
|
|
30
30
|
} from './minimize-context';
|
|
31
31
|
export type { MinimizeState } from './minimize-context';
|
|
32
|
-
export {
|
|
32
|
+
export { useTabBarFootprint } from './use-footprint';
|
|
33
33
|
export type { TabBarButtonProps, TabBarItem, TabBarProps, TabBarTheme } from './types';
|
|
@@ -31,5 +31,5 @@ export {
|
|
|
31
31
|
useTabBarMinimized,
|
|
32
32
|
} from './minimize-context';
|
|
33
33
|
export type { MinimizeState } from './minimize-context';
|
|
34
|
-
export {
|
|
34
|
+
export { useTabBarFootprint } from './use-footprint';
|
|
35
35
|
export type { TabBarButtonProps, TabBarItem, TabBarProps, TabBarTheme } from './types';
|
package/src/tab-bar/shared.ts
CHANGED
|
@@ -63,9 +63,9 @@ const MIN_BOTTOM_GAP = 12;
|
|
|
63
63
|
/**
|
|
64
64
|
* Vertical gap between the bottom of the pill and the bottom of the window.
|
|
65
65
|
*
|
|
66
|
-
* The safe-area inset is folded in HERE, which is why anything
|
|
67
|
-
*
|
|
68
|
-
* number from this same function) rather than adding `insets.bottom` to a
|
|
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
69
|
* hardcoded height — that double-counts the inset.
|
|
70
70
|
*/
|
|
71
71
|
export function tabBarBottomGap(bottomInset: number): number {
|
|
@@ -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 (
|