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.
Files changed (90) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +479 -0
  3. package/lib/module/components/FlatList.js +64 -0
  4. package/lib/module/components/FlatList.js.map +1 -0
  5. package/lib/module/components/Header.js +19 -0
  6. package/lib/module/components/Header.js.map +1 -0
  7. package/lib/module/components/HeaderBase.js +59 -0
  8. package/lib/module/components/HeaderBase.js.map +1 -0
  9. package/lib/module/components/HeaderMotion.js +84 -0
  10. package/lib/module/components/HeaderMotion.js.map +1 -0
  11. package/lib/module/components/ScrollManager.js +39 -0
  12. package/lib/module/components/ScrollManager.js.map +1 -0
  13. package/lib/module/components/ScrollView.js +47 -0
  14. package/lib/module/components/ScrollView.js.map +1 -0
  15. package/lib/module/components/index.js +9 -0
  16. package/lib/module/components/index.js.map +1 -0
  17. package/lib/module/context.js +5 -0
  18. package/lib/module/context.js.map +1 -0
  19. package/lib/module/hooks/index.js +6 -0
  20. package/lib/module/hooks/index.js.map +1 -0
  21. package/lib/module/hooks/useActiveScrollId.js +47 -0
  22. package/lib/module/hooks/useActiveScrollId.js.map +1 -0
  23. package/lib/module/hooks/useMotionProgress.js +58 -0
  24. package/lib/module/hooks/useMotionProgress.js.map +1 -0
  25. package/lib/module/hooks/useScrollManager.js +150 -0
  26. package/lib/module/hooks/useScrollManager.js.map +1 -0
  27. package/lib/module/index.js +42 -0
  28. package/lib/module/index.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/types.js +4 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/module/utils/defaults.js +10 -0
  33. package/lib/module/utils/defaults.js.map +1 -0
  34. package/lib/module/utils/index.js +5 -0
  35. package/lib/module/utils/index.js.map +1 -0
  36. package/lib/module/utils/values.js +11 -0
  37. package/lib/module/utils/values.js.map +1 -0
  38. package/lib/typescript/package.json +1 -0
  39. package/lib/typescript/src/components/FlatList.d.ts +30 -0
  40. package/lib/typescript/src/components/FlatList.d.ts.map +1 -0
  41. package/lib/typescript/src/components/Header.d.ts +19 -0
  42. package/lib/typescript/src/components/Header.d.ts.map +1 -0
  43. package/lib/typescript/src/components/HeaderBase.d.ts +34 -0
  44. package/lib/typescript/src/components/HeaderBase.d.ts.map +1 -0
  45. package/lib/typescript/src/components/HeaderMotion.d.ts +52 -0
  46. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -0
  47. package/lib/typescript/src/components/ScrollManager.d.ts +40 -0
  48. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -0
  49. package/lib/typescript/src/components/ScrollView.d.ts +24 -0
  50. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -0
  51. package/lib/typescript/src/components/index.d.ts +7 -0
  52. package/lib/typescript/src/components/index.d.ts.map +1 -0
  53. package/lib/typescript/src/context.d.ts +14 -0
  54. package/lib/typescript/src/context.d.ts.map +1 -0
  55. package/lib/typescript/src/hooks/index.d.ts +4 -0
  56. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  57. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +32 -0
  58. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -0
  59. package/lib/typescript/src/hooks/useMotionProgress.d.ts +38 -0
  60. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -0
  61. package/lib/typescript/src/hooks/useScrollManager.d.ts +37 -0
  62. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -0
  63. package/lib/typescript/src/index.d.ts +51 -0
  64. package/lib/typescript/src/index.d.ts.map +1 -0
  65. package/lib/typescript/src/types.d.ts +43 -0
  66. package/lib/typescript/src/types.d.ts.map +1 -0
  67. package/lib/typescript/src/utils/defaults.d.ts +6 -0
  68. package/lib/typescript/src/utils/defaults.d.ts.map +1 -0
  69. package/lib/typescript/src/utils/index.d.ts +3 -0
  70. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  71. package/lib/typescript/src/utils/values.d.ts +3 -0
  72. package/lib/typescript/src/utils/values.d.ts.map +1 -0
  73. package/package.json +164 -0
  74. package/src/components/FlatList.tsx +72 -0
  75. package/src/components/Header.tsx +30 -0
  76. package/src/components/HeaderBase.tsx +51 -0
  77. package/src/components/HeaderMotion.tsx +183 -0
  78. package/src/components/ScrollManager.tsx +58 -0
  79. package/src/components/ScrollView.tsx +58 -0
  80. package/src/components/index.ts +6 -0
  81. package/src/context.ts +20 -0
  82. package/src/hooks/index.ts +3 -0
  83. package/src/hooks/useActiveScrollId.ts +59 -0
  84. package/src/hooks/useMotionProgress.ts +56 -0
  85. package/src/hooks/useScrollManager.ts +186 -0
  86. package/src/index.ts +76 -0
  87. package/src/types.ts +62 -0
  88. package/src/utils/defaults.ts +16 -0
  89. package/src/utils/index.ts +2 -0
  90. package/src/utils/values.ts +6 -0
