react-native-header-motion 0.1.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/LICENSE +20 -0
- package/README.md +479 -0
- package/lib/module/components/FlatList.js +64 -0
- package/lib/module/components/FlatList.js.map +1 -0
- package/lib/module/components/Header.js +19 -0
- package/lib/module/components/Header.js.map +1 -0
- package/lib/module/components/HeaderBase.js +59 -0
- package/lib/module/components/HeaderBase.js.map +1 -0
- package/lib/module/components/HeaderMotion.js +84 -0
- package/lib/module/components/HeaderMotion.js.map +1 -0
- package/lib/module/components/ScrollManager.js +39 -0
- package/lib/module/components/ScrollManager.js.map +1 -0
- package/lib/module/components/ScrollView.js +47 -0
- package/lib/module/components/ScrollView.js.map +1 -0
- package/lib/module/components/index.js +9 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/context.js +5 -0
- package/lib/module/context.js.map +1 -0
- package/lib/module/hooks/index.js +6 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useActiveScrollId.js +47 -0
- package/lib/module/hooks/useActiveScrollId.js.map +1 -0
- package/lib/module/hooks/useMotionProgress.js +58 -0
- package/lib/module/hooks/useMotionProgress.js.map +1 -0
- package/lib/module/hooks/useScrollManager.js +150 -0
- package/lib/module/hooks/useScrollManager.js.map +1 -0
- package/lib/module/index.js +42 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/defaults.js +10 -0
- package/lib/module/utils/defaults.js.map +1 -0
- package/lib/module/utils/index.js +5 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/values.js +11 -0
- package/lib/module/utils/values.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/FlatList.d.ts +30 -0
- package/lib/typescript/src/components/FlatList.d.ts.map +1 -0
- package/lib/typescript/src/components/Header.d.ts +19 -0
- package/lib/typescript/src/components/Header.d.ts.map +1 -0
- package/lib/typescript/src/components/HeaderBase.d.ts +34 -0
- package/lib/typescript/src/components/HeaderBase.d.ts.map +1 -0
- package/lib/typescript/src/components/HeaderMotion.d.ts +52 -0
- package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -0
- package/lib/typescript/src/components/ScrollManager.d.ts +40 -0
- package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -0
- package/lib/typescript/src/components/ScrollView.d.ts +24 -0
- package/lib/typescript/src/components/ScrollView.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +7 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -0
- package/lib/typescript/src/context.d.ts +14 -0
- package/lib/typescript/src/context.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +4 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts +32 -0
- package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useMotionProgress.d.ts +38 -0
- package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useScrollManager.d.ts +37 -0
- package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +51 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +43 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/defaults.d.ts +6 -0
- package/lib/typescript/src/utils/defaults.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +3 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/values.d.ts +3 -0
- package/lib/typescript/src/utils/values.d.ts.map +1 -0
- package/package.json +164 -0
- package/src/components/FlatList.tsx +72 -0
- package/src/components/Header.tsx +30 -0
- package/src/components/HeaderBase.tsx +51 -0
- package/src/components/HeaderMotion.tsx +183 -0
- package/src/components/ScrollManager.tsx +58 -0
- package/src/components/ScrollView.tsx +58 -0
- package/src/components/index.ts +6 -0
- package/src/context.ts +20 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useActiveScrollId.ts +59 -0
- package/src/hooks/useMotionProgress.ts +56 -0
- package/src/hooks/useScrollManager.ts +186 -0
- package/src/index.ts +76 -0
- package/src/types.ts +62 -0
- package/src/utils/defaults.ts +16 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/values.ts +6 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useSharedValue } from 'react-native-reanimated';
|
|
3
|
+
import type { ActiveScrollIdValues, SetActiveScrollId } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to manage active scroll ID for multi-scroll scenarios (e.g. tabs with different scroll views).
|
|
7
|
+
* Returns both a state value and a shared value, along with a setter function.
|
|
8
|
+
*
|
|
9
|
+
* Use this when you have multiple scroll views (like in a tabbed interface) and need to
|
|
10
|
+
* track which one is currently active. Pass the shared value to `HeaderMotion`'s `activeScrollId` prop.
|
|
11
|
+
*
|
|
12
|
+
* @template T - The type of the scroll ID string
|
|
13
|
+
* @param initialActiveScrollId - The initial active scroll ID
|
|
14
|
+
* @returns A tuple containing:
|
|
15
|
+
* - `[0]`: Object with `state` (React state) and `sv` (shared value) for the active scroll ID
|
|
16
|
+
* - `[1]`: Function to set the active scroll ID
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function TabbedScrollView() {
|
|
21
|
+
* const [activeScroll, setActiveScroll] = useActiveScrollId('tab1');
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <HeaderMotion activeScrollId={activeScroll.sv}>
|
|
25
|
+
* <Tabs onTabChange={setActiveScroll}>
|
|
26
|
+
* <Tab id="tab1" />
|
|
27
|
+
* <Tab id="tab2" />
|
|
28
|
+
* </Tabs>
|
|
29
|
+
* </HeaderMotion>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function useActiveScrollId<T extends string>(
|
|
35
|
+
initialActiveScrollId: T
|
|
36
|
+
): [ActiveScrollIdValues<T>, SetActiveScrollId<T>] {
|
|
37
|
+
const [activeScrollIdState, setActiveScrollIdState] = useState<T>(
|
|
38
|
+
initialActiveScrollId
|
|
39
|
+
);
|
|
40
|
+
const activeScrollIdSv = useSharedValue<T>(initialActiveScrollId);
|
|
41
|
+
|
|
42
|
+
const setActiveScrollId = useCallback<SetActiveScrollId<T>>(
|
|
43
|
+
(newId) => {
|
|
44
|
+
setActiveScrollIdState(newId);
|
|
45
|
+
activeScrollIdSv.set(newId);
|
|
46
|
+
},
|
|
47
|
+
[setActiveScrollIdState, activeScrollIdSv]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const values = useMemo<ActiveScrollIdValues<T>>(
|
|
51
|
+
() => ({
|
|
52
|
+
state: activeScrollIdState,
|
|
53
|
+
sv: activeScrollIdSv,
|
|
54
|
+
}),
|
|
55
|
+
[activeScrollIdState, activeScrollIdSv]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return [values, setActiveScrollId];
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { HeaderMotionContext } from '../context';
|
|
3
|
+
import type { MotionProgress } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to access motion progress values and measuring functions for header animations.
|
|
7
|
+
* Returns the progress value (0-1), threshold, and measurement functions.
|
|
8
|
+
*
|
|
9
|
+
* Must be used within a {@link HeaderMotion} component.
|
|
10
|
+
*
|
|
11
|
+
* @returns Motion progress values and measuring functions:
|
|
12
|
+
* - `progress`: Shared value from 0 to 1
|
|
13
|
+
* - `progressThreshold`: The threshold at which animation completes
|
|
14
|
+
* - `measureTotalHeight`: Function to measure total header height. Should be passed to the `onLayout` prop of the base of a header, to let scrollables account for the total header height
|
|
15
|
+
* - `measureDynamic`: Function to measure a dimension of choice of the animated element of the header - should be passed to the `onLayout` prop of such. If used, can be used for dynamic calculation of the {@link progressThreshold}.
|
|
16
|
+
*
|
|
17
|
+
* @throws Error if used outside of a {@link HeaderMotion} component
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function MyHeader() {
|
|
22
|
+
* const { progress, progressThreshold, measureTotalHeight, measureDynamic } = useMotionProgress();
|
|
23
|
+
* const dynamicStyle = useAnimatedStyle(() => {
|
|
24
|
+
* const translateY = interpolate(
|
|
25
|
+
* progress.value,
|
|
26
|
+
* [0, 1],
|
|
27
|
+
* [0, -progressThreshold],
|
|
28
|
+
* Extrapolation.CLAMP,
|
|
29
|
+
* )
|
|
30
|
+
* return { transform: [{ translateY }] }
|
|
31
|
+
* })
|
|
32
|
+
* return (
|
|
33
|
+
* <AnimatedHeaderBase onLayout={measureTotalHeight}>
|
|
34
|
+
* <Animated.View onLayout={measureDynamic} style={dynamicStyle} />
|
|
35
|
+
* </AnimatedHeaderBase>
|
|
36
|
+
* )
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useMotionProgress(): MotionProgress {
|
|
41
|
+
const ctxValue = useContext(HeaderMotionContext);
|
|
42
|
+
if (!ctxValue) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'useMotionProgress must be used within a <HeaderMotion /> component. If using inside a navigation header, consider using <HeaderMotion.Header /> instead to ensure context access.'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const { progress, measureTotalHeight, measureDynamic, progressThreshold } =
|
|
48
|
+
ctxValue;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
progress,
|
|
52
|
+
measureTotalHeight,
|
|
53
|
+
measureDynamic,
|
|
54
|
+
progressThreshold,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { useContext, useCallback, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
measure,
|
|
4
|
+
scrollTo,
|
|
5
|
+
useAnimatedReaction,
|
|
6
|
+
useAnimatedRef,
|
|
7
|
+
useAnimatedScrollHandler,
|
|
8
|
+
useAnimatedStyle,
|
|
9
|
+
type ScrollHandler,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
import { RuntimeKind, scheduleOnUI } from 'react-native-worklets';
|
|
12
|
+
import { HeaderMotionContext } from '../context';
|
|
13
|
+
import type { ScrollManagerConfig, ScrollValues } from '../types';
|
|
14
|
+
import { DEFAULT_SCROLL_ID, getInitialScrollValue } from '../utils';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook that manages scroll tracking and synchronization for header animations.
|
|
18
|
+
* Returns props to apply to scrollable components and additional values that help with adjusting styling of the scrollables to header's dimensions.
|
|
19
|
+
*
|
|
20
|
+
* This hook handles:
|
|
21
|
+
* - Scroll position tracking
|
|
22
|
+
* - Synchronization between multiple scroll views (when using multiple scroll IDs)
|
|
23
|
+
* - Content container minimum height calculations for cases where one of the tracked scrollables does not take enough space to reach the progress threshold/
|
|
24
|
+
*
|
|
25
|
+
* Must be used within a HeaderMotion component.
|
|
26
|
+
*
|
|
27
|
+
* @param scrollId - Optional unique identifier for the related scrollable.
|
|
28
|
+
* Use when you have multiple scrollables (e.g., in tabs).
|
|
29
|
+
* @returns Configuration object containing:
|
|
30
|
+
* - `scrollableProps`: Props to apply to scrollable component (onScroll, scrollEventThrottle, ref)
|
|
31
|
+
* - `headerMotionContext`: Header context values (originalHeaderHeight, minHeightContentContainerStyle)
|
|
32
|
+
*
|
|
33
|
+
* @throws Error if used outside of a HeaderMotion component
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* function CustomScrollComponent() {
|
|
38
|
+
* const { scrollableProps, headerMotionContext } = useScrollManager('myScroll');
|
|
39
|
+
*
|
|
40
|
+
* return (
|
|
41
|
+
* <CustomScrollView {...scrollableProps}>
|
|
42
|
+
* <View style={{ paddingTop: headerMotionContext.originalHeaderHeight }}>
|
|
43
|
+
* Content
|
|
44
|
+
* </View>
|
|
45
|
+
* </CustomScrollView>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function useScrollManager(scrollId?: string): ScrollManagerConfig {
|
|
51
|
+
const ctxValue = useContext(HeaderMotionContext);
|
|
52
|
+
if (!ctxValue) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'useScrollManager must be used within a HeaderMotion component'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
scrollValues,
|
|
60
|
+
progress,
|
|
61
|
+
activeScrollId,
|
|
62
|
+
progressThreshold,
|
|
63
|
+
originalHeaderHeight,
|
|
64
|
+
} = ctxValue;
|
|
65
|
+
const id = scrollId ?? DEFAULT_SCROLL_ID;
|
|
66
|
+
|
|
67
|
+
const animatedRef = useAnimatedRef<any>(); // TODO: better typing
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
return () => {
|
|
71
|
+
scheduleOnUI((scrollIdToDelete) => {
|
|
72
|
+
scrollValues.modify((value) => {
|
|
73
|
+
'worklet';
|
|
74
|
+
delete value[scrollIdToDelete];
|
|
75
|
+
return value;
|
|
76
|
+
});
|
|
77
|
+
}, id);
|
|
78
|
+
};
|
|
79
|
+
}, [scrollValues, id]);
|
|
80
|
+
|
|
81
|
+
useAnimatedReaction(
|
|
82
|
+
() => progress.value,
|
|
83
|
+
(newProgress, oldProgress) => {
|
|
84
|
+
// FUTURE: If really needed for, can use other scroll handlers to only do this either on scroll end or between scroll end and momentum end in onScroll (keep context in shared value)
|
|
85
|
+
// Only sync inactive scroll views when we have multiple tabs being tracked
|
|
86
|
+
const currentActiveScrollId = activeScrollId?.get();
|
|
87
|
+
if (
|
|
88
|
+
!currentActiveScrollId ||
|
|
89
|
+
id === currentActiveScrollId ||
|
|
90
|
+
oldProgress === null
|
|
91
|
+
) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!scrollValues.get()[id]) {
|
|
96
|
+
scrollValues.modify((value) => {
|
|
97
|
+
(value as ScrollValues)[id] = getInitialScrollValue();
|
|
98
|
+
return value;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let newCur = -1;
|
|
103
|
+
|
|
104
|
+
scrollValues.modify((value) => {
|
|
105
|
+
let scrollValue = value[id];
|
|
106
|
+
if (!scrollValue) {
|
|
107
|
+
(value as ScrollValues)[id] = getInitialScrollValue();
|
|
108
|
+
scrollValue = value[id]!;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const progressDiff = oldProgress - newProgress;
|
|
112
|
+
newCur = scrollValue.current - progressDiff * progressThreshold;
|
|
113
|
+
const newMin = newCur - newProgress * progressThreshold;
|
|
114
|
+
scrollValue.current = newCur;
|
|
115
|
+
scrollValue.min = newMin;
|
|
116
|
+
|
|
117
|
+
return value;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (newCur >= 0) {
|
|
121
|
+
scrollTo(animatedRef, 0, newCur, false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const scrollHandler = useCallback<ScrollHandler>(
|
|
127
|
+
(e) => {
|
|
128
|
+
'worklet';
|
|
129
|
+
|
|
130
|
+
scrollValues.modify((value) => {
|
|
131
|
+
if (!value[id]) {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const activeScrollIdValue = activeScrollId?.get();
|
|
136
|
+
if (activeScrollIdValue && activeScrollIdValue !== id) {
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const oldCurrent = value[id].current;
|
|
141
|
+
const oldMin = value[id].min;
|
|
142
|
+
const isCollapsed = oldCurrent >= oldMin + progressThreshold - 0.001;
|
|
143
|
+
|
|
144
|
+
const newCurrent = e.contentOffset.y;
|
|
145
|
+
value[id].current = newCurrent;
|
|
146
|
+
|
|
147
|
+
if (isCollapsed) {
|
|
148
|
+
value[id].min = Math.max(0, newCurrent - progressThreshold);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return value;
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
[scrollValues, id, activeScrollId, progressThreshold]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const onScroll = useAnimatedScrollHandler(scrollHandler);
|
|
158
|
+
|
|
159
|
+
const minHeightContentContainerStyle = useAnimatedStyle(() => {
|
|
160
|
+
if (globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative) {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const measurement = measure(animatedRef);
|
|
165
|
+
|
|
166
|
+
if (!measurement) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
minHeight: measurement.height + progressThreshold,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const scrollableProps = {
|
|
176
|
+
onScroll,
|
|
177
|
+
scrollEventThrottle: 16,
|
|
178
|
+
ref: animatedRef,
|
|
179
|
+
};
|
|
180
|
+
const headerMotionContext = {
|
|
181
|
+
originalHeaderHeight,
|
|
182
|
+
minHeightContentContainerStyle,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return { scrollableProps, headerMotionContext };
|
|
186
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnimatedHeaderBase,
|
|
3
|
+
HeaderBase,
|
|
4
|
+
HeaderMotionContextProvider,
|
|
5
|
+
HeaderMotionFlatList,
|
|
6
|
+
HeaderMotionHeader,
|
|
7
|
+
HeaderMotionScrollManager,
|
|
8
|
+
HeaderMotionScrollView,
|
|
9
|
+
type HeaderMotionFlatListProps,
|
|
10
|
+
type HeaderMotionHeaderProps,
|
|
11
|
+
type HeaderMotionProps,
|
|
12
|
+
type HeaderMotionScrollManagerProps,
|
|
13
|
+
type HeaderMotionScrollViewProps,
|
|
14
|
+
} from './components';
|
|
15
|
+
import type { ReactElement } from 'react';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compound component type for HeaderMotion.
|
|
19
|
+
* Provides the main context provider and sub-components for building collapsible headers.
|
|
20
|
+
*/
|
|
21
|
+
type HeaderMotionComponent = {
|
|
22
|
+
/** Main context provider component */
|
|
23
|
+
<T extends string>(props: HeaderMotionProps<T>): ReactElement;
|
|
24
|
+
/** Component for providing motion progress properties to animated headers.
|
|
25
|
+
* Use to pass props to the header components in React Navigation / Expo Router, which cannot access HeaderMotion's context and `useMotionProgress` otherwise.
|
|
26
|
+
*/
|
|
27
|
+
Header: typeof HeaderMotionHeader;
|
|
28
|
+
/** Component for custom scroll implementations */
|
|
29
|
+
ScrollManager: typeof HeaderMotionScrollManager;
|
|
30
|
+
/** Animated ScrollView component with header motion integration */
|
|
31
|
+
ScrollView: typeof HeaderMotionScrollView;
|
|
32
|
+
/** Animated FlatList component with header motion integration */
|
|
33
|
+
FlatList: typeof HeaderMotionFlatList;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Main HeaderMotion component.
|
|
38
|
+
* A compound component that provides context for collapsible header animations.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <HeaderMotion>
|
|
43
|
+
* <HeaderMotion.Header>
|
|
44
|
+
* {(headerProps) => (
|
|
45
|
+
* <Stack.Screen
|
|
46
|
+
* options={{
|
|
47
|
+
* header: () => (
|
|
48
|
+
* <MyAnimatedHeader {...headerProps} />
|
|
49
|
+
* ),
|
|
50
|
+
* }}
|
|
51
|
+
* />
|
|
52
|
+
* )}
|
|
53
|
+
* </HeaderMotion.Header>
|
|
54
|
+
* <HeaderMotion.ScrollView>
|
|
55
|
+
* <MyScrollableContent />
|
|
56
|
+
* </HeaderMotion.ScrollView>
|
|
57
|
+
* </HeaderMotion>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
const HeaderMotion = HeaderMotionContextProvider as HeaderMotionComponent;
|
|
61
|
+
HeaderMotion.Header = HeaderMotionHeader;
|
|
62
|
+
HeaderMotion.ScrollManager = HeaderMotionScrollManager;
|
|
63
|
+
HeaderMotion.ScrollView = HeaderMotionScrollView;
|
|
64
|
+
HeaderMotion.FlatList = HeaderMotionFlatList;
|
|
65
|
+
|
|
66
|
+
export default HeaderMotion;
|
|
67
|
+
export * from './hooks';
|
|
68
|
+
export type * from './types';
|
|
69
|
+
export { AnimatedHeaderBase, HeaderBase };
|
|
70
|
+
export type {
|
|
71
|
+
HeaderMotionFlatListProps,
|
|
72
|
+
HeaderMotionHeaderProps,
|
|
73
|
+
HeaderMotionProps,
|
|
74
|
+
HeaderMotionScrollManagerProps,
|
|
75
|
+
HeaderMotionScrollViewProps,
|
|
76
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LayoutChangeEvent, ScrollViewProps } from 'react-native';
|
|
2
|
+
import type { AnimatedRef, SharedValue } from 'react-native-reanimated';
|
|
3
|
+
import { DEFAULT_SCROLL_ID } from './utils/defaults';
|
|
4
|
+
|
|
5
|
+
export type Progress = SharedValue<number>;
|
|
6
|
+
|
|
7
|
+
export type ProgressThreshold =
|
|
8
|
+
| number
|
|
9
|
+
| ((measuredHeaderValue: number) => number);
|
|
10
|
+
export type MeasureAnimatedHeader = (e: LayoutChangeEvent) => number;
|
|
11
|
+
export type MeasureAnimatedHeaderAndSet = (e: LayoutChangeEvent) => void;
|
|
12
|
+
|
|
13
|
+
export type ActiveScrollIdValues<T extends string = string> = {
|
|
14
|
+
state: T;
|
|
15
|
+
sv: SharedValue<T>;
|
|
16
|
+
};
|
|
17
|
+
export type SetActiveScrollId<T extends string> = (newId: T) => void;
|
|
18
|
+
|
|
19
|
+
export interface ScrollValue {
|
|
20
|
+
min: number;
|
|
21
|
+
current: number;
|
|
22
|
+
}
|
|
23
|
+
export type ScrollValues = Record<string, ScrollValue> & {
|
|
24
|
+
[key in typeof DEFAULT_SCROLL_ID]?: ScrollValue;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type WithCollapsibleHeaderProps<
|
|
28
|
+
T extends Record<string, unknown> = Record<string, unknown>
|
|
29
|
+
> = T & MotionProgress;
|
|
30
|
+
|
|
31
|
+
export type WithCollapsiblePagedHeaderProps<
|
|
32
|
+
Tab extends string = string,
|
|
33
|
+
T extends Record<string, unknown> = Record<string, unknown>
|
|
34
|
+
> = WithCollapsibleHeaderProps<T> & {
|
|
35
|
+
onTabChange: (newTab: Tab) => void;
|
|
36
|
+
activeTab: Tab;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface MotionProgress {
|
|
40
|
+
progress: Progress;
|
|
41
|
+
progressThreshold: number;
|
|
42
|
+
measureTotalHeight: MeasureAnimatedHeaderAndSet;
|
|
43
|
+
measureDynamic: MeasureAnimatedHeaderAndSet;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ScrollManagerHeaderMotionContext {
|
|
47
|
+
originalHeaderHeight: number;
|
|
48
|
+
minHeightContentContainerStyle:
|
|
49
|
+
| {}
|
|
50
|
+
| {
|
|
51
|
+
minHeight: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ScrollManagerConfig {
|
|
56
|
+
scrollableProps: Required<
|
|
57
|
+
Pick<ScrollViewProps, 'onScroll' | 'scrollEventThrottle'>
|
|
58
|
+
> & {
|
|
59
|
+
ref: AnimatedRef<any>; // TODO: better typing
|
|
60
|
+
};
|
|
61
|
+
headerMotionContext: ScrollManagerHeaderMotionContext;
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MeasureAnimatedHeader, ProgressThreshold } from '../types';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PROGRESS_THRESHOLD: ProgressThreshold = (measuredHeaderValue) =>
|
|
4
|
+
measuredHeaderValue;
|
|
5
|
+
const DEFAULT_MEASURE_DYNAMIC: MeasureAnimatedHeader = (e) =>
|
|
6
|
+
e.nativeEvent.layout.height;
|
|
7
|
+
|
|
8
|
+
// Symbol doesn't work?
|
|
9
|
+
// const DEFAULT_SCROLL_ID = Symbol("HEADER_MOTION_DEFAULT_SCROLL_ID");
|
|
10
|
+
const DEFAULT_SCROLL_ID = '__HEADER_MOTION_DEFAULT_SCROLL_ID__';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
DEFAULT_MEASURE_DYNAMIC,
|
|
14
|
+
DEFAULT_PROGRESS_THRESHOLD,
|
|
15
|
+
DEFAULT_SCROLL_ID,
|
|
16
|
+
};
|