@orionarm/react-native-collapse-tabs 1.0.2 → 1.0.3
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/README.md +245 -1
- package/lib/CollapseTabs.d.ts.map +1 -1
- package/lib/CollapseTabs.js +14 -5
- package/lib/lib/CollapseTabs.d.ts +4 -0
- package/lib/lib/CollapseTabs.d.ts.map +1 -0
- package/lib/lib/CollapseTabs.js +156 -0
- package/lib/lib/CollapseTabs.js.map +1 -0
- package/lib/lib/FlatList.d.ts +8 -0
- package/lib/lib/FlatList.d.ts.map +1 -0
- package/lib/lib/FlatList.js +59 -0
- package/lib/lib/ScrollView.d.ts +9 -0
- package/lib/lib/ScrollView.d.ts.map +1 -0
- package/lib/lib/ScrollView.js +61 -0
- package/lib/lib/Tab.d.ts +8 -0
- package/lib/lib/Tab.d.ts.map +1 -0
- package/lib/lib/Tab.js +19 -0
- package/lib/lib/TabBar.d.ts +4 -0
- package/lib/lib/TabBar.d.ts.map +1 -0
- package/lib/lib/TabBar.js +89 -0
- package/lib/lib/context/index.d.ts +5 -0
- package/lib/lib/context/index.d.ts.map +1 -0
- package/lib/lib/context/index.js +8 -0
- package/lib/lib/hooks/index.d.ts +3 -0
- package/lib/lib/hooks/index.d.ts.map +1 -0
- package/lib/lib/hooks/index.js +11 -0
- package/lib/lib/index.d.ts +8 -0
- package/lib/lib/index.d.ts.map +1 -0
- package/lib/lib/index.js +15 -0
- package/lib/lib/index.js.map +1 -0
- package/lib/lib/types/types.d.ts +57 -0
- package/lib/lib/types/types.d.ts.map +1 -0
- package/lib/lib/types/types.js +2 -0
- package/package.json +1 -1
- package/src/CollapseTabs.tsx +13 -4
package/README.md
CHANGED
|
@@ -1,3 +1,247 @@
|
|
|
1
1
|
# @orionarm/react-native-collapse-tabs
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A lightweight, performant collapsible-tabs component for React Native, built on top of [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) and [`react-native-reanimated`](https://github.com/software-mansion/react-native-reanimated) v3.
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Work in Progress** — APIs may still change before `1.x` is considered stable.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 📜 **Collapsible header** — the header smoothly collapses while the inner list is scrolled.
|
|
12
|
+
- 🗂 **Swipeable tabs** — horizontal paging powered by `react-native-pager-view`.
|
|
13
|
+
- 🎚 **Per-tab scroll state** — each tab keeps its own scroll position.
|
|
14
|
+
- 🪄 **Animated tab switching** — header smoothly tweens between tabs (no sudden jumps).
|
|
15
|
+
- 🎨 **Custom header & tab bar** — bring your own UI, or use the built-in `DefaultTabBar`.
|
|
16
|
+
- ⚡ **Reanimated 3 worklets** — animations run on the UI thread for 60fps.
|
|
17
|
+
- 🧩 **Drop-in scrollables** — wrapped `FlatList` / `ScrollView` handle the plumbing for you.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
| Peer dependency | Version |
|
|
24
|
+
| ----------------------------- | ---------- |
|
|
25
|
+
| `react` | `>=16.8.0` |
|
|
26
|
+
| `react-native` | `>=0.60.0` |
|
|
27
|
+
| `react-native-gesture-handler`| `>=2.0.0` |
|
|
28
|
+
| `react-native-pager-view` | `>=6.0.0` |
|
|
29
|
+
| `react-native-reanimated` | `>=3.0.0` |
|
|
30
|
+
|
|
31
|
+
Make sure Reanimated is set up correctly (Babel plugin + `react-native-reanimated/plugin` at the **end** of your `babel.config.js` plugins array).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# npm
|
|
39
|
+
npm install @orionarm/react-native-collapse-tabs
|
|
40
|
+
|
|
41
|
+
# yarn
|
|
42
|
+
yarn add @orionarm/react-native-collapse-tabs
|
|
43
|
+
|
|
44
|
+
# pnpm
|
|
45
|
+
pnpm add @orionarm/react-native-collapse-tabs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then install the peer dependencies if you haven't already:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install react-native-pager-view react-native-reanimated react-native-gesture-handler
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For iOS, don't forget to install pods:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd ios && pod install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import React from "react";
|
|
66
|
+
import { Text, View, StyleSheet } from "react-native";
|
|
67
|
+
import {
|
|
68
|
+
CollapseTabs,
|
|
69
|
+
Tab,
|
|
70
|
+
FlatList,
|
|
71
|
+
} from "@orionarm/react-native-collapse-tabs";
|
|
72
|
+
|
|
73
|
+
const HEADER_HEIGHT = 200;
|
|
74
|
+
const TAB_BAR_HEIGHT = 44;
|
|
75
|
+
|
|
76
|
+
export default function Example() {
|
|
77
|
+
return (
|
|
78
|
+
<CollapseTabs
|
|
79
|
+
headerHeight={HEADER_HEIGHT}
|
|
80
|
+
tabBarHeight={TAB_BAR_HEIGHT}
|
|
81
|
+
renderHeader={() => (
|
|
82
|
+
<View style={styles.header}>
|
|
83
|
+
<Text style={styles.headerText}>My Profile</Text>
|
|
84
|
+
</View>
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
<Tab name="Posts">
|
|
88
|
+
<FlatList
|
|
89
|
+
name="Posts"
|
|
90
|
+
data={Array.from({ length: 30 }).map((_, i) => `Post ${i}`)}
|
|
91
|
+
keyExtractor={(item) => item}
|
|
92
|
+
renderItem={({ item }) => (
|
|
93
|
+
<View style={styles.row}>
|
|
94
|
+
<Text>{item}</Text>
|
|
95
|
+
</View>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
</Tab>
|
|
99
|
+
|
|
100
|
+
<Tab name="Likes">
|
|
101
|
+
<FlatList
|
|
102
|
+
name="Likes"
|
|
103
|
+
data={Array.from({ length: 30 }).map((_, i) => `Like ${i}`)}
|
|
104
|
+
keyExtractor={(item) => item}
|
|
105
|
+
renderItem={({ item }) => (
|
|
106
|
+
<View style={styles.row}>
|
|
107
|
+
<Text>{item}</Text>
|
|
108
|
+
</View>
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
</Tab>
|
|
112
|
+
</CollapseTabs>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const styles = StyleSheet.create({
|
|
117
|
+
header: { flex: 1, backgroundColor: "#2D8CFF", justifyContent: "center", alignItems: "center" },
|
|
118
|
+
headerText: { color: "#fff", fontSize: 22, fontWeight: "600" },
|
|
119
|
+
row: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: "#eee" },
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> ⚠️ Important: the `name` prop of `<Tab>` and the `name` prop of the wrapped `<FlatList>` / `<ScrollView>` **must match** — that's how each tab's scroll position is tracked.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
### `<CollapseTabs />`
|
|
130
|
+
|
|
131
|
+
The root container.
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Required | Description |
|
|
134
|
+
| ---------------------- | ---------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
135
|
+
| `headerHeight` | `number` | ✅ | Fixed height of the header content (above the tab bar). |
|
|
136
|
+
| `tabBarHeight` | `number` | ✅ | Fixed height of the tab bar. |
|
|
137
|
+
| `children` | `TabReactElement \| TabReactElement[]` | ✅ | One or more `<Tab>` children. |
|
|
138
|
+
| `initialTabName` | `string` | | Tab to focus on mount. Defaults to the first child. |
|
|
139
|
+
| `minHeaderHeight` | `number` | | Minimum visible header height once collapsed. Default `0`. |
|
|
140
|
+
| `renderHeader` | `(props: HeaderProps) => ReactElement \| null` | | Render function for the header content. |
|
|
141
|
+
| `renderTabBar` | `(props: TabBarProps) => ReactElement \| null` | | Render function for a custom tab bar. Falls back to `<DefaultTabBar>` if not provided. |
|
|
142
|
+
| `containerStyle` | `StyleProp<ViewStyle>` | | Style for the outer container. |
|
|
143
|
+
| `headerContainerStyle` | `StyleProp<ViewStyle>` | | Style for the absolutely-positioned header wrapper. |
|
|
144
|
+
| `pagerProps` | `Omit<PagerViewProps, "onPageScroll" \| "initialPage">` | | Extra props forwarded to `PagerView`. |
|
|
145
|
+
| `onIndexChange` | `(index: number) => void` | | Fired after the focused tab changes. |
|
|
146
|
+
|
|
147
|
+
### `<Tab />`
|
|
148
|
+
|
|
149
|
+
Wraps a single tab's content. Should be a direct child of `<CollapseTabs>`.
|
|
150
|
+
|
|
151
|
+
| Prop | Type | Required | Description |
|
|
152
|
+
| ---------- | ----------------- | -------- | ------------------------------------------------- |
|
|
153
|
+
| `name` | `string` | ✅ | Unique identifier — must match the inner list's `name`. |
|
|
154
|
+
| `label` | `string` | | Optional display label (currently used by `DefaultTabBar` via `name`). |
|
|
155
|
+
| `children` | `React.ReactNode` | ✅ | Tab content — usually a wrapped `<FlatList>` or `<ScrollView>`. |
|
|
156
|
+
|
|
157
|
+
### `<FlatList />` and `<ScrollView />`
|
|
158
|
+
|
|
159
|
+
Drop-in replacements for the standard components, pre-wired to the collapse-tabs scroll system.
|
|
160
|
+
|
|
161
|
+
| Prop | Type | Required | Description |
|
|
162
|
+
| ------ | -------- | -------- | ------------------------------------------------------ |
|
|
163
|
+
| `name` | `string` | ✅ | Must match the parent `<Tab name="..." />`. |
|
|
164
|
+
| ... | — | | All other standard `FlatList` / `ScrollView` props. |
|
|
165
|
+
|
|
166
|
+
> They automatically apply `paddingTop: headerHeight + tabBarHeight` to the content so your first item starts below the header.
|
|
167
|
+
|
|
168
|
+
### `<DefaultTabBar />`
|
|
169
|
+
|
|
170
|
+
A minimal built-in tab bar with a sliding underline indicator. Pass any custom UI through `renderTabBar` if you need something different.
|
|
171
|
+
|
|
172
|
+
### `useTabsContext()`
|
|
173
|
+
|
|
174
|
+
Hook for advanced use cases (e.g. building a fully custom header/tab bar that needs access to the shared scroll state).
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
const {
|
|
178
|
+
headerHeight,
|
|
179
|
+
tabBarHeight,
|
|
180
|
+
headerScrollDistance,
|
|
181
|
+
tabNames,
|
|
182
|
+
index, // SharedValue<number>
|
|
183
|
+
indexDecimal, // SharedValue<number>
|
|
184
|
+
focusedTab, // SharedValue<string>
|
|
185
|
+
scrollY, // SharedValue<Record<string, number>>
|
|
186
|
+
headerTranslateY,// SharedValue<number>
|
|
187
|
+
containerRef,
|
|
188
|
+
} = useTabsContext();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Customization
|
|
194
|
+
|
|
195
|
+
### Custom tab bar
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<CollapseTabs
|
|
199
|
+
headerHeight={200}
|
|
200
|
+
tabBarHeight={44}
|
|
201
|
+
renderTabBar={({ tabNames, indexDecimal, onTabPress }) => (
|
|
202
|
+
<MyFancyTabBar
|
|
203
|
+
tabs={tabNames}
|
|
204
|
+
indexDecimal={indexDecimal}
|
|
205
|
+
onPress={onTabPress}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
{/* ... */}
|
|
210
|
+
</CollapseTabs>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Animated header
|
|
214
|
+
|
|
215
|
+
`renderHeader` receives the same `HeaderProps` so you can animate things based on `indexDecimal` or read `headerTranslateY` from `useTabsContext()` inside a child component.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Types
|
|
220
|
+
|
|
221
|
+
All public types are exported from the package root:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import type {
|
|
225
|
+
CollapseTabsProps,
|
|
226
|
+
TabProps,
|
|
227
|
+
TabBarProps,
|
|
228
|
+
HeaderProps,
|
|
229
|
+
TabName,
|
|
230
|
+
} from "@orionarm/react-native-collapse-tabs";
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Roadmap
|
|
236
|
+
|
|
237
|
+
- [ ] Pull-to-refresh integration
|
|
238
|
+
- [ ] Dynamic header height
|
|
239
|
+
- [ ] SectionList / horizontal-list support
|
|
240
|
+
- [ ] Snap-to-collapse behavior
|
|
241
|
+
- [ ] Example app & screenshots
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
ISC © yanan_orionarm
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollapseTabs.d.ts","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CollapseTabs.d.ts","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAkBf,OAAO,EACL,iBAAiB,EAOlB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgLpD,CAAC"}
|
package/lib/CollapseTabs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useRef, useState, } from "react";
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
3
|
import PagerView from "react-native-pager-view";
|
|
4
|
-
import Animated, { useAnimatedReaction, useAnimatedStyle, useEvent, useHandler, useSharedValue, } from "react-native-reanimated";
|
|
4
|
+
import Animated, { useAnimatedReaction, useAnimatedStyle, useEvent, useHandler, useSharedValue, withTiming, } from "react-native-reanimated";
|
|
5
5
|
import { Context } from "./context";
|
|
6
6
|
import { DefaultTabBar } from "./TabBar";
|
|
7
7
|
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView);
|
|
@@ -21,10 +21,19 @@ export const CollapseTabs = ({ children, initialTabName, headerHeight, tabBarHei
|
|
|
21
21
|
const scrollY = useSharedValue(initialScrollY);
|
|
22
22
|
const headerTranslateY = useSharedValue(0);
|
|
23
23
|
useAnimatedReaction(() => {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const tab = focusedTab.value;
|
|
25
|
+
const y = scrollY.value[tab] ?? 0;
|
|
26
|
+
return {
|
|
27
|
+
tab,
|
|
28
|
+
translateY: -Math.min(y, headerScrollDistance),
|
|
29
|
+
};
|
|
30
|
+
}, (next, prev) => {
|
|
31
|
+
if (prev && prev.tab !== next.tab) {
|
|
32
|
+
headerTranslateY.value = withTiming(next.translateY, { duration: 250 });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
headerTranslateY.value = next.translateY;
|
|
36
|
+
}
|
|
28
37
|
}, [headerScrollDistance]);
|
|
29
38
|
const refMap = useRef({}).current;
|
|
30
39
|
const setRef = useCallback((key, ref) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapseTabs.d.ts","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAiBf,OAAO,EACL,iBAAiB,EAOlB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAwKpD,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.CollapseTabs = void 0;
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
41
|
+
const react_native_1 = require("react-native");
|
|
42
|
+
const react_native_pager_view_1 = __importDefault(require("react-native-pager-view"));
|
|
43
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
44
|
+
const context_1 = require("./context");
|
|
45
|
+
const TabBar_1 = require("./TabBar");
|
|
46
|
+
const AnimatedPagerView = react_native_reanimated_1.default.createAnimatedComponent(react_native_pager_view_1.default);
|
|
47
|
+
const CollapseTabs = ({ children, initialTabName, headerHeight, tabBarHeight, minHeaderHeight = 0, renderHeader, renderTabBar, containerStyle, headerContainerStyle, pagerProps, onIndexChange, }) => {
|
|
48
|
+
const tabs = (0, react_1.useMemo)(() => react_1.default.Children.toArray(children).filter((c) => react_1.default.isValidElement(c)), [children]);
|
|
49
|
+
const tabNames = (0, react_1.useMemo)(() => tabs.map((t) => t.props.name), [tabs]);
|
|
50
|
+
const initialIndex = Math.max(0, initialTabName ? tabNames.indexOf(initialTabName) : 0);
|
|
51
|
+
const headerScrollDistance = headerHeight - minHeaderHeight;
|
|
52
|
+
const index = (0, react_native_reanimated_1.useSharedValue)(initialIndex);
|
|
53
|
+
const indexDecimal = (0, react_native_reanimated_1.useSharedValue)(initialIndex);
|
|
54
|
+
const focusedTab = (0, react_native_reanimated_1.useSharedValue)(tabNames[initialIndex] ?? "");
|
|
55
|
+
const initialScrollY = (0, react_1.useMemo)(() => {
|
|
56
|
+
const m = {};
|
|
57
|
+
tabNames.forEach((n) => (m[n] = 0));
|
|
58
|
+
return m;
|
|
59
|
+
}, [tabNames]);
|
|
60
|
+
const scrollY = (0, react_native_reanimated_1.useSharedValue)(initialScrollY);
|
|
61
|
+
const headerTranslateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
62
|
+
(0, react_native_reanimated_1.useAnimatedReaction)(() => {
|
|
63
|
+
const y = scrollY.value[focusedTab.value] ?? 0;
|
|
64
|
+
return -Math.min(y, headerScrollDistance);
|
|
65
|
+
}, (next) => {
|
|
66
|
+
headerTranslateY.value = next;
|
|
67
|
+
}, [headerScrollDistance]);
|
|
68
|
+
const refMap = (0, react_1.useRef)({}).current;
|
|
69
|
+
const setRef = (0, react_1.useCallback)((key, ref) => {
|
|
70
|
+
refMap[key] = ref;
|
|
71
|
+
return ref;
|
|
72
|
+
}, [refMap]);
|
|
73
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
74
|
+
const onTabPress = (0, react_1.useCallback)((name) => {
|
|
75
|
+
const i = tabNames.indexOf(name);
|
|
76
|
+
if (i < 0)
|
|
77
|
+
return;
|
|
78
|
+
containerRef.current?.setPage(i);
|
|
79
|
+
}, [tabNames]);
|
|
80
|
+
const [renderIndex, setRenderIndex] = (0, react_1.useState)(initialIndex);
|
|
81
|
+
const { doDependenciesDiffer } = (0, react_native_reanimated_1.useHandler)({});
|
|
82
|
+
const pageScrollHandler = (0, react_native_reanimated_1.useEvent)((e) => {
|
|
83
|
+
"worklet";
|
|
84
|
+
indexDecimal.value = e.position + e.offset;
|
|
85
|
+
}, ["onPageScroll"], doDependenciesDiffer);
|
|
86
|
+
const onPageSelected = (0, react_1.useCallback)((e) => {
|
|
87
|
+
const i = e.nativeEvent.position;
|
|
88
|
+
index.value = i;
|
|
89
|
+
focusedTab.value = tabNames[i] ?? "";
|
|
90
|
+
setRenderIndex(i);
|
|
91
|
+
onIndexChange?.(i);
|
|
92
|
+
}, [tabNames, index, focusedTab, onIndexChange]);
|
|
93
|
+
const headerAnimStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
94
|
+
transform: [{ translateY: headerTranslateY.value }],
|
|
95
|
+
}));
|
|
96
|
+
const ctxValue = {
|
|
97
|
+
headerHeight,
|
|
98
|
+
tabBarHeight,
|
|
99
|
+
minHeaderHeight,
|
|
100
|
+
headerScrollDistance,
|
|
101
|
+
tabNames,
|
|
102
|
+
index,
|
|
103
|
+
indexDecimal,
|
|
104
|
+
focusedTab,
|
|
105
|
+
scrollY,
|
|
106
|
+
headerTranslateY,
|
|
107
|
+
setRef,
|
|
108
|
+
refMap,
|
|
109
|
+
containerRef,
|
|
110
|
+
};
|
|
111
|
+
const headerProps = {
|
|
112
|
+
tabNames,
|
|
113
|
+
focusedTab,
|
|
114
|
+
index,
|
|
115
|
+
indexDecimal,
|
|
116
|
+
onTabPress,
|
|
117
|
+
};
|
|
118
|
+
return (<context_1.Context.Provider value={ctxValue}>
|
|
119
|
+
<react_native_1.View style={[styles.container, containerStyle]}>
|
|
120
|
+
<AnimatedPagerView ref={containerRef} style={react_native_1.StyleSheet.absoluteFill} initialPage={initialIndex} onPageScroll={pageScrollHandler} onPageSelected={onPageSelected} {...pagerProps}>
|
|
121
|
+
{tabs.map((tab, i) => (<react_native_1.View key={tab.props.name} style={styles.page} collapsable={false}>
|
|
122
|
+
{tab}
|
|
123
|
+
</react_native_1.View>))}
|
|
124
|
+
</AnimatedPagerView>
|
|
125
|
+
|
|
126
|
+
<react_native_reanimated_1.default.View pointerEvents="box-none" style={[
|
|
127
|
+
styles.headerContainer,
|
|
128
|
+
{ height: headerHeight + tabBarHeight },
|
|
129
|
+
headerContainerStyle,
|
|
130
|
+
headerAnimStyle,
|
|
131
|
+
]}>
|
|
132
|
+
<react_native_1.View pointerEvents="box-none" style={{ height: headerHeight }}>
|
|
133
|
+
{renderHeader?.(headerProps)}
|
|
134
|
+
</react_native_1.View>
|
|
135
|
+
<react_native_1.View style={{ height: tabBarHeight }}>
|
|
136
|
+
{renderTabBar
|
|
137
|
+
? renderTabBar(headerProps)
|
|
138
|
+
: <TabBar_1.DefaultTabBar {...headerProps}/>}
|
|
139
|
+
</react_native_1.View>
|
|
140
|
+
</react_native_reanimated_1.default.View>
|
|
141
|
+
</react_native_1.View>
|
|
142
|
+
</context_1.Context.Provider>);
|
|
143
|
+
};
|
|
144
|
+
exports.CollapseTabs = CollapseTabs;
|
|
145
|
+
const styles = react_native_1.StyleSheet.create({
|
|
146
|
+
container: { flex: 1, overflow: "hidden" },
|
|
147
|
+
page: { flex: 1 },
|
|
148
|
+
headerContainer: {
|
|
149
|
+
position: "absolute",
|
|
150
|
+
top: 0,
|
|
151
|
+
left: 0,
|
|
152
|
+
right: 0,
|
|
153
|
+
zIndex: 10,
|
|
154
|
+
backgroundColor: "#fff",
|
|
155
|
+
},
|
|
156
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapseTabs.js","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":";;;AAAA,iCAA0B;AAC1B,+CAAiE;AAO1D,MAAM,YAAY,GAAgC,CAAC,EACxD,QAAQ,EACR,KAAK,GACN,EAAE,EAAE;IACH,OAAO,CAAC,mBAAI,CAAE,CAAA,CAAC,QAAQ,CAAC,EAAE,mBAAI,CAAC,CAAC;AAClC,CAAC,CAAC;AALW,QAAA,YAAY,gBAKvB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FlatListProps } from "react-native";
|
|
3
|
+
type Props<T> = FlatListProps<T> & {
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function FlatList<T>({ name, contentContainerStyle, ...rest }: Props<T>): React.JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=FlatList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlatList.d.ts","sourceRoot":"","sources":["../src/FlatList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AACzC,OAAO,EAA0B,aAAa,EAAE,MAAM,cAAc,CAAC;AAOrE,KAAK,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,GAAG,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,qBAoC7E"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FlatList = FlatList;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
39
|
+
const hooks_1 = require("./hooks");
|
|
40
|
+
function FlatList({ name, contentContainerStyle, ...rest }) {
|
|
41
|
+
const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = (0, hooks_1.useTabsContext)();
|
|
42
|
+
const ref = (0, react_native_reanimated_1.useAnimatedRef)();
|
|
43
|
+
(0, react_1.useEffect)(() => {
|
|
44
|
+
setRef(name, ref);
|
|
45
|
+
}, [name, ref, setRef]);
|
|
46
|
+
const handler = (0, react_native_reanimated_1.useAnimatedScrollHandler)({
|
|
47
|
+
onScroll: (event) => {
|
|
48
|
+
if (focusedTab.value !== name)
|
|
49
|
+
return;
|
|
50
|
+
const map = { ...scrollY.value };
|
|
51
|
+
map[name] = event.contentOffset.y;
|
|
52
|
+
scrollY.value = map;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return (<react_native_reanimated_1.default.FlatList {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
|
|
56
|
+
{ paddingTop: headerHeight + tabBarHeight },
|
|
57
|
+
contentContainerStyle,
|
|
58
|
+
]}/>);
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ScrollViewProps } from "react-native";
|
|
3
|
+
type Props = ScrollViewProps & {
|
|
4
|
+
name: string;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function ScrollView({ name, contentContainerStyle, children, ...rest }: Props): React.JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ScrollView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollView.d.ts","sourceRoot":"","sources":["../src/ScrollView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,eAAe,EAA8B,MAAM,cAAc,CAAC;AAO3E,KAAK,KAAK,GAAG,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAAC;AAE5E,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,qBAAqB,EACrB,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,KAAK,qBAsCP"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ScrollView = ScrollView;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
39
|
+
const hooks_1 = require("./hooks");
|
|
40
|
+
function ScrollView({ name, contentContainerStyle, children, ...rest }) {
|
|
41
|
+
const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = (0, hooks_1.useTabsContext)();
|
|
42
|
+
const ref = (0, react_native_reanimated_1.useAnimatedRef)();
|
|
43
|
+
(0, react_1.useEffect)(() => {
|
|
44
|
+
setRef(name, ref);
|
|
45
|
+
}, [name, ref, setRef]);
|
|
46
|
+
const handler = (0, react_native_reanimated_1.useAnimatedScrollHandler)({
|
|
47
|
+
onScroll: (event) => {
|
|
48
|
+
if (focusedTab.value !== name)
|
|
49
|
+
return;
|
|
50
|
+
const map = { ...scrollY.value };
|
|
51
|
+
map[name] = event.contentOffset.y;
|
|
52
|
+
scrollY.value = map;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return (<react_native_reanimated_1.default.ScrollView {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
|
|
56
|
+
{ paddingTop: headerHeight + tabBarHeight },
|
|
57
|
+
contentContainerStyle,
|
|
58
|
+
]}>
|
|
59
|
+
{children}
|
|
60
|
+
</react_native_reanimated_1.default.ScrollView>);
|
|
61
|
+
}
|
package/lib/lib/Tab.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TabProps } from "./types/types";
|
|
3
|
+
/**
|
|
4
|
+
* Container for a single tab's content. Should be a direct child of
|
|
5
|
+
* <CollapseTabs>. Place a wrapped <FlatList> or <ScrollView> inside.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Tab: <T extends string>({ children }: TabProps<T>) => React.JSX.Element;
|
|
8
|
+
//# sourceMappingURL=Tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tab.d.ts","sourceRoot":"","sources":["../src/Tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAAE,cAAc,QAAQ,CAAC,CAAC,CAAC,sBAE9D,CAAC"}
|
package/lib/lib/Tab.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Tab = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const react_native_1 = require("react-native");
|
|
9
|
+
/**
|
|
10
|
+
* Container for a single tab's content. Should be a direct child of
|
|
11
|
+
* <CollapseTabs>. Place a wrapped <FlatList> or <ScrollView> inside.
|
|
12
|
+
*/
|
|
13
|
+
const Tab = ({ children }) => {
|
|
14
|
+
return <react_native_1.View style={styles.container}>{children}</react_native_1.View>;
|
|
15
|
+
};
|
|
16
|
+
exports.Tab = Tab;
|
|
17
|
+
const styles = react_native_1.StyleSheet.create({
|
|
18
|
+
container: { flex: 1 },
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TabBar.d.ts","sourceRoot":"","sources":["../src/TabBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmB/C,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.DefaultTabBar = void 0;
|
|
40
|
+
const react_1 = __importDefault(require("react"));
|
|
41
|
+
const react_native_1 = require("react-native");
|
|
42
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
43
|
+
const DefaultTabBar = ({ tabNames, indexDecimal, onTabPress, }) => {
|
|
44
|
+
return (<react_native_1.View style={styles.container}>
|
|
45
|
+
{tabNames.map((name, i) => (<TabBarItem key={name} name={name} index={i} indexDecimal={indexDecimal} onPress={() => onTabPress(name)}/>))}
|
|
46
|
+
<Indicator count={tabNames.length} indexDecimal={indexDecimal}/>
|
|
47
|
+
</react_native_1.View>);
|
|
48
|
+
};
|
|
49
|
+
exports.DefaultTabBar = DefaultTabBar;
|
|
50
|
+
const TabBarItem = ({ name, index, indexDecimal, onPress }) => {
|
|
51
|
+
const labelStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
52
|
+
opacity: (0, react_native_reanimated_1.interpolate)(indexDecimal.value, [index - 1, index, index + 1], [0.6, 1, 0.6], "clamp"),
|
|
53
|
+
}));
|
|
54
|
+
return (<react_native_1.Pressable style={styles.item} onPress={onPress}>
|
|
55
|
+
<react_native_reanimated_1.default.Text style={[styles.label, labelStyle]}>{name}</react_native_reanimated_1.default.Text>
|
|
56
|
+
</react_native_1.Pressable>);
|
|
57
|
+
};
|
|
58
|
+
const Indicator = ({ count, indexDecimal }) => {
|
|
59
|
+
const style = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
60
|
+
transform: [
|
|
61
|
+
{ translateX: (indexDecimal.value * 100) / count + "%" },
|
|
62
|
+
],
|
|
63
|
+
width: `${100 / count}%`,
|
|
64
|
+
}));
|
|
65
|
+
return <react_native_reanimated_1.default.View style={[styles.indicator, style]}/>;
|
|
66
|
+
};
|
|
67
|
+
const styles = react_native_1.StyleSheet.create({
|
|
68
|
+
container: {
|
|
69
|
+
flexDirection: "row",
|
|
70
|
+
backgroundColor: "#fff",
|
|
71
|
+
},
|
|
72
|
+
item: {
|
|
73
|
+
flex: 1,
|
|
74
|
+
alignItems: "center",
|
|
75
|
+
justifyContent: "center",
|
|
76
|
+
},
|
|
77
|
+
label: {
|
|
78
|
+
fontSize: 14,
|
|
79
|
+
fontWeight: "500",
|
|
80
|
+
color: "#333",
|
|
81
|
+
},
|
|
82
|
+
indicator: {
|
|
83
|
+
position: "absolute",
|
|
84
|
+
bottom: 0,
|
|
85
|
+
left: 0,
|
|
86
|
+
height: 2,
|
|
87
|
+
backgroundColor: "#2D8CFF",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAW,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzE,eAAO,MAAM,OAAO,gDAEnB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Context = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
exports.Context = react_1.default.createContext(undefined);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,wBAAgB,cAAc,IAAI,WAAW,CAAC,MAAM,CAAC,CAIpD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTabsContext = useTabsContext;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const context_1 = require("../context");
|
|
6
|
+
function useTabsContext() {
|
|
7
|
+
const c = (0, react_1.useContext)(context_1.Context);
|
|
8
|
+
if (!c)
|
|
9
|
+
throw new Error("useTabsContext must be used inside <CollapseTabs>");
|
|
10
|
+
return c;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CollapseTabs } from "./CollapseTabs";
|
|
2
|
+
export { Tab } from "./Tab";
|
|
3
|
+
export { FlatList } from "./FlatList";
|
|
4
|
+
export { ScrollView } from "./ScrollView";
|
|
5
|
+
export { DefaultTabBar } from "./TabBar";
|
|
6
|
+
export { useTabsContext } from "./hooks";
|
|
7
|
+
export type { CollapseTabsProps, TabProps, TabBarProps, HeaderProps, TabName, } from "./types/types";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,YAAY,EACV,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,OAAO,GACR,MAAM,eAAe,CAAC"}
|
package/lib/lib/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTabsContext = exports.DefaultTabBar = exports.ScrollView = exports.FlatList = exports.Tab = exports.CollapseTabs = void 0;
|
|
4
|
+
var CollapseTabs_1 = require("./CollapseTabs");
|
|
5
|
+
Object.defineProperty(exports, "CollapseTabs", { enumerable: true, get: function () { return CollapseTabs_1.CollapseTabs; } });
|
|
6
|
+
var Tab_1 = require("./Tab");
|
|
7
|
+
Object.defineProperty(exports, "Tab", { enumerable: true, get: function () { return Tab_1.Tab; } });
|
|
8
|
+
var FlatList_1 = require("./FlatList");
|
|
9
|
+
Object.defineProperty(exports, "FlatList", { enumerable: true, get: function () { return FlatList_1.FlatList; } });
|
|
10
|
+
var ScrollView_1 = require("./ScrollView");
|
|
11
|
+
Object.defineProperty(exports, "ScrollView", { enumerable: true, get: function () { return ScrollView_1.ScrollView; } });
|
|
12
|
+
var TabBar_1 = require("./TabBar");
|
|
13
|
+
Object.defineProperty(exports, "DefaultTabBar", { enumerable: true, get: function () { return TabBar_1.DefaultTabBar; } });
|
|
14
|
+
var hooks_1 = require("./hooks");
|
|
15
|
+
Object.defineProperty(exports, "useTabsContext", { enumerable: true, get: function () { return hooks_1.useTabsContext; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;;AAAA,+CAA8C;AAArC,4GAAA,YAAY,OAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FlatList, ScrollView, StyleProp, ViewStyle } from "react-native";
|
|
3
|
+
import PagerView, { PagerViewProps } from "react-native-pager-view";
|
|
4
|
+
import Animated, { AnimatedRef, SharedValue } from "react-native-reanimated";
|
|
5
|
+
export type TabName = string;
|
|
6
|
+
export type ContainerRef = PagerView;
|
|
7
|
+
export type RefComponent = FlatList<any> | ScrollView | Animated.ScrollView;
|
|
8
|
+
export type TabBarProps<T extends TabName = TabName> = {
|
|
9
|
+
tabNames: T[];
|
|
10
|
+
focusedTab: SharedValue<T>;
|
|
11
|
+
index: SharedValue<number>;
|
|
12
|
+
indexDecimal: SharedValue<number>;
|
|
13
|
+
onTabPress: (name: T) => void;
|
|
14
|
+
};
|
|
15
|
+
export type HeaderProps<T extends TabName = TabName> = TabBarProps<T>;
|
|
16
|
+
export type TabProps<T extends TabName = TabName> = {
|
|
17
|
+
readonly name: T;
|
|
18
|
+
label?: string;
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
};
|
|
21
|
+
export type TabReactElement<T extends TabName = TabName> = React.ReactElement<TabProps<T>>;
|
|
22
|
+
export type CollapseTabsProps = {
|
|
23
|
+
children: TabReactElement | TabReactElement[];
|
|
24
|
+
/** Initial tab name. Defaults to the first child. */
|
|
25
|
+
initialTabName?: TabName;
|
|
26
|
+
/** Fixed header height. Required for MVP. */
|
|
27
|
+
headerHeight: number;
|
|
28
|
+
/** Fixed tab bar height. Required for MVP. */
|
|
29
|
+
tabBarHeight: number;
|
|
30
|
+
/** Minimum header height when fully collapsed. @default 0 */
|
|
31
|
+
minHeaderHeight?: number;
|
|
32
|
+
renderHeader?: (props: HeaderProps) => React.ReactElement | null;
|
|
33
|
+
renderTabBar?: (props: TabBarProps) => React.ReactElement | null;
|
|
34
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
35
|
+
headerContainerStyle?: StyleProp<ViewStyle>;
|
|
36
|
+
pagerProps?: Omit<PagerViewProps, "onPageScroll" | "initialPage">;
|
|
37
|
+
onIndexChange?: (index: number) => void;
|
|
38
|
+
};
|
|
39
|
+
/** Internal context shared between CollapseTabs and its children. */
|
|
40
|
+
export type ContextType<T extends TabName = TabName> = {
|
|
41
|
+
headerHeight: number;
|
|
42
|
+
tabBarHeight: number;
|
|
43
|
+
minHeaderHeight: number;
|
|
44
|
+
headerScrollDistance: number;
|
|
45
|
+
tabNames: T[];
|
|
46
|
+
index: SharedValue<number>;
|
|
47
|
+
indexDecimal: SharedValue<number>;
|
|
48
|
+
focusedTab: SharedValue<T>;
|
|
49
|
+
/** Per-tab scroll Y, keyed by tab name. */
|
|
50
|
+
scrollY: SharedValue<Record<T, number>>;
|
|
51
|
+
/** Header translateY (clamped to [-headerScrollDistance, 0]). */
|
|
52
|
+
headerTranslateY: SharedValue<number>;
|
|
53
|
+
setRef: <R extends RefComponent>(key: T, ref: AnimatedRef<R>) => AnimatedRef<R>;
|
|
54
|
+
refMap: Record<T, AnimatedRef<RefComponent>>;
|
|
55
|
+
containerRef: React.RefObject<ContainerRef>;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/CollapseTabs.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import Animated, {
|
|
|
17
17
|
useEvent,
|
|
18
18
|
useHandler,
|
|
19
19
|
useSharedValue,
|
|
20
|
+
withTiming,
|
|
20
21
|
} from "react-native-reanimated";
|
|
21
22
|
import { Context } from "./context";
|
|
22
23
|
import { DefaultTabBar } from "./TabBar";
|
|
@@ -80,11 +81,19 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
|
|
|
80
81
|
|
|
81
82
|
useAnimatedReaction(
|
|
82
83
|
() => {
|
|
83
|
-
const
|
|
84
|
-
|
|
84
|
+
const tab = focusedTab.value;
|
|
85
|
+
const y = scrollY.value[tab] ?? 0;
|
|
86
|
+
return {
|
|
87
|
+
tab,
|
|
88
|
+
translateY: -Math.min(y, headerScrollDistance),
|
|
89
|
+
};
|
|
85
90
|
},
|
|
86
|
-
(next) => {
|
|
87
|
-
|
|
91
|
+
(next, prev) => {
|
|
92
|
+
if (prev && prev.tab !== next.tab) {
|
|
93
|
+
headerTranslateY.value = withTiming(next.translateY, { duration: 250 });
|
|
94
|
+
} else {
|
|
95
|
+
headerTranslateY.value = next.translateY;
|
|
96
|
+
}
|
|
88
97
|
},
|
|
89
98
|
[headerScrollDistance]
|
|
90
99
|
);
|