package/package.json ADDED
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "react-native-header-motion",
3
+ "version": "0.1.0",
4
+ "description": "Smooth, animated collapsible headers with scroll-based motion control in React Native",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace react-native-header-motion-example",
36
+ "clean": "del-cli lib",
37
+ "prepare": "bob build",
38
+ "typecheck": "tsc",
39
+ "test": "jest",
40
+ "release": "release-it --only-version",
41
+ "lint": "eslint \"**/*.{js,ts,tsx}\""
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/pawicao/react-native-header-motion.git"
51
+ },
52
+ "author": "Oskar Pawica <pawicao@gmail.com> (https://github.com/pawicao)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/pawicao/react-native-header-motion/issues"
56
+ },
57
+ "homepage": "https://github.com/pawicao/react-native-header-motion#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/config-conventional": "^19.8.1",
63
+ "@eslint/compat": "^1.3.2",
64
+ "@eslint/eslintrc": "^3.3.1",
65
+ "@eslint/js": "^9.35.0",
66
+ "@react-native/babel-preset": "0.83.0",
67
+ "@react-native/eslint-config": "0.83.0",
68
+ "@release-it/conventional-changelog": "^10.0.1",
69
+ "@types/jest": "^29.5.14",
70
+ "@types/react": "^19.1.12",
71
+ "commitlint": "^19.8.1",
72
+ "del-cli": "^6.0.0",
73
+ "eslint": "^9.35.0",
74
+ "eslint-config-prettier": "^10.1.8",
75
+ "eslint-plugin-prettier": "^5.5.4",
76
+ "jest": "^29.7.0",
77
+ "lefthook": "^2.0.3",
78
+ "prettier": "^2.8.8",
79
+ "react": "19.1.0",
80
+ "react-native": "0.81.5",
81
+ "react-native-builder-bob": "^0.40.17",
82
+ "react-native-reanimated": "4.1.1",
83
+ "react-native-worklets": "0.5.1",
84
+ "release-it": "^19.0.4",
85
+ "typescript": "^5.9.2"
86
+ },
87
+ "peerDependencies": {
88
+ "react": "*",
89
+ "react-native": "*",
90
+ "react-native-reanimated": ">=4.0.0",
91
+ "react-native-worklets": ">=0.4.0"
92
+ },
93
+ "workspaces": [
94
+ "example"
95
+ ],
96
+ "packageManager": "yarn@4.11.0",
97
+ "react-native-builder-bob": {
98
+ "source": "src",
99
+ "output": "lib",
100
+ "targets": [
101
+ [
102
+ "module",
103
+ {
104
+ "esm": true
105
+ }
106
+ ],
107
+ [
108
+ "typescript",
109
+ {
110
+ "project": "tsconfig.build.json"
111
+ }
112
+ ]
113
+ ]
114
+ },
115
+ "jest": {
116
+ "preset": "react-native",
117
+ "modulePathIgnorePatterns": [
118
+ "<rootDir>/example/node_modules",
119
+ "<rootDir>/lib/"
120
+ ]
121
+ },
122
+ "commitlint": {
123
+ "extends": [
124
+ "@commitlint/config-conventional"
125
+ ]
126
+ },
127
+ "release-it": {
128
+ "git": {
129
+ "commitMessage": "chore: release ${version}",
130
+ "tagName": "v${version}"
131
+ },
132
+ "npm": {
133
+ "publish": true
134
+ },
135
+ "github": {
136
+ "release": true
137
+ },
138
+ "plugins": {
139
+ "@release-it/conventional-changelog": {
140
+ "preset": {
141
+ "name": "angular"
142
+ }
143
+ }
144
+ }
145
+ },
146
+ "prettier": {
147
+ "quoteProps": "consistent",
148
+ "singleQuote": true,
149
+ "tabWidth": 2,
150
+ "trailingComma": "es5",
151
+ "useTabs": false
152
+ },
153
+ "create-react-native-library": {
154
+ "type": "library",
155
+ "languages": "js",
156
+ "tools": [
157
+ "jest",
158
+ "lefthook",
159
+ "release-it",
160
+ "eslint"
161
+ ],
162
+ "version": "0.56.0"
163
+ }
164
+ }
@@ -0,0 +1,72 @@
1
+ import { forwardRef, type ComponentProps, type ComponentRef } from 'react';
2
+ import { ScrollView, type ScrollViewProps } from 'react-native';
3
+ import Animated from 'react-native-reanimated';
4
+ import { HeaderMotionScrollManager } from './ScrollManager';
5
+
6
+ type AnimatedFlatListProps<T = any> = ComponentProps<
7
+ typeof Animated.FlatList<T>
8
+ >;
9
+
10
+ export type HeaderMotionFlatListProps<T = any> = AnimatedFlatListProps<T> & {
11
+ /**
12
+ * Optional unique identifier for this scroll view.
13
+ * Use this when you have multiple scroll views (e.g. in tabs) to track them separately.
14
+ */
15
+ scrollId?: string;
16
+ };
17
+
18
+ /**
19
+ * Animated FlatList component that integrates with HeaderMotion.
20
+ * Automatically handles scroll tracking and header animation synchronization.
21
+ * Must be used within a HeaderMotion component.
22
+ *
23
+ * @template T - The type of items in the FlatList
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <HeaderMotion>
28
+ * <HeaderMotion.FlatList
29
+ * data={items}
30
+ * renderItem={({ item }) => <Text>{item}</Text>}
31
+ * />
32
+ * </HeaderMotion>
33
+ * ```
34
+ */
35
+ export function HeaderMotionFlatList<T = any>({
36
+ scrollId,
37
+ ...props
38
+ }: HeaderMotionFlatListProps<T>) {
39
+ return (
40
+ <HeaderMotionScrollManager scrollId={scrollId}>
41
+ {(
42
+ { onScroll, ...scrollViewProps },
43
+ { originalHeaderHeight, minHeightContentContainerStyle }
44
+ ) => (
45
+ <Animated.FlatList
46
+ {...scrollViewProps}
47
+ {...props}
48
+ onScroll={onScroll}
49
+ renderScrollComponent={(propsz) => (
50
+ <AnimatedScrollContainer {...propsz} />
51
+ )}
52
+ contentContainerStyle={[
53
+ minHeightContentContainerStyle,
54
+ { paddingTop: originalHeaderHeight },
55
+ props.contentContainerStyle,
56
+ ]}
57
+ />
58
+ )}
59
+ </HeaderMotionScrollManager>
60
+ );
61
+ }
62
+
63
+ const AnimatedScrollContainer = forwardRef<
64
+ ComponentRef<typeof ScrollView>,
65
+ ScrollViewProps
66
+ >(({ children, contentContainerStyle, ...rest }, ref) => {
67
+ return (
68
+ <ScrollView {...rest} ref={ref}>
69
+ <Animated.View style={contentContainerStyle}>{children}</Animated.View>
70
+ </ScrollView>
71
+ );
72
+ });
@@ -0,0 +1,30 @@
1
+ import { useMotionProgress } from '../hooks/useMotionProgress';
2
+ import type { MotionProgress } from '../types';
3
+ import type { ReactNode } from 'react';
4
+
5
+ type HeaderRenderChildren = (props: MotionProgress) => ReactNode;
6
+
7
+ export interface HeaderMotionHeaderProps {
8
+ /**
9
+ * Render function that receives motion progress props.
10
+ * Use this to animate your header based on scroll progress and to provide measurement functions to the elements of the header.
11
+ */
12
+ children: HeaderRenderChildren;
13
+ }
14
+
15
+ /**
16
+ * Header component for providing motion progress properties to animated headers.
17
+ * Must be used within a HeaderMotion component.
18
+ *
19
+ * Use to pass props to the header components in React Navigation / Expo Router, which cannot access HeaderMotion's context and `useMotionProgress` otherwise.`
20
+ */
21
+ export function HeaderMotionHeader({ children }: HeaderMotionHeaderProps) {
22
+ if (typeof children !== 'function') {
23
+ throw new Error(
24
+ 'HeaderMotion.Header only accepts render function as the only child.'
25
+ );
26
+ }
27
+
28
+ const motionProgressProps = useMotionProgress();
29
+ return children(motionProgressProps);
30
+ }
@@ -0,0 +1,51 @@
1
+ import { StyleSheet, View, type ViewProps } from 'react-native';
2
+ import Animated, { type AnimatedProps } from 'react-native-reanimated';
3
+
4
+ export type HeaderBaseProps = ViewProps;
5
+ export type AnimatedHeaderBaseProps = AnimatedProps<ViewProps>;
6
+
7
+ /**
8
+ * Base header component with absolute positioning.
9
+ * Provides a foundation for building headers that need to be positioned absolutely.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <HeaderBase
14
+ * onLayout={measureTotalHeight}
15
+ * >
16
+ * ...
17
+ * </HeaderBase>
18
+ * ```
19
+ */
20
+ export function HeaderBase({ style, ...rest }: HeaderBaseProps) {
21
+ return <View style={[style, styles.container]} {...rest} />;
22
+ }
23
+
24
+ /**
25
+ * Animated version of HeaderBase using Reanimated's Animated.View.
26
+ * Use this when you need to animate the header based on scroll progress.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * <AnimatedHeaderBase
31
+ * onLayout={measureTotalHeight}
32
+ * style={[{ paddingTop: insets.top }, animatedStyle]}
33
+ * >
34
+ * ...
35
+ * </AnimatedHeaderBase>
36
+ * ```
37
+ */
38
+ export function AnimatedHeaderBase({
39
+ style,
40
+ ...rest
41
+ }: AnimatedHeaderBaseProps) {
42
+ return <Animated.View style={[style, styles.container]} {...rest} />;
43
+ }
44
+
45
+ const styles = StyleSheet.create({
46
+ container: {
47
+ position: 'absolute',
48
+ left: 0,
49
+ right: 0,
50
+ },
51
+ });
@@ -0,0 +1,183 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ Extrapolation,
4
+ interpolate,
5
+ useAnimatedReaction,
6
+ useDerivedValue,
7
+ useSharedValue,
8
+ type ExtrapolationType,
9
+ type SharedValue,
10
+ } from 'react-native-reanimated';
11
+ import { HeaderMotionContext } from '../context';
12
+ import type { ReactNode } from 'react';
13
+ import type {
14
+ MeasureAnimatedHeader,
15
+ MeasureAnimatedHeaderAndSet,
16
+ ProgressThreshold,
17
+ ScrollValues,
18
+ } from '../types';
19
+ import {
20
+ DEFAULT_MEASURE_DYNAMIC,
21
+ DEFAULT_PROGRESS_THRESHOLD,
22
+ DEFAULT_SCROLL_ID,
23
+ getInitialScrollValue,
24
+ } from '../utils';
25
+
26
+ export interface HeaderMotionProps<T extends string> {
27
+ /**
28
+ * The threshold at which the header animation completes (reaches progress = 1).
29
+ * Can be a fixed number or a function that calculates based on the result of {@link measureDynamic}.
30
+ *
31
+ * Defaults to a function that returns the return value of `measureDynamic` unchanged.
32
+ */
33
+ progressThreshold?: ProgressThreshold;
34
+ /**
35
+ * Function to measure a dimension of choice of the animated element of the header.
36
+ *
37
+ * Receives the layout change event from React Native.
38
+ *
39
+ * This function can be further accessed when rendering headers from `HeaderMotion.Header` or `useMotionProgress` - should be passed to the `onLayout` prop of such. If used, can be used for dynamic calculation of the {@link progressThreshold}.
40
+ *
41
+ * Defaults to measuring the height from the event.
42
+ */
43
+ measureDynamic?: MeasureAnimatedHeader;
44
+ /**
45
+ * Mode for measuring dynamic header height.
46
+ * - 'mount': Only measure once on mount
47
+ * - 'update': Update measurement on every layout recalculation of the component that {@link measureDynamic} was provided to as the `onLayout` property
48
+ * @default 'mount'
49
+ */
50
+ measureDynamicMode?: 'update' | 'mount';
51
+ /**
52
+ * Shared value for tracking the active scroll ID in multi-scroll scenarios (e.g. tabs).
53
+ * When provided, the header animation will sync across multiple scroll views.
54
+ */
55
+ activeScrollId?: SharedValue<T>;
56
+ /**
57
+ * Extrapolation type for the progress animation.
58
+ * Controls how the progress value behaves outside the threshold range.
59
+ *
60
+ * You may want to modify it to achieve some animations for the overscroll scenarios.
61
+ * @default Extrapolation.CLAMP
62
+ */
63
+ progressExtrapolation?: ExtrapolationType;
64
+ /** Child components that will have access to the header motion context */
65
+ children: ReactNode;
66
+ }
67
+
68
+ /**
69
+ * Context provider component for HeaderMotion.
70
+ * Manages header animation state and provides it to child components via context.
71
+ * @template T - The type of scroll ID string
72
+ */
73
+ function HeaderMotionContextProvider<T extends string>({
74
+ progressThreshold = DEFAULT_PROGRESS_THRESHOLD,
75
+ measureDynamic = DEFAULT_MEASURE_DYNAMIC,
76
+ measureDynamicMode = 'mount',
77
+ activeScrollId,
78
+ progressExtrapolation = Extrapolation.CLAMP,
79
+ children,
80
+ }: HeaderMotionProps<T>) {
81
+ const [dynamicMeasurement, setDynamicMeasurement] = useState<
82
+ number | undefined
83
+ >(undefined);
84
+ const [originalHeaderHeight, setOriginalHeaderHeight] = useState(0);
85
+
86
+ const setOrUpdateDynamicMeasurement =
87
+ useCallback<MeasureAnimatedHeaderAndSet>(
88
+ (e) => {
89
+ const measured = measureDynamic(e);
90
+ setDynamicMeasurement((prevMeasurement) => {
91
+ if (prevMeasurement !== undefined && measureDynamicMode === 'mount') {
92
+ return prevMeasurement;
93
+ }
94
+
95
+ return measured;
96
+ });
97
+ },
98
+ [measureDynamicMode, measureDynamic, setDynamicMeasurement]
99
+ );
100
+
101
+ const calculatedProgressThreshold = useMemo(() => {
102
+ if (typeof progressThreshold === 'number') {
103
+ return progressThreshold;
104
+ }
105
+
106
+ if (dynamicMeasurement === undefined) {
107
+ return Infinity;
108
+ }
109
+ return progressThreshold(dynamicMeasurement);
110
+ }, [dynamicMeasurement, progressThreshold]);
111
+
112
+ const measureTotalHeight = useCallback<MeasureAnimatedHeaderAndSet>(
113
+ (e) => {
114
+ const measuredValue = e.nativeEvent.layout.height;
115
+ setOriginalHeaderHeight(measuredValue);
116
+ },
117
+ [setOriginalHeaderHeight]
118
+ );
119
+
120
+ const scrollValues = useSharedValue<ScrollValues>({
121
+ [DEFAULT_SCROLL_ID]: getInitialScrollValue(),
122
+ });
123
+
124
+ useAnimatedReaction(
125
+ () => activeScrollId?.get(),
126
+ (id) => {
127
+ if (!id || scrollValues.get()[id]) {
128
+ return;
129
+ }
130
+
131
+ scrollValues.modify((value) => {
132
+ (value as ScrollValues)[id] = getInitialScrollValue();
133
+ return value;
134
+ });
135
+ }
136
+ );
137
+
138
+ const progress = useDerivedValue(() => {
139
+ const id = activeScrollId?.get() ?? DEFAULT_SCROLL_ID;
140
+ const scrollValue = scrollValues.get()[id];
141
+
142
+ if (!scrollValue) {
143
+ return 0;
144
+ }
145
+
146
+ const { min, current } = scrollValue;
147
+ return interpolate(
148
+ current,
149
+ [min, min + calculatedProgressThreshold],
150
+ [0, 1],
151
+ progressExtrapolation
152
+ );
153
+ });
154
+
155
+ const ctxValue = useMemo(
156
+ () => ({
157
+ progress,
158
+ originalHeaderHeight,
159
+ measureDynamic: setOrUpdateDynamicMeasurement,
160
+ measureTotalHeight,
161
+ progressThreshold: calculatedProgressThreshold,
162
+ scrollValues,
163
+ activeScrollId: activeScrollId as SharedValue<string> | undefined,
164
+ }),
165
+ [
166
+ originalHeaderHeight,
167
+ progress,
168
+ measureTotalHeight,
169
+ setOrUpdateDynamicMeasurement,
170
+ scrollValues,
171
+ activeScrollId,
172
+ calculatedProgressThreshold,
173
+ ]
174
+ );
175
+
176
+ return (
177
+ <HeaderMotionContext.Provider value={ctxValue}>
178
+ {children}
179
+ </HeaderMotionContext.Provider>
180
+ );
181
+ }
182
+
183
+ export { HeaderMotionContextProvider };
@@ -0,0 +1,58 @@
1
+ import { useScrollManager } from '../hooks';
2
+ import type { ScrollManagerConfig } from '../types';
3
+ import type { ReactNode } from 'react';
4
+
5
+ type ScrollManagerRenderChildren = (
6
+ scrollableProps: ScrollManagerConfig['scrollableProps'],
7
+ options: ScrollManagerConfig['headerMotionContext']
8
+ ) => ReactNode;
9
+
10
+ export interface HeaderMotionScrollManagerProps {
11
+ /**
12
+ * Optional unique identifier for this scroll view.
13
+ * Use this when you have multiple scroll views (e.g., in tabs) to track them separately.
14
+ */
15
+ scrollId?: string;
16
+ /**
17
+ * Render function that receives scroll props and header context.
18
+ * Use this to create custom scroll implementations that integrate with HeaderMotion.
19
+ */
20
+ children: ScrollManagerRenderChildren;
21
+ }
22
+
23
+ /**
24
+ * ScrollManager component that provides scroll tracking functionality for custom scroll implementations. Uses {@link useScrollManager} under the hood.
25
+ * Must be used within a HeaderMotion component.
26
+ *
27
+ * This is useful when you need to use a scroll component that isn't directly supported
28
+ * (like a custom scroll view or third-party list components).
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * <HeaderMotion>
33
+ * <HeaderMotion.ScrollManager>
34
+ * {(scrollableProps, { originalHeaderHeight }) => (
35
+ * <CustomScrollView {...scrollableProps}>
36
+ * <View style={{ paddingTop: originalHeaderHeight }}>
37
+ * <Text>Content</Text>
38
+ * </View>
39
+ * </CustomScrollView>
40
+ * )}
41
+ * </HeaderMotion.ScrollManager>
42
+ * </HeaderMotion>
43
+ * ```
44
+ */
45
+ export function HeaderMotionScrollManager({
46
+ children,
47
+ scrollId,
48
+ }: HeaderMotionScrollManagerProps) {
49
+ if (typeof children !== 'function') {
50
+ throw new Error(
51
+ 'HeaderMotion.ScrollManager only accepts render function as the only child.'
52
+ );
53
+ }
54
+
55
+ const { scrollableProps, headerMotionContext } = useScrollManager(scrollId);
56
+
57
+ return children(scrollableProps, headerMotionContext);
58
+ }
@@ -0,0 +1,58 @@
1
+ import Animated, {
2
+ type AnimatedScrollViewProps,
3
+ } from 'react-native-reanimated';
4
+ import { HeaderMotionScrollManager } from './ScrollManager';
5
+
6
+ export type HeaderMotionScrollViewProps = AnimatedScrollViewProps & {
7
+ /**
8
+ * Optional unique identifier for this scroll view.
9
+ * Use this when you have multiple scroll views (e.g. in tabs) to track them separately.
10
+ */
11
+ scrollId?: string;
12
+ };
13
+
14
+ /**
15
+ * Animated ScrollView component that integrates with HeaderMotion.
16
+ * Automatically handles scroll tracking and header animation synchronization.
17
+ * Must be used within a HeaderMotion component.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <HeaderMotion>
22
+ * <HeaderMotion.ScrollView>
23
+ * <MyScrollableContent />
24
+ * </HeaderMotion.ScrollView>
25
+ * </HeaderMotion>
26
+ * ```
27
+ */
28
+ export function HeaderMotionScrollView({
29
+ scrollId,
30
+ children,
31
+ contentContainerStyle,
32
+ ...props
33
+ }: HeaderMotionScrollViewProps) {
34
+ return (
35
+ <HeaderMotionScrollManager scrollId={scrollId}>
36
+ {(
37
+ { onScroll, ...scrollViewProps },
38
+ { originalHeaderHeight, minHeightContentContainerStyle }
39
+ ) => (
40
+ <Animated.ScrollView
41
+ {...scrollViewProps}
42
+ {...props}
43
+ onScroll={onScroll}
44
+ >
45
+ <Animated.View
46
+ style={[
47
+ minHeightContentContainerStyle,
48
+ { paddingTop: originalHeaderHeight },
49
+ contentContainerStyle,
50
+ ]}
51
+ >
52
+ {children}
53
+ </Animated.View>
54
+ </Animated.ScrollView>
55
+ )}
56
+ </HeaderMotionScrollManager>
57
+ );
58
+ }
@@ -0,0 +1,6 @@
1
+ export * from './FlatList';
2
+ export * from './Header';
3
+ export * from './HeaderBase';
4
+ export * from './HeaderMotion';
5
+ export * from './ScrollManager';
6
+ export * from './ScrollView';
package/src/context.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { createContext } from 'react';
2
+ import { type SharedValue } from 'react-native-reanimated';
3
+ import type {
4
+ MeasureAnimatedHeaderAndSet,
5
+ Progress,
6
+ ScrollValues,
7
+ } from './types';
8
+
9
+ interface HeaderMotionContextType {
10
+ progress: Progress;
11
+ measureTotalHeight: MeasureAnimatedHeaderAndSet;
12
+ measureDynamic: MeasureAnimatedHeaderAndSet;
13
+ scrollValues: SharedValue<ScrollValues>;
14
+ activeScrollId: SharedValue<string> | undefined;
15
+ progressThreshold: number;
16
+ originalHeaderHeight: number;
17
+ }
18
+
19
+ export const HeaderMotionContext =
20
+ createContext<HeaderMotionContextType | null>(null);
@@ -0,0 +1,3 @@
1
+ export * from './useActiveScrollId';
2
+ export * from './useMotionProgress';
3
+ export * from './useScrollManager';