@r0b0t3d/react-native-collapsible 1.0.1 → 1.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/CollapsibleContainer.js +4 -2
- package/lib/commonjs/components/CollapsibleContainer.js.map +1 -1
- package/lib/commonjs/components/CollapsibleView.js +4 -3
- package/lib/commonjs/components/CollapsibleView.js.map +1 -1
- package/lib/commonjs/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
- package/lib/commonjs/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
- package/lib/commonjs/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +9 -8
- package/lib/commonjs/components/header/CollapsibleHeaderContainer.js.map +1 -0
- package/lib/commonjs/components/{StickyView.js → header/StickyView.js} +18 -31
- package/lib/commonjs/components/header/StickyView.js.map +1 -0
- package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js +75 -0
- package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
- package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js +35 -0
- package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
- package/lib/commonjs/components/pullToRefresh/RefreshControl.js +73 -0
- package/lib/commonjs/components/pullToRefresh/RefreshControl.js.map +1 -0
- package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js +24 -0
- package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
- package/lib/commonjs/components/pullToRefresh/utils.js +59 -0
- package/lib/commonjs/components/pullToRefresh/utils.js.map +1 -0
- package/lib/commonjs/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +36 -31
- package/lib/commonjs/components/scrollable/CollapsibleFlatList.js.map +1 -0
- package/lib/commonjs/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +6 -6
- package/lib/commonjs/components/scrollable/CollapsibleScrollView.js.map +1 -0
- package/lib/commonjs/{hooks → components/scrollable}/useAnimatedScroll.js +7 -7
- package/lib/commonjs/components/scrollable/useAnimatedScroll.js.map +1 -0
- package/lib/commonjs/hooks/useInternalCollapsibleContext.js +1 -1
- package/lib/commonjs/hooks/useInternalCollapsibleContext.js.map +1 -1
- package/lib/commonjs/index.js +21 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/{types.js → types.d.js} +1 -1
- package/lib/commonjs/{types.js.map → types.d.js.map} +0 -0
- package/lib/commonjs/utils/debounce.js +20 -0
- package/lib/commonjs/utils/debounce.js.map +1 -0
- package/lib/commonjs/{hooks/withCollapsibleContext.js → withCollapsibleContext.js} +95 -64
- package/lib/commonjs/withCollapsibleContext.js.map +1 -0
- package/lib/module/components/CollapsibleContainer.js +1 -1
- package/lib/module/components/CollapsibleContainer.js.map +1 -1
- package/lib/module/components/CollapsibleView.js +4 -3
- package/lib/module/components/CollapsibleView.js.map +1 -1
- package/lib/module/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
- package/lib/module/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
- package/lib/module/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +7 -6
- package/lib/module/components/header/CollapsibleHeaderContainer.js.map +1 -0
- package/lib/module/components/{StickyView.js → header/StickyView.js} +17 -30
- package/lib/module/components/header/StickyView.js.map +1 -0
- package/lib/module/components/pullToRefresh/PullToRefreshContainer.js +56 -0
- package/lib/module/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
- package/lib/module/components/pullToRefresh/PullToRefreshProvider.js +21 -0
- package/lib/module/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
- package/lib/module/components/pullToRefresh/RefreshControl.js +55 -0
- package/lib/module/components/pullToRefresh/RefreshControl.js.map +1 -0
- package/lib/module/components/pullToRefresh/usePullToRefreshContext.js +13 -0
- package/lib/module/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
- package/lib/module/components/pullToRefresh/utils.js +42 -0
- package/lib/module/components/pullToRefresh/utils.js.map +1 -0
- package/lib/module/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +36 -32
- package/lib/module/components/scrollable/CollapsibleFlatList.js.map +1 -0
- package/lib/module/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +5 -5
- package/lib/module/components/scrollable/CollapsibleScrollView.js.map +1 -0
- package/lib/module/{hooks → components/scrollable}/useAnimatedScroll.js +6 -6
- package/lib/module/components/scrollable/useAnimatedScroll.js.map +1 -0
- package/lib/module/hooks/useInternalCollapsibleContext.js +1 -1
- package/lib/module/hooks/useInternalCollapsibleContext.js.map +1 -1
- package/lib/module/index.js +6 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.d.js +2 -0
- package/lib/module/{types.js.map → types.d.js.map} +0 -0
- package/lib/module/utils/debounce.js +13 -0
- package/lib/module/utils/debounce.js.map +1 -0
- package/lib/module/withCollapsibleContext.js +163 -0
- package/lib/module/withCollapsibleContext.js.map +1 -0
- package/lib/typescript/components/CollapsibleView.d.ts +1 -1
- package/lib/typescript/components/{AnimatedTopView.d.ts → header/AnimatedTopView.d.ts} +0 -0
- package/lib/typescript/components/{CollapsibleHeaderContainer.d.ts → header/CollapsibleHeaderContainer.d.ts} +0 -0
- package/lib/typescript/components/{StickyView.d.ts → header/StickyView.d.ts} +0 -0
- package/lib/typescript/components/pullToRefresh/PullToRefreshContainer.d.ts +8 -0
- package/lib/typescript/components/pullToRefresh/PullToRefreshProvider.d.ts +6 -0
- package/lib/typescript/components/pullToRefresh/RefreshControl.d.ts +9 -0
- package/lib/typescript/components/pullToRefresh/usePullToRefreshContext.d.ts +4 -0
- package/lib/typescript/components/pullToRefresh/utils.d.ts +20 -0
- package/lib/typescript/components/{CollapsibleFlatList.d.ts → scrollable/CollapsibleFlatList.d.ts} +1 -1
- package/lib/typescript/components/{CollapsibleScrollView.d.ts → scrollable/CollapsibleScrollView.d.ts} +1 -1
- package/lib/typescript/{hooks → components/scrollable}/useAnimatedScroll.d.ts +0 -0
- package/lib/typescript/hooks/useInternalCollapsibleContext.d.ts +1 -1
- package/lib/typescript/index.d.ts +6 -5
- package/lib/typescript/utils/debounce.d.ts +1 -0
- package/lib/typescript/{hooks/withCollapsibleContext.d.ts → withCollapsibleContext.d.ts} +0 -0
- package/package.json +4 -2
- package/src/components/CollapsibleContainer.tsx +1 -1
- package/src/components/CollapsibleView.tsx +4 -3
- package/src/components/{AnimatedTopView.tsx → header/AnimatedTopView.tsx} +0 -0
- package/src/components/{CollapsibleHeaderContainer.tsx → header/CollapsibleHeaderContainer.tsx} +6 -4
- package/src/components/{StickyView.tsx → header/StickyView.tsx} +15 -22
- package/src/components/pullToRefresh/PullToRefreshContainer.tsx +65 -0
- package/src/components/pullToRefresh/PullToRefreshProvider.tsx +27 -0
- package/src/components/pullToRefresh/RefreshControl.tsx +80 -0
- package/src/components/pullToRefresh/usePullToRefreshContext.ts +13 -0
- package/src/components/pullToRefresh/utils.ts +49 -0
- package/src/components/scrollable/CollapsibleFlatList.tsx +135 -0
- package/src/components/{CollapsibleScrollView.tsx → scrollable/CollapsibleScrollView.tsx} +6 -6
- package/src/{hooks → components/scrollable}/useAnimatedScroll.ts +8 -8
- package/src/hooks/useInternalCollapsibleContext.ts +1 -1
- package/src/index.tsx +6 -5
- package/src/{types.ts → types.d.ts} +14 -3
- package/src/utils/debounce.ts +10 -0
- package/src/withCollapsibleContext.tsx +201 -0
- package/lib/commonjs/components/CollapsibleFlatList.js.map +0 -1
- package/lib/commonjs/components/CollapsibleHeaderContainer.js.map +0 -1
- package/lib/commonjs/components/CollapsibleScrollView.js.map +0 -1
- package/lib/commonjs/components/StickyView.js.map +0 -1
- package/lib/commonjs/hooks/useAnimatedScroll.js.map +0 -1
- package/lib/commonjs/hooks/withCollapsibleContext.js.map +0 -1
- package/lib/module/components/CollapsibleFlatList.js.map +0 -1
- package/lib/module/components/CollapsibleHeaderContainer.js.map +0 -1
- package/lib/module/components/CollapsibleScrollView.js.map +0 -1
- package/lib/module/components/StickyView.js.map +0 -1
- package/lib/module/hooks/useAnimatedScroll.js.map +0 -1
- package/lib/module/hooks/withCollapsibleContext.js +0 -136
- package/lib/module/hooks/withCollapsibleContext.js.map +0 -1
- package/lib/module/types.js +0 -2
- package/lib/typescript/types.d.ts +0 -33
- package/src/components/CollapsibleFlatList.tsx +0 -119
- package/src/hooks/withCollapsibleContext.tsx +0 -164
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@r0b0t3d/react-native-collapsible",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.2.0-alpha.0",
|
|
4
4
|
"description": "Fully customizable collapsible views",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"react": "16.13.1",
|
|
65
65
|
"react-native": "0.63.4",
|
|
66
66
|
"react-native-builder-bob": "^0.18.0",
|
|
67
|
+
"react-native-gesture-handler": "^1.10.3",
|
|
67
68
|
"react-native-reanimated": "^2.2.0",
|
|
68
69
|
"release-it": "^14.11.5",
|
|
69
70
|
"typescript": "^4.1.3"
|
|
@@ -71,7 +72,8 @@
|
|
|
71
72
|
"peerDependencies": {
|
|
72
73
|
"react": "*",
|
|
73
74
|
"react-native": "*",
|
|
74
|
-
"react-native-
|
|
75
|
+
"react-native-gesture-handler": "*",
|
|
76
|
+
"react-native-reanimated": ">=2.2.0"
|
|
75
77
|
},
|
|
76
78
|
"jest": {
|
|
77
79
|
"preset": "react-native",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { LayoutChangeEvent, StyleSheet, View, ViewProps } from 'react-native';
|
|
3
|
-
import
|
|
3
|
+
import useInternalCollapsibleContext from '../hooks/useInternalCollapsibleContext';
|
|
4
4
|
|
|
5
5
|
type Props = Omit<ViewProps, 'ref' | 'onLayout'> & {
|
|
6
6
|
children: Element;
|
|
@@ -63,7 +63,7 @@ export default function CollapsibleView({
|
|
|
63
63
|
}, []);
|
|
64
64
|
|
|
65
65
|
const handleLayout = useCallback((event: LayoutChangeEvent) => {
|
|
66
|
-
if (event.nativeEvent.layout.height
|
|
66
|
+
if (event.nativeEvent.layout.height >= 0) {
|
|
67
67
|
actualHeight.value = event.nativeEvent.layout.height;
|
|
68
68
|
}
|
|
69
69
|
}, []);
|
|
@@ -142,7 +142,7 @@ export function CollapsibleHeaderText({
|
|
|
142
142
|
iconInitialAngle = 0,
|
|
143
143
|
children,
|
|
144
144
|
}: {
|
|
145
|
-
title: string;
|
|
145
|
+
title: string | Element;
|
|
146
146
|
style?: StyleProp<ViewStyle>;
|
|
147
147
|
titleStyle?: StyleProp<TextStyle>;
|
|
148
148
|
icon?: ReactNode;
|
|
@@ -165,7 +165,7 @@ export function CollapsibleHeaderText({
|
|
|
165
165
|
}, [iconInitialAngle]);
|
|
166
166
|
|
|
167
167
|
return (
|
|
168
|
-
<TouchableOpacity activeOpacity={0.9} onPress={onToggle} style={
|
|
168
|
+
<TouchableOpacity activeOpacity={0.9} onPress={onToggle} style={style}>
|
|
169
169
|
<View style={styles.headerContainer}>
|
|
170
170
|
<Text style={[styles.headerTitle, titleStyle]}>{title}</Text>
|
|
171
171
|
{icon && <Animated.View style={iconStyle}>{icon}</Animated.View>}
|
|
@@ -182,6 +182,7 @@ const styles = StyleSheet.create({
|
|
|
182
182
|
content: {},
|
|
183
183
|
headerContainer: {
|
|
184
184
|
flexDirection: 'row',
|
|
185
|
+
alignItems: 'center',
|
|
185
186
|
},
|
|
186
187
|
headerTitle: {
|
|
187
188
|
flex: 1,
|
|
File without changes
|
package/src/components/{CollapsibleHeaderContainer.tsx → header/CollapsibleHeaderContainer.tsx}
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
-
import
|
|
2
|
+
import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
|
|
3
3
|
import React, { ReactNode, useCallback, useEffect, useMemo } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
LayoutChangeEvent,
|
|
6
|
+
Platform,
|
|
6
7
|
StyleProp,
|
|
7
8
|
StyleSheet,
|
|
8
9
|
View,
|
|
@@ -15,7 +16,7 @@ import Animated, {
|
|
|
15
16
|
useSharedValue,
|
|
16
17
|
withTiming,
|
|
17
18
|
} from 'react-native-reanimated';
|
|
18
|
-
import useCollapsibleContext from '
|
|
19
|
+
import useCollapsibleContext from '../../hooks/useCollapsibleContext';
|
|
19
20
|
|
|
20
21
|
type Props = {
|
|
21
22
|
children: ReactNode;
|
|
@@ -78,14 +79,14 @@ export default function CollapsibleHeaderContainer({
|
|
|
78
79
|
|
|
79
80
|
return (
|
|
80
81
|
<Animated.View
|
|
81
|
-
style={[
|
|
82
|
+
style={[headerStyle, internalStyle]}
|
|
82
83
|
pointerEvents="box-none"
|
|
83
84
|
>
|
|
84
85
|
<View
|
|
85
86
|
key={contentKey}
|
|
86
87
|
onLayout={handleHeaderLayout}
|
|
87
88
|
pointerEvents="box-none"
|
|
88
|
-
style={containerStyle}
|
|
89
|
+
style={[styles.container, containerStyle]}
|
|
89
90
|
>
|
|
90
91
|
{children}
|
|
91
92
|
</View>
|
|
@@ -96,5 +97,6 @@ export default function CollapsibleHeaderContainer({
|
|
|
96
97
|
const styles = StyleSheet.create({
|
|
97
98
|
container: {
|
|
98
99
|
backgroundColor: 'white',
|
|
100
|
+
marginTop: Platform.OS === 'android' ? -1 : 0,
|
|
99
101
|
},
|
|
100
102
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
-
import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
|
|
3
2
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
3
|
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
|
5
|
-
import useCollapsibleContext from '
|
|
4
|
+
import useCollapsibleContext from '../../hooks/useCollapsibleContext';
|
|
5
|
+
import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
|
|
6
6
|
import Animated, {
|
|
7
7
|
Extrapolate,
|
|
8
8
|
interpolate,
|
|
9
9
|
useAnimatedStyle,
|
|
10
|
-
|
|
10
|
+
useDerivedValue,
|
|
11
11
|
} from 'react-native-reanimated';
|
|
12
12
|
|
|
13
13
|
type Props = {
|
|
@@ -20,46 +20,39 @@ let stickyKey = 0;
|
|
|
20
20
|
export default function StickyView({ children, style }: Props) {
|
|
21
21
|
const key = useMemo(() => `sticky_${stickyKey++}`, []);
|
|
22
22
|
const viewRef = useRef<View>(null);
|
|
23
|
-
const {
|
|
23
|
+
const { handleStickyViewLayout, stickyViewTops, stickyViewPositions } =
|
|
24
24
|
useInternalCollapsibleContext();
|
|
25
25
|
const { scrollY } = useCollapsibleContext();
|
|
26
|
-
const layoutValues = useSharedValue({ top: 0, height: 0 });
|
|
27
26
|
|
|
28
27
|
useEffect(() => {
|
|
29
28
|
return () => handleStickyViewLayout(key, undefined);
|
|
30
29
|
}, []);
|
|
31
30
|
|
|
32
31
|
const handleLayout = useCallback(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
containerRef.current,
|
|
37
|
-
(left, top, width, height) => {
|
|
38
|
-
handleStickyViewLayout(key, { left, top, width, height });
|
|
39
|
-
layoutValues.value = { top, height };
|
|
40
|
-
},
|
|
41
|
-
() => {}
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
}, [handleStickyViewLayout]);
|
|
32
|
+
handleStickyViewLayout(key, viewRef);
|
|
33
|
+
}, [handleStickyViewLayout, key]);
|
|
45
34
|
|
|
46
|
-
const
|
|
35
|
+
const translateY = useDerivedValue(() => {
|
|
47
36
|
const top = stickyViewTops.value[key] || 0;
|
|
48
|
-
const
|
|
49
|
-
const
|
|
37
|
+
const layoutValues = stickyViewPositions.value[key] || { top: 0 };
|
|
38
|
+
const inputMid = layoutValues.top - top;
|
|
39
|
+
return interpolate(
|
|
50
40
|
scrollY.value,
|
|
51
41
|
[0, inputMid, inputMid + 100000],
|
|
52
42
|
[0, 0, 100000],
|
|
53
43
|
Extrapolate.CLAMP
|
|
54
44
|
);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
55
48
|
return {
|
|
56
49
|
transform: [
|
|
57
50
|
{
|
|
58
|
-
translateY: translateY,
|
|
51
|
+
translateY: translateY.value,
|
|
59
52
|
},
|
|
60
53
|
],
|
|
61
54
|
};
|
|
62
|
-
}, [
|
|
55
|
+
}, []);
|
|
63
56
|
|
|
64
57
|
return (
|
|
65
58
|
<Animated.View
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NativeViewGestureHandler,
|
|
3
|
+
PanGestureHandler,
|
|
4
|
+
} from 'react-native-gesture-handler';
|
|
5
|
+
import React, { useRef } from 'react';
|
|
6
|
+
import Animated, {
|
|
7
|
+
useAnimatedGestureHandler,
|
|
8
|
+
withTiming,
|
|
9
|
+
} from 'react-native-reanimated';
|
|
10
|
+
import usePullToRefreshContext from './usePullToRefreshContext';
|
|
11
|
+
import { StyleSheet } from 'react-native';
|
|
12
|
+
import { rubberClamp } from './utils';
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
scrollY: Animated.SharedValue<number>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function PullToRefreshContainer({ children, scrollY }: Props) {
|
|
20
|
+
const scrollRef = useRef();
|
|
21
|
+
const panRef = useRef();
|
|
22
|
+
const { refreshValue, internalRefreshing, internalHeight } =
|
|
23
|
+
usePullToRefreshContext();
|
|
24
|
+
|
|
25
|
+
const gestureHandler = useAnimatedGestureHandler({
|
|
26
|
+
onStart: (_, ctx: any) => {
|
|
27
|
+
ctx.startY = internalRefreshing.value ? refreshValue.value : 0;
|
|
28
|
+
},
|
|
29
|
+
onActive: (event, ctx: any) => {
|
|
30
|
+
if (scrollY.value <= 1) {
|
|
31
|
+
const tranY = event.translationY + ctx.startY;
|
|
32
|
+
const clampedValue = rubberClamp(tranY, 0, internalHeight.value);
|
|
33
|
+
refreshValue.value = clampedValue;
|
|
34
|
+
if (clampedValue > internalHeight.value) {
|
|
35
|
+
internalRefreshing.value = true;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
refreshValue.value = 0;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
onEnd: () => {
|
|
42
|
+
if (refreshValue.value > 0) {
|
|
43
|
+
const value = internalRefreshing.value ? internalHeight.value : 0;
|
|
44
|
+
refreshValue.value = withTiming(value);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<PanGestureHandler
|
|
51
|
+
ref={panRef}
|
|
52
|
+
simultaneousHandlers={scrollRef}
|
|
53
|
+
onGestureEvent={gestureHandler}
|
|
54
|
+
shouldCancelWhenOutside={false}
|
|
55
|
+
enableTrackpadTwoFingerGesture
|
|
56
|
+
maxPointers={1}
|
|
57
|
+
>
|
|
58
|
+
<Animated.View style={StyleSheet.absoluteFill}>
|
|
59
|
+
<NativeViewGestureHandler ref={scrollRef} simultaneousHandlers={panRef}>
|
|
60
|
+
{children}
|
|
61
|
+
</NativeViewGestureHandler>
|
|
62
|
+
</Animated.View>
|
|
63
|
+
</PanGestureHandler>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { useSharedValue } from 'react-native-reanimated';
|
|
3
|
+
import { PullToRefreshContext } from './usePullToRefreshContext';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function PullToRefreshProvider({ children }: Props) {
|
|
10
|
+
const refreshValue = useSharedValue(0);
|
|
11
|
+
const internalRefreshing = useSharedValue(false);
|
|
12
|
+
const internalHeight = useSharedValue(0);
|
|
13
|
+
|
|
14
|
+
const context = useMemo(() => {
|
|
15
|
+
return {
|
|
16
|
+
refreshValue: refreshValue,
|
|
17
|
+
internalRefreshing,
|
|
18
|
+
internalHeight,
|
|
19
|
+
};
|
|
20
|
+
}, [refreshValue, internalRefreshing, internalHeight]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<PullToRefreshContext.Provider value={context}>
|
|
24
|
+
{children}
|
|
25
|
+
</PullToRefreshContext.Provider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import React, { useCallback, useEffect } from 'react';
|
|
3
|
+
import { StyleSheet } from 'react-native';
|
|
4
|
+
import Animated, {
|
|
5
|
+
runOnJS,
|
|
6
|
+
useAnimatedProps,
|
|
7
|
+
useAnimatedReaction,
|
|
8
|
+
useAnimatedStyle,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
import usePullToRefreshContext from './usePullToRefreshContext';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
height?: number;
|
|
15
|
+
refreshing: boolean;
|
|
16
|
+
onRefresh: () => void;
|
|
17
|
+
renderAnimation: (animatedProps: any) => React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function RefreshControl({
|
|
21
|
+
height = 100,
|
|
22
|
+
refreshing,
|
|
23
|
+
onRefresh,
|
|
24
|
+
renderAnimation,
|
|
25
|
+
}: Props) {
|
|
26
|
+
const { refreshValue, internalRefreshing, internalHeight } =
|
|
27
|
+
usePullToRefreshContext();
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
internalHeight.value = height;
|
|
31
|
+
}, [height]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
internalRefreshing.value = refreshing;
|
|
35
|
+
}, [refreshing]);
|
|
36
|
+
|
|
37
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
38
|
+
return {
|
|
39
|
+
height: refreshValue.value,
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const handleRefresh = useCallback(() => {
|
|
44
|
+
console.log('refresh');
|
|
45
|
+
onRefresh();
|
|
46
|
+
}, [onRefresh]);
|
|
47
|
+
|
|
48
|
+
useAnimatedReaction(
|
|
49
|
+
() => internalRefreshing.value,
|
|
50
|
+
(result, prev) => {
|
|
51
|
+
if (result !== prev) {
|
|
52
|
+
if (result) {
|
|
53
|
+
runOnJS(handleRefresh)();
|
|
54
|
+
} else {
|
|
55
|
+
refreshValue.value = withTiming(0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const animatedProps = useAnimatedProps(() => {
|
|
62
|
+
return {
|
|
63
|
+
progress: internalRefreshing.value
|
|
64
|
+
? undefined
|
|
65
|
+
: Math.min(refreshValue.value / height, 1),
|
|
66
|
+
};
|
|
67
|
+
}, [height]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Animated.View style={[styles.container, animatedStyle]}>
|
|
71
|
+
{renderAnimation(animatedProps)}
|
|
72
|
+
</Animated.View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
container: {
|
|
78
|
+
overflow: 'hidden',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { PullToRefreshContextType } from '../../types';
|
|
3
|
+
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
export const PullToRefreshContext = createContext<PullToRefreshContextType>({});
|
|
6
|
+
|
|
7
|
+
export default function usePullToRefreshContext() {
|
|
8
|
+
const ctx = useContext(PullToRefreshContext);
|
|
9
|
+
if (!ctx) {
|
|
10
|
+
throw new Error('Component should be wrapped with withCollapsibleContext');
|
|
11
|
+
}
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const springConfig = (velocity: number) => {
|
|
2
|
+
'worklet';
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
stiffness: 1000,
|
|
6
|
+
damping: 500,
|
|
7
|
+
mass: 3,
|
|
8
|
+
overshootClamping: true,
|
|
9
|
+
restDisplacementThreshold: 0.01,
|
|
10
|
+
restSpeedThreshold: 0.01,
|
|
11
|
+
velocity,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function clamp(value: number, lowerbound: number, upperbound: number) {
|
|
16
|
+
'worklet';
|
|
17
|
+
|
|
18
|
+
return Math.min(Math.max(value, lowerbound), upperbound);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* calculates rubber value
|
|
23
|
+
*
|
|
24
|
+
* @param x distance from the edge
|
|
25
|
+
* @param dim dimension, either width or height
|
|
26
|
+
* @param coeff constant value, UIScrollView uses 0.55
|
|
27
|
+
* @returns rubber = (1.0 – (1.0 / ((x * coeff / dim) + 1.0))) * dim
|
|
28
|
+
*/
|
|
29
|
+
export const rubberBandClamp = (x: number, dim: number, coeff: number) => {
|
|
30
|
+
'worklet';
|
|
31
|
+
|
|
32
|
+
return (1.0 - 1.0 / ((x * coeff) / dim + 1.0)) * dim;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const rubberClamp = (
|
|
36
|
+
y: number,
|
|
37
|
+
topBound: number,
|
|
38
|
+
bottomBound: number,
|
|
39
|
+
coeff = 0.55
|
|
40
|
+
) => {
|
|
41
|
+
'worklet';
|
|
42
|
+
|
|
43
|
+
const clampedY = clamp(y, topBound, bottomBound);
|
|
44
|
+
const diff = Math.abs(y - clampedY);
|
|
45
|
+
const sign = clampedY > y ? -1 : 1;
|
|
46
|
+
const dimension = bottomBound - topBound;
|
|
47
|
+
|
|
48
|
+
return clampedY + sign * rubberBandClamp(diff, dimension, coeff);
|
|
49
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { FlatListProps, View, StyleSheet, FlatList } from 'react-native';
|
|
9
|
+
import Animated, {
|
|
10
|
+
runOnJS,
|
|
11
|
+
useAnimatedReaction,
|
|
12
|
+
} from 'react-native-reanimated';
|
|
13
|
+
import AnimatedTopView from '../header/AnimatedTopView';
|
|
14
|
+
import useAnimatedScroll from './useAnimatedScroll';
|
|
15
|
+
import useCollapsibleContext from '../../hooks/useCollapsibleContext';
|
|
16
|
+
import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
|
|
17
|
+
import type { CollapsibleProps } from '../../types';
|
|
18
|
+
import PullToRefreshContainer from '../pullToRefresh/PullToRefreshContainer';
|
|
19
|
+
|
|
20
|
+
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
|
21
|
+
|
|
22
|
+
type Props<Data> = Omit<FlatListProps<Data>, 'scrollEnabled'> &
|
|
23
|
+
CollapsibleProps;
|
|
24
|
+
|
|
25
|
+
export default function CollapsibleFlatList<Data>({
|
|
26
|
+
headerSnappable = true,
|
|
27
|
+
...props
|
|
28
|
+
}: Props<Data>) {
|
|
29
|
+
const { headerHeight, scrollY } = useCollapsibleContext();
|
|
30
|
+
const { contentMinHeight } = useInternalCollapsibleContext();
|
|
31
|
+
const scrollRef = useRef<any>(null);
|
|
32
|
+
const mounted = useRef(true);
|
|
33
|
+
const contentHeight = useRef(0);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
return () => {
|
|
37
|
+
mounted.current = false;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const scrollTo = useCallback(
|
|
42
|
+
(yValue: number, animated = true) => {
|
|
43
|
+
scrollRef.current?.scrollToOffset({
|
|
44
|
+
offset: yValue,
|
|
45
|
+
animated,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
[scrollRef]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const handleInternalContentHeight = useCallback((value: number) => {
|
|
52
|
+
if (mounted.current) {
|
|
53
|
+
setInternalContentMinHeight(value);
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const { scrollHandler } = useAnimatedScroll({
|
|
58
|
+
headerSnappable,
|
|
59
|
+
scrollTo,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const [internalContentMinHeight, setInternalContentMinHeight] = useState(
|
|
63
|
+
contentMinHeight.value
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
useAnimatedReaction(
|
|
67
|
+
() => {
|
|
68
|
+
return contentMinHeight.value;
|
|
69
|
+
},
|
|
70
|
+
(result, previous) => {
|
|
71
|
+
if (result !== previous) {
|
|
72
|
+
if (
|
|
73
|
+
contentHeight.current < contentMinHeight.value &&
|
|
74
|
+
internalContentMinHeight !== contentMinHeight.value
|
|
75
|
+
) {
|
|
76
|
+
runOnJS(handleInternalContentHeight)(contentMinHeight.value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const contentContainerStyle = useMemo(
|
|
83
|
+
() => [
|
|
84
|
+
styles.contentContainer,
|
|
85
|
+
{ minHeight: internalContentMinHeight },
|
|
86
|
+
props.contentContainerStyle,
|
|
87
|
+
],
|
|
88
|
+
[props.contentContainerStyle, internalContentMinHeight]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const handleContentSizeChange = useCallback((_, height) => {
|
|
92
|
+
contentHeight.current = height;
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const renderListHeader = () => (
|
|
96
|
+
<View>
|
|
97
|
+
<AnimatedTopView height={headerHeight} />
|
|
98
|
+
{props.ListHeaderComponent}
|
|
99
|
+
</View>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<PullToRefreshContainer scrollY={scrollY}>
|
|
104
|
+
{/* @ts-ignore */}
|
|
105
|
+
<AnimatedFlatList
|
|
106
|
+
ref={scrollRef}
|
|
107
|
+
bounces={false}
|
|
108
|
+
keyboardDismissMode="on-drag"
|
|
109
|
+
keyboardShouldPersistTaps="handled"
|
|
110
|
+
scrollEventThrottle={1}
|
|
111
|
+
{...props}
|
|
112
|
+
style={[styles.container, props.style]}
|
|
113
|
+
contentContainerStyle={contentContainerStyle}
|
|
114
|
+
onScroll={scrollHandler}
|
|
115
|
+
ListHeaderComponent={renderListHeader()}
|
|
116
|
+
onContentSizeChange={handleContentSizeChange}
|
|
117
|
+
/>
|
|
118
|
+
</PullToRefreshContainer>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const styles = StyleSheet.create({
|
|
123
|
+
container: {
|
|
124
|
+
...StyleSheet.absoluteFillObject,
|
|
125
|
+
},
|
|
126
|
+
contentContainer: {
|
|
127
|
+
flexGrow: 1,
|
|
128
|
+
},
|
|
129
|
+
topView: {
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
top: 0,
|
|
132
|
+
left: 0,
|
|
133
|
+
right: 0,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import AnimatedTopView from '
|
|
2
|
-
import useAnimatedScroll from '
|
|
1
|
+
import AnimatedTopView from '../header/AnimatedTopView';
|
|
2
|
+
import useAnimatedScroll from './useAnimatedScroll';
|
|
3
3
|
import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import { ScrollViewProps, StyleSheet } from 'react-native';
|
|
5
5
|
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
6
|
-
import type { CollapsibleProps } from '
|
|
7
|
-
import useCollapsibleContext from '
|
|
8
|
-
import
|
|
6
|
+
import type { CollapsibleProps } from '../../types';
|
|
7
|
+
import useCollapsibleContext from '../../hooks/useCollapsibleContext';
|
|
8
|
+
import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
|
|
9
9
|
|
|
10
10
|
type Props = ScrollViewProps &
|
|
11
11
|
CollapsibleProps & {
|
|
@@ -52,7 +52,7 @@ export default function CollapsibleScrollView({
|
|
|
52
52
|
onScroll={scrollHandler}
|
|
53
53
|
keyboardDismissMode="on-drag"
|
|
54
54
|
keyboardShouldPersistTaps="handled"
|
|
55
|
-
scrollEventThrottle={
|
|
55
|
+
scrollEventThrottle={1}
|
|
56
56
|
>
|
|
57
57
|
<Animated.View style={animatedStyle}>
|
|
58
58
|
<AnimatedTopView height={headerHeight} />
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
useAnimatedScrollHandler,
|
|
7
7
|
useSharedValue,
|
|
8
8
|
} from 'react-native-reanimated';
|
|
9
|
-
import useCollapsibleContext from '
|
|
10
|
-
import
|
|
9
|
+
import useCollapsibleContext from '../../hooks/useCollapsibleContext';
|
|
10
|
+
import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
|
|
11
11
|
|
|
12
12
|
const { height: wHeight } = Dimensions.get('window');
|
|
13
13
|
|
|
@@ -33,9 +33,9 @@ export default function useAnimatedScroll({
|
|
|
33
33
|
|
|
34
34
|
const collapse = useCallback(() => {
|
|
35
35
|
scrollTo(
|
|
36
|
-
Math.min(fixedHeaderHeight.
|
|
36
|
+
Math.min(fixedHeaderHeight.value || 0, firstStickyViewY.value || 0)
|
|
37
37
|
);
|
|
38
|
-
}, [scrollTo
|
|
38
|
+
}, [scrollTo]);
|
|
39
39
|
|
|
40
40
|
const expand = useCallback(() => scrollTo(0), [scrollTo]);
|
|
41
41
|
|
|
@@ -58,9 +58,9 @@ export default function useAnimatedScroll({
|
|
|
58
58
|
onEndDrag: () => {
|
|
59
59
|
if (!headerSnappable) return;
|
|
60
60
|
const maxY =
|
|
61
|
-
firstStickyViewY.
|
|
62
|
-
? firstStickyViewY.
|
|
63
|
-
: fixedHeaderHeight.
|
|
61
|
+
firstStickyViewY.value && firstStickyViewY.value > 0
|
|
62
|
+
? firstStickyViewY.value
|
|
63
|
+
: fixedHeaderHeight.value || 0;
|
|
64
64
|
|
|
65
65
|
if (scrollY.value < maxY) {
|
|
66
66
|
const delta = Math.abs(scrollY.value - maxY);
|
|
@@ -74,7 +74,7 @@ export default function useAnimatedScroll({
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
},
|
|
77
|
-
[
|
|
77
|
+
[scrollTo]
|
|
78
78
|
);
|
|
79
79
|
|
|
80
80
|
return {
|
|
@@ -5,7 +5,7 @@ export const InternalCollapsibleContext =
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
createContext<CollapsibleContextInternalType>();
|
|
7
7
|
|
|
8
|
-
export function useInternalCollapsibleContext() {
|
|
8
|
+
export default function useInternalCollapsibleContext() {
|
|
9
9
|
const ctx = useContext(InternalCollapsibleContext);
|
|
10
10
|
if (!ctx) {
|
|
11
11
|
throw new Error('Component should be wrapped with withCollapsibleContext');
|
package/src/index.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { default as withCollapsibleContext } from './
|
|
1
|
+
export { default as withCollapsibleContext } from './withCollapsibleContext';
|
|
2
2
|
export { default as useCollapsibleContext } from './hooks/useCollapsibleContext';
|
|
3
3
|
|
|
4
4
|
export { default as CollapsibleContainer } from './components/CollapsibleContainer';
|
|
5
|
-
export { default as CollapsibleFlatList } from './components/CollapsibleFlatList';
|
|
6
|
-
export { default as CollapsibleScrollView } from './components/CollapsibleScrollView';
|
|
7
|
-
export { default as CollapsibleHeaderContainer } from './components/CollapsibleHeaderContainer';
|
|
5
|
+
export { default as CollapsibleFlatList } from './components/scrollable/CollapsibleFlatList';
|
|
6
|
+
export { default as CollapsibleScrollView } from './components/scrollable/CollapsibleScrollView';
|
|
7
|
+
export { default as CollapsibleHeaderContainer } from './components/header/CollapsibleHeaderContainer';
|
|
8
|
+
export { default as StickyView } from './components/header/StickyView';
|
|
9
|
+
export { default as RefreshControl } from './components/pullToRefresh/RefreshControl';
|
|
8
10
|
export { default as CollapsibleView } from './components/CollapsibleView';
|
|
9
|
-
export { default as StickyView } from './components/StickyView';
|
|
10
11
|
export * from './components/CollapsibleView';
|