@towsila/expo-ui-library 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/ui/scrollIndicator.tsx +130 -0
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
LayoutChangeEvent,
|
|
5
|
+
ScrollViewProps,
|
|
6
|
+
View,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
9
|
+
import { useLanguage } from "../../context/LanguageContext";
|
|
10
|
+
|
|
11
|
+
interface Props extends ScrollViewProps {
|
|
12
|
+
trackWidth?: number;
|
|
13
|
+
trackLength?: number;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const IDLE_OPACITY = 0.45;
|
|
18
|
+
|
|
19
|
+
export const ScrollWithIndicator: React.FC<Props> = ({
|
|
20
|
+
children,
|
|
21
|
+
trackWidth = 5,
|
|
22
|
+
trackLength = 200,
|
|
23
|
+
style,
|
|
24
|
+
contentContainerStyle,
|
|
25
|
+
...rest
|
|
26
|
+
}) => {
|
|
27
|
+
const { colors } = useAppTheme();
|
|
28
|
+
const { isRTL } = useLanguage();
|
|
29
|
+
|
|
30
|
+
const scrollY = useRef(new Animated.Value(0)).current;
|
|
31
|
+
const fade = useRef(new Animated.Value(IDLE_OPACITY)).current;
|
|
32
|
+
const awake = useRef(false);
|
|
33
|
+
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
34
|
+
|
|
35
|
+
const [viewH, setViewH] = useState(0);
|
|
36
|
+
const [contentH, setContentH] = useState(0);
|
|
37
|
+
|
|
38
|
+
const scrollable = viewH > 0 && contentH > viewH + 1;
|
|
39
|
+
|
|
40
|
+
// short fixed-length track instead of full height
|
|
41
|
+
const trackH = Math.min(trackLength, viewH);
|
|
42
|
+
const thumbH = scrollable
|
|
43
|
+
? Math.min(trackH * 0.7, Math.max(24, (viewH / contentH) * trackH))
|
|
44
|
+
: 0;
|
|
45
|
+
const maxScroll = Math.max(contentH - viewH, 1);
|
|
46
|
+
const maxThumb = Math.max(trackH - thumbH, 1);
|
|
47
|
+
|
|
48
|
+
const translateY = scrollY.interpolate({
|
|
49
|
+
inputRange: [0, maxScroll],
|
|
50
|
+
outputRange: [0, maxThumb],
|
|
51
|
+
extrapolate: "clamp",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// brighter while scrolling, softer when idle
|
|
55
|
+
const wake = () => {
|
|
56
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
57
|
+
if (!awake.current) {
|
|
58
|
+
awake.current = true;
|
|
59
|
+
Animated.timing(fade, { toValue: 1, duration: 120, useNativeDriver: true }).start();
|
|
60
|
+
}
|
|
61
|
+
hideTimer.current = setTimeout(() => {
|
|
62
|
+
awake.current = false;
|
|
63
|
+
Animated.timing(fade, { toValue: IDLE_OPACITY, duration: 400, useNativeDriver: true }).start();
|
|
64
|
+
}, 900);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
return () => {
|
|
69
|
+
if (hideTimer.current) clearTimeout(hideTimer.current);
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View style={[{ flex: 1, flexDirection: isRTL ? "row" : "row-reverse" }, style]}>
|
|
75
|
+
<Animated.ScrollView
|
|
76
|
+
{...rest}
|
|
77
|
+
style={{ flex: 1 }}
|
|
78
|
+
contentContainerStyle={contentContainerStyle}
|
|
79
|
+
showsVerticalScrollIndicator={false}
|
|
80
|
+
scrollEventThrottle={10}
|
|
81
|
+
onLayout={(e: LayoutChangeEvent) => setViewH(e.nativeEvent.layout.height)}
|
|
82
|
+
onContentSizeChange={(_, h) => setContentH(h)}
|
|
83
|
+
onScroll={Animated.event(
|
|
84
|
+
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
|
85
|
+
{ useNativeDriver: true, listener: wake }
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{children}
|
|
89
|
+
</Animated.ScrollView>
|
|
90
|
+
|
|
91
|
+
{scrollable && (
|
|
92
|
+
<Animated.View
|
|
93
|
+
pointerEvents="none"
|
|
94
|
+
style={[
|
|
95
|
+
{
|
|
96
|
+
width: trackWidth,
|
|
97
|
+
height: trackH,
|
|
98
|
+
alignSelf: "center",
|
|
99
|
+
opacity: fade,
|
|
100
|
+
},
|
|
101
|
+
isRTL ? { marginLeft: 15 } : { marginRight: 15 },
|
|
102
|
+
]}
|
|
103
|
+
>
|
|
104
|
+
{/* soft track */}
|
|
105
|
+
<View
|
|
106
|
+
style={{
|
|
107
|
+
position: "absolute",
|
|
108
|
+
top: 0,
|
|
109
|
+
bottom: 0,
|
|
110
|
+
width: trackWidth,
|
|
111
|
+
borderRadius: trackWidth / 2,
|
|
112
|
+
backgroundColor: colors.primary,
|
|
113
|
+
opacity: 0.15,
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
{/* thumb */}
|
|
117
|
+
<Animated.View
|
|
118
|
+
style={{
|
|
119
|
+
width: trackWidth,
|
|
120
|
+
height: thumbH,
|
|
121
|
+
borderRadius: trackWidth / 2,
|
|
122
|
+
backgroundColor: colors.primary,
|
|
123
|
+
transform: [{ translateY }],
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</Animated.View>
|
|
127
|
+
)}
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -67,4 +67,5 @@ export {useArrivalDetection} from "./hooks/useArrivalDetection";
|
|
|
67
67
|
export {default as Toggle} from "./components/ui/toggle";
|
|
68
68
|
export {default as BackIcon} from "./components/ui/backIcon";
|
|
69
69
|
export {default as SuccessScreen} from "./components/screens/sucessScreen";
|
|
70
|
-
export {default as AnimatedSuccessIcon} from "./components/ui/AnimatedSuccessIcon";
|
|
70
|
+
export {default as AnimatedSuccessIcon} from "./components/ui/AnimatedSuccessIcon";
|
|
71
|
+
export {ScrollWithIndicator} from "./components/ui/scrollIndicator"
|