react-native-flatlist-collapsible-header 1.0.1
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 +119 -0
- package/lib/module/AnimatedHeaderFlatList.js +95 -0
- package/lib/module/AnimatedHeaderFlatList.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AnimatedHeaderFlatList.d.ts +30 -0
- package/lib/typescript/src/AnimatedHeaderFlatList.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +164 -0
- package/src/AnimatedHeaderFlatList.tsx +144 -0
- package/src/index.tsx +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MomenMustafa45
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# react-native-flatlist-collapsible-header
|
|
2
|
+
|
|
3
|
+
A reusable **React Native FlatList with a smooth collapsible animated header**, built using **react-native-reanimated**.
|
|
4
|
+
The header smoothly fades, translates, and optionally scales away as the user scrolls.
|
|
5
|
+
|
|
6
|
+
Ideal for feeds, profile screens, dashboards, and modern mobile UIs.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- ⚡ Smooth animations powered by **react-native-reanimated**
|
|
13
|
+
- 🎯 Fully typed with **TypeScript**
|
|
14
|
+
- 🧩 Drop-in replacement for `FlatList`
|
|
15
|
+
- 🎛 Configurable animation behavior
|
|
16
|
+
- 📱 Optimized for performance
|
|
17
|
+
- 🧠 Clean and simple API
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📸 Preview
|
|
22
|
+
|
|
23
|
+
Demo of the collapsible header behavior:
|
|
24
|
+
|
|
25
|
+
```md
|
|
26
|
+
https://github.com/your-username/react-native-flatlist-collapsible-header/assets/demo.mp4
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 📥 Installation
|
|
30
|
+
|
|
31
|
+
Using npm:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install react-native-flatlist-collapsible-header
|
|
35
|
+
or
|
|
36
|
+
yarn add react-native-flatlist-collapsible-header
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🔧 Peer Dependencies
|
|
40
|
+
|
|
41
|
+
This library depends on the following peer dependency:
|
|
42
|
+
|
|
43
|
+
- **react-native-reanimated** `v2+`
|
|
44
|
+
|
|
45
|
+
If you haven't installed or configured it yet, follow the official installation guide:
|
|
46
|
+
https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation
|
|
47
|
+
|
|
48
|
+
## 🚀 Usage
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { AnimatedHeaderFlatList } from 'react-native-flatlist-collapsible-header';
|
|
52
|
+
|
|
53
|
+
<AnimatedHeaderFlatList
|
|
54
|
+
data={data}
|
|
55
|
+
headerHeight={400}
|
|
56
|
+
renderHeader={<ListHeader />}
|
|
57
|
+
renderItem={({ item }) => <ListItem item={item} />}
|
|
58
|
+
keyExtractor={(item) => item.id}
|
|
59
|
+
/>;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 🧩 Props
|
|
63
|
+
|
|
64
|
+
### `AnimatedHeaderFlatList<T>`
|
|
65
|
+
|
|
66
|
+
| Prop | Type | Required | Default | Description |
|
|
67
|
+
| --------------------------- | ---------------------- | -------- | ------- | ----------------------------------------------------- |
|
|
68
|
+
| `data` | `T[]` | ✅ | — | Data array for the FlatList |
|
|
69
|
+
| `renderItem` | `ListRenderItem<T>` | ✅ | — | Function to render each item |
|
|
70
|
+
| `keyExtractor` | `(item: T) => string` | ✅ | — | Extracts unique keys |
|
|
71
|
+
| `headerHeight` | `number` | ✅ | — | Height of the header in pixels |
|
|
72
|
+
| `renderHeader` | `React.ReactNode` | ✅ | — | Header component to render |
|
|
73
|
+
| `hideThreshold` | `number` | ❌ | `0.5` | Percentage (0–1) of header height before fully hidden |
|
|
74
|
+
| `headerContainerStyle` | `ViewStyle` | ❌ | — | Style override for the header container |
|
|
75
|
+
| `contentContainerStyle` | `StyleProp<ViewStyle>` | ❌ | — | Styles for FlatList content |
|
|
76
|
+
| `scrollEventThrottle` | `number` | ❌ | `16` | Scroll event frequency |
|
|
77
|
+
| `animationConfig.fadeOut` | `boolean` | ❌ | `true` | Fade header while scrolling |
|
|
78
|
+
| `animationConfig.translate` | `boolean` | ❌ | `true` | Translate header vertically |
|
|
79
|
+
| `animationConfig.scale` | `boolean` | ❌ | `false` | Scale header during scroll |
|
|
80
|
+
| `onScroll` | `(event) => void` | ❌ | — | Optional scroll listener |
|
|
81
|
+
|
|
82
|
+
> ✅ All standard `FlatList` props are supported.
|
|
83
|
+
|
|
84
|
+
## 🤝 Contributing
|
|
85
|
+
|
|
86
|
+
Contributions are welcome!
|
|
87
|
+
|
|
88
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
89
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
90
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
91
|
+
|
|
92
|
+
## 🎛 Animation Configuration Example
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<AnimatedHeaderFlatList
|
|
96
|
+
headerHeight={300}
|
|
97
|
+
renderHeader={<Header />}
|
|
98
|
+
animationConfig={{
|
|
99
|
+
fadeOut: true,
|
|
100
|
+
translate: true,
|
|
101
|
+
scale: true,
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## ⚠️ Notes & Best Practices
|
|
107
|
+
|
|
108
|
+
- Requires **react-native-reanimated v2+**
|
|
109
|
+
- Header uses **absolute positioning**
|
|
110
|
+
- Avoid using margins for header layout
|
|
111
|
+
- Best performance with `scrollEventThrottle={16}`
|
|
112
|
+
|
|
113
|
+
## 📄 License
|
|
114
|
+
|
|
115
|
+
MIT © Momen Mustafa
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
Made with ❤️ using [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { StyleSheet, View } from 'react-native';
|
|
5
|
+
import { FlatList } from 'react-native';
|
|
6
|
+
import Animated, { useSharedValue, useAnimatedStyle, interpolate, Extrapolation } from 'react-native-reanimated';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
/**
|
|
9
|
+
* A FlatList component with an animated header that hides on scroll.
|
|
10
|
+
* @typeparam T - Type of items in the FlatList
|
|
11
|
+
*/
|
|
12
|
+
export function AnimatedHeaderFlatList({
|
|
13
|
+
headerHeight,
|
|
14
|
+
renderHeader,
|
|
15
|
+
onScroll,
|
|
16
|
+
contentContainerStyle,
|
|
17
|
+
headerContainerStyle,
|
|
18
|
+
hideThreshold = 0.5,
|
|
19
|
+
scrollEventThrottle = 16,
|
|
20
|
+
animationConfig = {
|
|
21
|
+
fadeOut: true,
|
|
22
|
+
translate: true,
|
|
23
|
+
scale: false
|
|
24
|
+
},
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
const scrollY = useSharedValue(0);
|
|
28
|
+
const calculatedHideThreshold = headerHeight * hideThreshold;
|
|
29
|
+
const scrollHandler = event => {
|
|
30
|
+
const offsetY = event.nativeEvent.contentOffset.y;
|
|
31
|
+
scrollY.value = offsetY;
|
|
32
|
+
if (onScroll) {
|
|
33
|
+
onScroll(event);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const headerContainerStyles = useAnimatedStyle(() => {
|
|
37
|
+
const visibility = interpolate(scrollY.value, [0, calculatedHideThreshold], [1, 0], Extrapolation.CLAMP);
|
|
38
|
+
let animatedStyle = {};
|
|
39
|
+
if (animationConfig.fadeOut) {
|
|
40
|
+
animatedStyle = {
|
|
41
|
+
...animatedStyle,
|
|
42
|
+
opacity: visibility
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let transforms = [];
|
|
46
|
+
if (animationConfig.translate) {
|
|
47
|
+
transforms.push({
|
|
48
|
+
translateY: interpolate(visibility, [1, 0], [0, -20])
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (animationConfig.scale) {
|
|
52
|
+
transforms.push({
|
|
53
|
+
scale: interpolate(visibility, [0, 1], [0.8, 1])
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (transforms.length > 0) {
|
|
57
|
+
animatedStyle = {
|
|
58
|
+
...animatedStyle,
|
|
59
|
+
transform: transforms
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return animatedStyle;
|
|
63
|
+
});
|
|
64
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
65
|
+
style: styles.listContainer,
|
|
66
|
+
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
67
|
+
style: [styles.headerViewContainer, {
|
|
68
|
+
height: headerHeight
|
|
69
|
+
}, headerContainerStyles, headerContainerStyle],
|
|
70
|
+
pointerEvents: "box-none",
|
|
71
|
+
children: renderHeader
|
|
72
|
+
}), /*#__PURE__*/_jsx(FlatList, {
|
|
73
|
+
...props,
|
|
74
|
+
onScroll: scrollHandler,
|
|
75
|
+
scrollEventThrottle: scrollEventThrottle,
|
|
76
|
+
contentContainerStyle: [{
|
|
77
|
+
paddingTop: headerHeight
|
|
78
|
+
}, contentContainerStyle]
|
|
79
|
+
})]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
listContainer: {
|
|
84
|
+
position: 'relative'
|
|
85
|
+
},
|
|
86
|
+
headerViewContainer: {
|
|
87
|
+
backgroundColor: 'transparent',
|
|
88
|
+
width: '100%',
|
|
89
|
+
flex: 1,
|
|
90
|
+
position: 'absolute',
|
|
91
|
+
top: 0,
|
|
92
|
+
left: 0
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=AnimatedHeaderFlatList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","StyleSheet","View","FlatList","Animated","useSharedValue","useAnimatedStyle","interpolate","Extrapolation","jsx","_jsx","jsxs","_jsxs","AnimatedHeaderFlatList","headerHeight","renderHeader","onScroll","contentContainerStyle","headerContainerStyle","hideThreshold","scrollEventThrottle","animationConfig","fadeOut","translate","scale","props","scrollY","calculatedHideThreshold","scrollHandler","event","offsetY","nativeEvent","contentOffset","y","value","headerContainerStyles","visibility","CLAMP","animatedStyle","opacity","transforms","push","translateY","length","transform","style","styles","listContainer","children","headerViewContainer","height","pointerEvents","paddingTop","create","position","backgroundColor","width","flex","top","left"],"sourceRoot":"../../src","sources":["AnimatedHeaderFlatList.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["AnimatedHeaderFlatList"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,sBAAsB,QAAQ,6BAA0B","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type FlatListProps, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
export type AnimatedHeaderFlatListProps<T> = FlatListProps<T> & {
|
|
4
|
+
/** Height of the header in pixels */
|
|
5
|
+
headerHeight: number;
|
|
6
|
+
/** Function that renders the header component */
|
|
7
|
+
renderHeader: React.ReactNode;
|
|
8
|
+
/** Additional styles for the header container */
|
|
9
|
+
headerContainerStyle?: ViewStyle;
|
|
10
|
+
/** Additional styles for the content container */
|
|
11
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
12
|
+
/** Threshold for hiding the header (0 to 1) relative to headerHeight */
|
|
13
|
+
hideThreshold?: number;
|
|
14
|
+
scrollEventThrottle?: number;
|
|
15
|
+
/** Animation configuration */
|
|
16
|
+
animationConfig?: {
|
|
17
|
+
/** Whether to fade out the header */
|
|
18
|
+
fadeOut?: boolean;
|
|
19
|
+
/** Whether to translate the header */
|
|
20
|
+
translate?: boolean;
|
|
21
|
+
/** Whether to scale the header */
|
|
22
|
+
scale?: boolean;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A FlatList component with an animated header that hides on scroll.
|
|
27
|
+
* @typeparam T - Type of items in the FlatList
|
|
28
|
+
*/
|
|
29
|
+
export declare function AnimatedHeaderFlatList<T>({ headerHeight, renderHeader, onScroll, contentContainerStyle, headerContainerStyle, hideThreshold, scrollEventThrottle, animationConfig, ...props }: AnimatedHeaderFlatListProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=AnimatedHeaderFlatList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnimatedHeaderFlatList.d.ts","sourceRoot":"","sources":["../../../src/AnimatedHeaderFlatList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,KAAK,aAAa,EAGlB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAStB,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IAC9D,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,SAAS,CAAC;IACjC,kDAAkD;IAClD,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,eAAe,CAAC,EAAE;QAChB,qCAAqC;QACrC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,sCAAsC;QACtC,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,kCAAkC;QAClC,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,EACxC,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,qBAAqB,EACrB,oBAAoB,EACpB,aAAmB,EACnB,mBAAwB,EACxB,eAIC,EACD,GAAG,KAAK,EACT,EAAE,2BAA2B,CAAC,CAAC,CAAC,2CAwEhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-flatlist-collapsible-header",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A React Native FlatList with collapsible header that smoothly disappears on scroll",
|
|
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-flatlist-collapsible-header-example",
|
|
36
|
+
"clean": "del-cli lib",
|
|
37
|
+
"prepare": "bob build",
|
|
38
|
+
"typecheck": "tsc",
|
|
39
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
40
|
+
"test": "jest",
|
|
41
|
+
"release": "release-it --only-version",
|
|
42
|
+
"build": "tsc"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"react-native",
|
|
46
|
+
"ios",
|
|
47
|
+
"android"
|
|
48
|
+
],
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/MomenMustafa45/react-native-flatlist-collapisable-header.git"
|
|
52
|
+
},
|
|
53
|
+
"author": "MomenMustafa45 <momenkara2@gmail.com> (https://github.com/MomenMustafa45)",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/MomenMustafa45/react-native-flatlist-collapisable-header/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/MomenMustafa45/react-native-flatlist-collapisable-header#readme",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
64
|
+
"@eslint/compat": "^1.3.2",
|
|
65
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
66
|
+
"@eslint/js": "^9.35.0",
|
|
67
|
+
"@react-native/babel-preset": "0.83.0",
|
|
68
|
+
"@react-native/eslint-config": "0.83.0",
|
|
69
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
70
|
+
"@types/jest": "^29.5.14",
|
|
71
|
+
"@types/react": "^19.1.12",
|
|
72
|
+
"commitlint": "^19.8.1",
|
|
73
|
+
"del-cli": "^6.0.0",
|
|
74
|
+
"eslint": "^9.35.0",
|
|
75
|
+
"eslint-config-prettier": "^10.1.8",
|
|
76
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
77
|
+
"jest": "^29.7.0",
|
|
78
|
+
"lefthook": "^2.0.3",
|
|
79
|
+
"prettier": "^2.8.8",
|
|
80
|
+
"react": "19.1.0",
|
|
81
|
+
"react-native": "0.81.5",
|
|
82
|
+
"react-native-builder-bob": "^0.40.17",
|
|
83
|
+
"react-native-reanimated": "^4.2.1",
|
|
84
|
+
"react-native-worklets": "0.5.1",
|
|
85
|
+
"release-it": "^19.0.4",
|
|
86
|
+
"typescript": "^5.9.3"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"react": "*",
|
|
90
|
+
"react-native": "*",
|
|
91
|
+
"react-native-reanimated": "^4.1.1"
|
|
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
|
+
"prettier": {
|
|
116
|
+
"quoteProps": "consistent",
|
|
117
|
+
"singleQuote": true,
|
|
118
|
+
"tabWidth": 2,
|
|
119
|
+
"trailingComma": "es5",
|
|
120
|
+
"useTabs": false
|
|
121
|
+
},
|
|
122
|
+
"jest": {
|
|
123
|
+
"preset": "react-native",
|
|
124
|
+
"modulePathIgnorePatterns": [
|
|
125
|
+
"<rootDir>/example/node_modules",
|
|
126
|
+
"<rootDir>/lib/"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"commitlint": {
|
|
130
|
+
"extends": [
|
|
131
|
+
"@commitlint/config-conventional"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"release-it": {
|
|
135
|
+
"git": {
|
|
136
|
+
"commitMessage": "chore: release ${version}",
|
|
137
|
+
"tagName": "v${version}"
|
|
138
|
+
},
|
|
139
|
+
"npm": {
|
|
140
|
+
"publish": true
|
|
141
|
+
},
|
|
142
|
+
"github": {
|
|
143
|
+
"release": true
|
|
144
|
+
},
|
|
145
|
+
"plugins": {
|
|
146
|
+
"@release-it/conventional-changelog": {
|
|
147
|
+
"preset": {
|
|
148
|
+
"name": "angular"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"create-react-native-library": {
|
|
154
|
+
"type": "library",
|
|
155
|
+
"languages": "js",
|
|
156
|
+
"tools": [
|
|
157
|
+
"eslint",
|
|
158
|
+
"jest",
|
|
159
|
+
"lefthook",
|
|
160
|
+
"release-it"
|
|
161
|
+
],
|
|
162
|
+
"version": "0.57.0"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
View,
|
|
5
|
+
type FlatListProps,
|
|
6
|
+
type NativeScrollEvent,
|
|
7
|
+
type NativeSyntheticEvent,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { FlatList } from 'react-native';
|
|
12
|
+
import Animated, {
|
|
13
|
+
useSharedValue,
|
|
14
|
+
useAnimatedStyle,
|
|
15
|
+
interpolate,
|
|
16
|
+
Extrapolation,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
|
|
19
|
+
export type AnimatedHeaderFlatListProps<T> = FlatListProps<T> & {
|
|
20
|
+
/** Height of the header in pixels */
|
|
21
|
+
headerHeight: number;
|
|
22
|
+
/** Function that renders the header component */
|
|
23
|
+
renderHeader: React.ReactNode;
|
|
24
|
+
/** Additional styles for the header container */
|
|
25
|
+
headerContainerStyle?: ViewStyle;
|
|
26
|
+
/** Additional styles for the content container */
|
|
27
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
28
|
+
/** Threshold for hiding the header (0 to 1) relative to headerHeight */
|
|
29
|
+
hideThreshold?: number;
|
|
30
|
+
scrollEventThrottle?: number;
|
|
31
|
+
/** Animation configuration */
|
|
32
|
+
animationConfig?: {
|
|
33
|
+
/** Whether to fade out the header */
|
|
34
|
+
fadeOut?: boolean;
|
|
35
|
+
/** Whether to translate the header */
|
|
36
|
+
translate?: boolean;
|
|
37
|
+
/** Whether to scale the header */
|
|
38
|
+
scale?: boolean;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A FlatList component with an animated header that hides on scroll.
|
|
44
|
+
* @typeparam T - Type of items in the FlatList
|
|
45
|
+
*/
|
|
46
|
+
export function AnimatedHeaderFlatList<T>({
|
|
47
|
+
headerHeight,
|
|
48
|
+
renderHeader,
|
|
49
|
+
onScroll,
|
|
50
|
+
contentContainerStyle,
|
|
51
|
+
headerContainerStyle,
|
|
52
|
+
hideThreshold = 0.5,
|
|
53
|
+
scrollEventThrottle = 16,
|
|
54
|
+
animationConfig = {
|
|
55
|
+
fadeOut: true,
|
|
56
|
+
translate: true,
|
|
57
|
+
scale: false,
|
|
58
|
+
},
|
|
59
|
+
...props
|
|
60
|
+
}: AnimatedHeaderFlatListProps<T>) {
|
|
61
|
+
const scrollY = useSharedValue(0);
|
|
62
|
+
|
|
63
|
+
const calculatedHideThreshold = headerHeight * hideThreshold;
|
|
64
|
+
|
|
65
|
+
const scrollHandler = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
66
|
+
const offsetY = event.nativeEvent.contentOffset.y;
|
|
67
|
+
scrollY.value = offsetY;
|
|
68
|
+
|
|
69
|
+
if (onScroll) {
|
|
70
|
+
onScroll(event);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const headerContainerStyles = useAnimatedStyle(() => {
|
|
75
|
+
const visibility = interpolate(
|
|
76
|
+
scrollY.value,
|
|
77
|
+
[0, calculatedHideThreshold],
|
|
78
|
+
[1, 0],
|
|
79
|
+
Extrapolation.CLAMP
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
let animatedStyle: ViewStyle = {};
|
|
83
|
+
|
|
84
|
+
if (animationConfig.fadeOut) {
|
|
85
|
+
animatedStyle = { ...animatedStyle, opacity: visibility };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let transforms: any[] = [];
|
|
89
|
+
|
|
90
|
+
if (animationConfig.translate) {
|
|
91
|
+
transforms.push({
|
|
92
|
+
translateY: interpolate(visibility, [1, 0], [0, -20]),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (animationConfig.scale) {
|
|
97
|
+
transforms.push({ scale: interpolate(visibility, [0, 1], [0.8, 1]) });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (transforms.length > 0) {
|
|
101
|
+
animatedStyle = { ...animatedStyle, transform: transforms };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return animatedStyle;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<View style={styles.listContainer}>
|
|
109
|
+
<Animated.View
|
|
110
|
+
style={[
|
|
111
|
+
styles.headerViewContainer,
|
|
112
|
+
{ height: headerHeight },
|
|
113
|
+
headerContainerStyles,
|
|
114
|
+
headerContainerStyle,
|
|
115
|
+
]}
|
|
116
|
+
pointerEvents="box-none"
|
|
117
|
+
>
|
|
118
|
+
{renderHeader}
|
|
119
|
+
</Animated.View>
|
|
120
|
+
|
|
121
|
+
<FlatList<T>
|
|
122
|
+
{...props}
|
|
123
|
+
onScroll={scrollHandler}
|
|
124
|
+
scrollEventThrottle={scrollEventThrottle}
|
|
125
|
+
contentContainerStyle={[
|
|
126
|
+
{ paddingTop: headerHeight },
|
|
127
|
+
contentContainerStyle,
|
|
128
|
+
]}
|
|
129
|
+
/>
|
|
130
|
+
</View>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const styles = StyleSheet.create({
|
|
135
|
+
listContainer: { position: 'relative' },
|
|
136
|
+
headerViewContainer: {
|
|
137
|
+
backgroundColor: 'transparent',
|
|
138
|
+
width: '100%',
|
|
139
|
+
flex: 1,
|
|
140
|
+
position: 'absolute',
|
|
141
|
+
top: 0,
|
|
142
|
+
left: 0,
|
|
143
|
+
},
|
|
144
|
+
});
|
package/src/index.tsx
ADDED