@oxyhq/bloom 0.1.11 → 0.1.13
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/index.js +27 -19
- package/lib/commonjs/bottom-sheet/index.js.map +1 -1
- package/lib/commonjs/loading/Loading.js +69 -49
- package/lib/commonjs/loading/Loading.js.map +1 -1
- package/lib/commonjs/loading/SpinnerIcon.js +37 -20
- package/lib/commonjs/loading/SpinnerIcon.js.map +1 -1
- package/lib/module/bottom-sheet/index.js +27 -19
- package/lib/module/bottom-sheet/index.js.map +1 -1
- package/lib/module/loading/Loading.js +69 -49
- package/lib/module/loading/Loading.js.map +1 -1
- package/lib/module/loading/SpinnerIcon.js +37 -20
- package/lib/module/loading/SpinnerIcon.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/loading/Loading.d.ts.map +1 -1
- package/lib/typescript/commonjs/loading/SpinnerIcon.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/module/loading/Loading.d.ts.map +1 -1
- package/lib/typescript/module/loading/SpinnerIcon.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/bottom-sheet/index.tsx +86 -76
- package/src/loading/Loading.tsx +71 -35
- package/src/loading/SpinnerIcon.tsx +44 -17
package/src/loading/Loading.tsx
CHANGED
|
@@ -70,6 +70,66 @@ const SpinnerLoading: React.FC<SpinnerLoadingProps> = ({
|
|
|
70
70
|
);
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Inner component for TopLoading that requires react-native-reanimated.
|
|
75
|
+
* Extracted to a dedicated component so all hooks are called unconditionally,
|
|
76
|
+
* satisfying the Rules of Hooks and React Compiler requirements.
|
|
77
|
+
*/
|
|
78
|
+
type AnimatedTopLoadingProps = {
|
|
79
|
+
showLoading: boolean;
|
|
80
|
+
targetHeight: number;
|
|
81
|
+
effectiveIconSize: number;
|
|
82
|
+
spinnerColor: string;
|
|
83
|
+
spinnerIcon: React.ReactNode | undefined;
|
|
84
|
+
style: TopLoadingProps['style'];
|
|
85
|
+
testID: string | undefined;
|
|
86
|
+
reanimated: NonNullable<ReturnType<typeof getReanimated>>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const AnimatedTopLoading: React.FC<AnimatedTopLoadingProps> = ({
|
|
90
|
+
showLoading,
|
|
91
|
+
targetHeight,
|
|
92
|
+
effectiveIconSize,
|
|
93
|
+
spinnerColor,
|
|
94
|
+
spinnerIcon,
|
|
95
|
+
style,
|
|
96
|
+
testID,
|
|
97
|
+
reanimated,
|
|
98
|
+
}) => {
|
|
99
|
+
const { default: Animated, useAnimatedStyle, useSharedValue, withTiming, Easing } = reanimated;
|
|
100
|
+
|
|
101
|
+
const height = useSharedValue(showLoading ? targetHeight : 0);
|
|
102
|
+
const opacity = useSharedValue(showLoading ? 1 : 0);
|
|
103
|
+
const translateY = useSharedValue(showLoading ? 0 : -targetHeight);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const timingConfig = { duration: animation.duration.slow, easing: Easing.out(Easing.cubic) };
|
|
107
|
+
height.value = withTiming(showLoading ? targetHeight : 0, timingConfig);
|
|
108
|
+
opacity.value = withTiming(showLoading ? 1 : 0, timingConfig);
|
|
109
|
+
translateY.value = withTiming(showLoading ? 0 : -targetHeight, timingConfig);
|
|
110
|
+
// Easing, withTiming: module-level constants from lazily-resolved reanimated, stable.
|
|
111
|
+
// height/opacity/translateY: shared value objects, stable references.
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
+
}, [showLoading, targetHeight]);
|
|
114
|
+
|
|
115
|
+
const containerAnimated = useAnimatedStyle(() => ({
|
|
116
|
+
height: height.value,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
const innerAnimated = useAnimatedStyle(() => ({
|
|
120
|
+
opacity: opacity.value,
|
|
121
|
+
transform: [{ translateY: translateY.value }],
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Animated.View style={[styles.topContainer, containerAnimated]} testID={testID}>
|
|
126
|
+
<Animated.View style={[styles.topLoadingView, { height: targetHeight }, innerAnimated, style]}>
|
|
127
|
+
{spinnerIcon ?? <SpinnerIcon size={effectiveIconSize} color={spinnerColor} />}
|
|
128
|
+
</Animated.View>
|
|
129
|
+
</Animated.View>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
73
133
|
const TopLoading: React.FC<TopLoadingProps> = ({
|
|
74
134
|
size = 'medium',
|
|
75
135
|
color,
|
|
@@ -88,8 +148,8 @@ const TopLoading: React.FC<TopLoadingProps> = ({
|
|
|
88
148
|
|
|
89
149
|
const reanimated = getReanimated();
|
|
90
150
|
|
|
91
|
-
// Non-animated fallback when reanimated is not available
|
|
92
151
|
if (!reanimated) {
|
|
152
|
+
// Non-animated fallback when reanimated is not available
|
|
93
153
|
if (!showLoading) return null;
|
|
94
154
|
return (
|
|
95
155
|
<View style={[styles.topContainer, { height: targetHeight }, style]} testID={testID}>
|
|
@@ -100,41 +160,17 @@ const TopLoading: React.FC<TopLoadingProps> = ({
|
|
|
100
160
|
);
|
|
101
161
|
}
|
|
102
162
|
|
|
103
|
-
const { default: Animated, useAnimatedStyle, useSharedValue, withTiming, Easing } = reanimated;
|
|
104
|
-
|
|
105
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
106
|
-
const height = useSharedValue(showLoading ? targetHeight : 0);
|
|
107
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
108
|
-
const opacity = useSharedValue(showLoading ? 1 : 0);
|
|
109
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
110
|
-
const translateY = useSharedValue(showLoading ? 0 : -targetHeight);
|
|
111
|
-
|
|
112
|
-
const timingConfig = { duration: animation.duration.slow, easing: Easing.out(Easing.cubic) };
|
|
113
|
-
|
|
114
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
height.value = withTiming(showLoading ? targetHeight : 0, timingConfig);
|
|
117
|
-
opacity.value = withTiming(showLoading ? 1 : 0, timingConfig);
|
|
118
|
-
translateY.value = withTiming(showLoading ? 0 : -targetHeight, timingConfig);
|
|
119
|
-
}, [showLoading, targetHeight, height, opacity, translateY]);
|
|
120
|
-
|
|
121
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
122
|
-
const containerAnimated = useAnimatedStyle(() => ({
|
|
123
|
-
height: height.value,
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
127
|
-
const innerAnimated = useAnimatedStyle(() => ({
|
|
128
|
-
opacity: opacity.value,
|
|
129
|
-
transform: [{ translateY: translateY.value }],
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
163
|
return (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
<AnimatedTopLoading
|
|
165
|
+
showLoading={showLoading}
|
|
166
|
+
targetHeight={targetHeight}
|
|
167
|
+
effectiveIconSize={effectiveIconSize}
|
|
168
|
+
spinnerColor={spinnerColor}
|
|
169
|
+
spinnerIcon={spinnerIcon}
|
|
170
|
+
style={style}
|
|
171
|
+
testID={testID}
|
|
172
|
+
reanimated={reanimated}
|
|
173
|
+
/>
|
|
138
174
|
);
|
|
139
175
|
};
|
|
140
176
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import { ActivityIndicator,
|
|
2
|
+
import { ActivityIndicator, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
4
|
// Lazy-loaded dependencies for the SVG spinner.
|
|
5
5
|
// Falls back to ActivityIndicator if react-native-svg or react-native-reanimated are not installed.
|
|
@@ -41,23 +41,22 @@ interface SpinnerIconProps {
|
|
|
41
41
|
style?: ViewStyle;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
type AnimatedSpinnerProps = SpinnerIconProps & {
|
|
45
|
+
svg: NonNullable<SvgModuleType>;
|
|
46
|
+
reanimated: NonNullable<ReanimatedType>;
|
|
47
|
+
};
|
|
48
|
+
|
|
44
49
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* Falls back to ActivityIndicator if either is missing.
|
|
50
|
+
* Inner component that unconditionally calls Reanimated hooks.
|
|
51
|
+
* Only rendered when both react-native-svg and react-native-reanimated are available.
|
|
48
52
|
*/
|
|
49
|
-
|
|
53
|
+
const AnimatedSpinner: React.FC<AnimatedSpinnerProps> = ({
|
|
50
54
|
color = '#005c67',
|
|
51
55
|
size = 26,
|
|
52
56
|
style,
|
|
57
|
+
svg,
|
|
58
|
+
reanimated,
|
|
53
59
|
}) => {
|
|
54
|
-
const svg = getSvgModule();
|
|
55
|
-
const reanimated = getReanimated();
|
|
56
|
-
|
|
57
|
-
if (!svg || !reanimated) {
|
|
58
|
-
return <ActivityIndicator size={size > 30 ? 'large' : 'small'} color={color} />;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
60
|
const { default: Svg, Rect } = svg;
|
|
62
61
|
const {
|
|
63
62
|
default: Animated,
|
|
@@ -68,19 +67,19 @@ export const SpinnerIcon: React.FC<SpinnerIconProps> = ({
|
|
|
68
67
|
Easing,
|
|
69
68
|
} = reanimated;
|
|
70
69
|
|
|
71
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
72
70
|
const rotation = useSharedValue(0);
|
|
73
71
|
|
|
74
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
75
72
|
useEffect(() => {
|
|
76
73
|
rotation.value = withRepeat(
|
|
77
74
|
withTiming(360, { duration: 400, easing: Easing.linear }),
|
|
78
75
|
-1,
|
|
79
|
-
false
|
|
76
|
+
false,
|
|
80
77
|
);
|
|
81
|
-
|
|
78
|
+
// Reanimated shared values are stable references; withRepeat/withTiming/Easing
|
|
79
|
+
// are module-level functions from the lazily-loaded module and are stable too.
|
|
80
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
+
}, []);
|
|
82
82
|
|
|
83
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
84
83
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
85
84
|
transform: [{ rotate: `${rotation.value}deg` }],
|
|
86
85
|
}));
|
|
@@ -112,4 +111,32 @@ export const SpinnerIcon: React.FC<SpinnerIconProps> = ({
|
|
|
112
111
|
);
|
|
113
112
|
};
|
|
114
113
|
|
|
114
|
+
/**
|
|
115
|
+
* iOS-style SVG spinner with 8 rotating rectangles and an opacity gradient trail.
|
|
116
|
+
* Requires react-native-svg and react-native-reanimated as peer dependencies.
|
|
117
|
+
* Falls back to ActivityIndicator if either is missing.
|
|
118
|
+
*/
|
|
119
|
+
export const SpinnerIcon: React.FC<SpinnerIconProps> = ({
|
|
120
|
+
color = '#005c67',
|
|
121
|
+
size = 26,
|
|
122
|
+
style,
|
|
123
|
+
}) => {
|
|
124
|
+
const svg = getSvgModule();
|
|
125
|
+
const reanimated = getReanimated();
|
|
126
|
+
|
|
127
|
+
if (!svg || !reanimated) {
|
|
128
|
+
return <ActivityIndicator size={size > 30 ? 'large' : 'small'} color={color} />;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<AnimatedSpinner
|
|
133
|
+
color={color}
|
|
134
|
+
size={size}
|
|
135
|
+
style={style}
|
|
136
|
+
svg={svg}
|
|
137
|
+
reanimated={reanimated}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
115
142
|
SpinnerIcon.displayName = 'SpinnerIcon';
|