@orionarm/react-native-collapse-tabs 1.0.2 → 1.0.4

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 (42) hide show
  1. package/README.md +245 -1
  2. package/lib/CollapseTabs.d.ts.map +1 -1
  3. package/lib/CollapseTabs.js +16 -9
  4. package/lib/FlatList.d.ts +1 -1
  5. package/lib/FlatList.d.ts.map +1 -1
  6. package/lib/FlatList.js +16 -8
  7. package/lib/ScrollView.d.ts +1 -1
  8. package/lib/ScrollView.d.ts.map +1 -1
  9. package/lib/ScrollView.js +16 -8
  10. package/lib/lib/CollapseTabs.d.ts +4 -0
  11. package/lib/lib/CollapseTabs.d.ts.map +1 -0
  12. package/lib/lib/CollapseTabs.js +156 -0
  13. package/lib/lib/CollapseTabs.js.map +1 -0
  14. package/lib/lib/FlatList.d.ts +8 -0
  15. package/lib/lib/FlatList.d.ts.map +1 -0
  16. package/lib/lib/FlatList.js +59 -0
  17. package/lib/lib/ScrollView.d.ts +9 -0
  18. package/lib/lib/ScrollView.d.ts.map +1 -0
  19. package/lib/lib/ScrollView.js +61 -0
  20. package/lib/lib/Tab.d.ts +8 -0
  21. package/lib/lib/Tab.d.ts.map +1 -0
  22. package/lib/lib/Tab.js +19 -0
  23. package/lib/lib/TabBar.d.ts +4 -0
  24. package/lib/lib/TabBar.d.ts.map +1 -0
  25. package/lib/lib/TabBar.js +89 -0
  26. package/lib/lib/context/index.d.ts +5 -0
  27. package/lib/lib/context/index.d.ts.map +1 -0
  28. package/lib/lib/context/index.js +8 -0
  29. package/lib/lib/hooks/index.d.ts +3 -0
  30. package/lib/lib/hooks/index.d.ts.map +1 -0
  31. package/lib/lib/hooks/index.js +11 -0
  32. package/lib/lib/index.d.ts +8 -0
  33. package/lib/lib/index.d.ts.map +1 -0
  34. package/lib/lib/index.js +15 -0
  35. package/lib/lib/index.js.map +1 -0
  36. package/lib/lib/types/types.d.ts +57 -0
  37. package/lib/lib/types/types.d.ts.map +1 -0
  38. package/lib/lib/types/types.js +2 -0
  39. package/package.json +1 -1
  40. package/src/CollapseTabs.tsx +37 -31
  41. package/src/FlatList.tsx +25 -8
  42. package/src/ScrollView.tsx +20 -7
package/README.md CHANGED
@@ -1,3 +1,247 @@
1
1
  # @orionarm/react-native-collapse-tabs
2
2
 
3
- > ⚠️ **Work in Progress** - This package is currently under active development and not yet ready for production use.
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;AAiBf,OAAO,EACL,iBAAiB,EAOlB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAwKpD,CAAC"}
1
+ {"version":3,"file":"CollapseTabs.d.ts","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAkBtE,OAAO,EACL,iBAAiB,EAOlB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAkLpD,CAAC"}
@@ -1,7 +1,7 @@
1
- import React, { useCallback, useMemo, useRef, useState, } from "react";
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 y = scrollY.value[focusedTab.value] ?? 0;
25
- return -Math.min(y, headerScrollDistance);
26
- }, (next) => {
27
- headerTranslateY.value = next;
24
+ const tab = focusedTab.value;
25
+ const y = scrollY.value[tab] ?? 0;
26
+ return {
27
+ tab,
28
+ translateY: -Math.min(Math.max(y, 0), 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) => {
@@ -94,9 +103,7 @@ export const CollapseTabs = ({ children, initialTabName, headerHeight, tabBarHei
94
103
  {renderHeader?.(headerProps)}
95
104
  </View>
96
105
  <View style={{ height: tabBarHeight }}>
97
- {renderTabBar
98
- ? renderTabBar(headerProps)
99
- : <DefaultTabBar {...headerProps}/>}
106
+ {renderTabBar ? (renderTabBar(headerProps)) : (<DefaultTabBar {...headerProps}/>)}
100
107
  </View>
101
108
  </Animated.View>
102
109
  </View>
package/lib/FlatList.d.ts CHANGED
@@ -3,6 +3,6 @@ import { FlatListProps } from "react-native";
3
3
  type Props<T> = FlatListProps<T> & {
4
4
  name: string;
5
5
  };
6
- export declare function FlatList<T>({ name, contentContainerStyle, ...rest }: Props<T>): React.JSX.Element;
6
+ export declare function FlatList<T>({ name, contentContainerStyle, onScroll: userOnScroll, ...rest }: Props<T>): React.JSX.Element;
7
7
  export {};
8
8
  //# sourceMappingURL=FlatList.d.ts.map
@@ -1 +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"}
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;AAQrE,KAAK,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAC1B,IAAI,EACJ,qBAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,GAAG,IAAI,EACR,EAAE,KAAK,CAAC,CAAC,CAAC,qBA+CV"}
package/lib/FlatList.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect } from "react";
2
- import Animated, { useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
2
+ import Animated, { runOnJS, useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
3
3
  import { useTabsContext } from "./hooks";
4
- export function FlatList({ name, contentContainerStyle, ...rest }) {
4
+ export function FlatList({ name, contentContainerStyle, onScroll: userOnScroll, ...rest }) {
5
5
  const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = useTabsContext();
6
6
  const ref = useAnimatedRef();
7
7
  useEffect(() => {
@@ -9,13 +9,21 @@ export function FlatList({ name, contentContainerStyle, ...rest }) {
9
9
  }, [name, ref, setRef]);
10
10
  const handler = useAnimatedScrollHandler({
11
11
  onScroll: (event) => {
12
- if (focusedTab.value !== name)
13
- return;
14
- const map = { ...scrollY.value };
15
- map[name] = event.contentOffset.y;
16
- scrollY.value = map;
12
+ if (focusedTab.value === name) {
13
+ const map = { ...scrollY.value };
14
+ map[name] = event.contentOffset.y;
15
+ scrollY.value = map;
16
+ }
17
+ if (userOnScroll) {
18
+ if (userOnScroll.worklet === true) {
19
+ userOnScroll(event);
20
+ }
21
+ else {
22
+ runOnJS(userOnScroll)(event);
23
+ }
24
+ }
17
25
  },
18
- });
26
+ }, [name, userOnScroll]);
19
27
  return (<Animated.FlatList {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
20
28
  { paddingTop: headerHeight + tabBarHeight },
21
29
  contentContainerStyle,
@@ -4,6 +4,6 @@ type Props = ScrollViewProps & {
4
4
  name: string;
5
5
  children?: React.ReactNode;
6
6
  };
7
- export declare function ScrollView({ name, contentContainerStyle, children, ...rest }: Props): React.JSX.Element;
7
+ export declare function ScrollView({ name, contentContainerStyle, children, onScroll: userOnScroll, ...rest }: Props): React.JSX.Element;
8
8
  export {};
9
9
  //# sourceMappingURL=ScrollView.d.ts.map
@@ -1 +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"}
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;AAQ3E,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,QAAQ,EAAE,YAAY,EACtB,GAAG,IAAI,EACR,EAAE,KAAK,qBAiDP"}
package/lib/ScrollView.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect } from "react";
2
- import Animated, { useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
2
+ import Animated, { runOnJS, useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
3
3
  import { useTabsContext } from "./hooks";
4
- export function ScrollView({ name, contentContainerStyle, children, ...rest }) {
4
+ export function ScrollView({ name, contentContainerStyle, children, onScroll: userOnScroll, ...rest }) {
5
5
  const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = useTabsContext();
6
6
  const ref = useAnimatedRef();
7
7
  useEffect(() => {
@@ -9,13 +9,21 @@ export function ScrollView({ name, contentContainerStyle, children, ...rest }) {
9
9
  }, [name, ref, setRef]);
10
10
  const handler = useAnimatedScrollHandler({
11
11
  onScroll: (event) => {
12
- if (focusedTab.value !== name)
13
- return;
14
- const map = { ...scrollY.value };
15
- map[name] = event.contentOffset.y;
16
- scrollY.value = map;
12
+ if (focusedTab.value === name) {
13
+ const map = { ...scrollY.value };
14
+ map[name] = event.contentOffset.y;
15
+ scrollY.value = map;
16
+ }
17
+ if (userOnScroll) {
18
+ if (userOnScroll.worklet === true) {
19
+ userOnScroll(event);
20
+ }
21
+ else {
22
+ runOnJS(userOnScroll)(event);
23
+ }
24
+ }
17
25
  },
18
- });
26
+ }, [name, userOnScroll]);
19
27
  return (<Animated.ScrollView {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
20
28
  { paddingTop: headerHeight + tabBarHeight },
21
29
  contentContainerStyle,
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { CollapseTabsProps } from "./types/types";
3
+ export declare const CollapseTabs: React.FC<CollapseTabsProps>;
4
+ //# sourceMappingURL=CollapseTabs.d.ts.map
@@ -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
+ }
@@ -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,4 @@
1
+ import React from "react";
2
+ import { TabBarProps } from "./types/types";
3
+ export declare const DefaultTabBar: React.FC<TabBarProps>;
4
+ //# sourceMappingURL=TabBar.d.ts.map
@@ -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,5 @@
1
+ import React from "react";
2
+ import { ContextType } from "../types/types";
3
+ export type { ContextType, TabName, RefComponent } from "../types/types";
4
+ export declare const Context: React.Context<ContextType<string> | undefined>;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ import { ContextType } from "../types/types";
2
+ export declare function useTabsContext(): ContextType<string>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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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"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orionarm/react-native-collapse-tabs",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A React Native collapsible tabs component",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,9 +1,4 @@
1
- import React, {
2
- useCallback,
3
- useMemo,
4
- useRef,
5
- useState,
6
- } from "react";
1
+ import React, { useCallback, useMemo, useRef, useState } from "react";
7
2
  import { StyleSheet, View } from "react-native";
8
3
  import PagerView, {
9
4
  PagerViewOnPageScrollEventData,
@@ -17,6 +12,7 @@ import Animated, {
17
12
  useEvent,
18
13
  useHandler,
19
14
  useSharedValue,
15
+ withTiming,
20
16
  } from "react-native-reanimated";
21
17
  import { Context } from "./context";
22
18
  import { DefaultTabBar } from "./TabBar";
@@ -47,20 +43,20 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
47
43
  }) => {
48
44
  const tabs = useMemo(
49
45
  () =>
50
- React.Children.toArray(children).filter(
51
- (c): c is TabReactElement => React.isValidElement(c)
46
+ React.Children.toArray(children).filter((c): c is TabReactElement =>
47
+ React.isValidElement(c),
52
48
  ),
53
- [children]
49
+ [children],
54
50
  );
55
51
 
56
52
  const tabNames = useMemo(
57
53
  () => tabs.map((t) => (t.props as TabProps).name),
58
- [tabs]
54
+ [tabs],
59
55
  );
60
56
 
61
57
  const initialIndex = Math.max(
62
58
  0,
63
- initialTabName ? tabNames.indexOf(initialTabName) : 0
59
+ initialTabName ? tabNames.indexOf(initialTabName) : 0,
64
60
  );
65
61
 
66
62
  const headerScrollDistance = headerHeight - minHeaderHeight;
@@ -80,13 +76,21 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
80
76
 
81
77
  useAnimatedReaction(
82
78
  () => {
83
- const y = scrollY.value[focusedTab.value] ?? 0;
84
- return -Math.min(y, headerScrollDistance);
79
+ const tab = focusedTab.value;
80
+ const y = scrollY.value[tab] ?? 0;
81
+ return {
82
+ tab,
83
+ translateY: -Math.min(Math.max(y, 0), headerScrollDistance),
84
+ };
85
85
  },
86
- (next) => {
87
- headerTranslateY.value = next;
86
+ (next, prev) => {
87
+ if (prev && prev.tab !== next.tab) {
88
+ headerTranslateY.value = withTiming(next.translateY, { duration: 250 });
89
+ } else {
90
+ headerTranslateY.value = next.translateY;
91
+ }
88
92
  },
89
- [headerScrollDistance]
93
+ [headerScrollDistance],
90
94
  );
91
95
 
92
96
  const refMap = useRef<Record<TabName, AnimatedRef<RefComponent>>>({}).current;
@@ -96,16 +100,19 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
96
100
  refMap[key] = ref as unknown as AnimatedRef<RefComponent>;
97
101
  return ref;
98
102
  },
99
- [refMap]
103
+ [refMap],
100
104
  );
101
105
 
102
106
  const containerRef = useRef<ContainerRef>(null);
103
107
 
104
- const onTabPress = useCallback((name: TabName) => {
105
- const i = tabNames.indexOf(name);
106
- if (i < 0) return;
107
- containerRef.current?.setPage(i);
108
- }, [tabNames]);
108
+ const onTabPress = useCallback(
109
+ (name: TabName) => {
110
+ const i = tabNames.indexOf(name);
111
+ if (i < 0) return;
112
+ containerRef.current?.setPage(i);
113
+ },
114
+ [tabNames],
115
+ );
109
116
 
110
117
  const [renderIndex, setRenderIndex] = useState(initialIndex);
111
118
 
@@ -116,7 +123,7 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
116
123
  indexDecimal.value = e.position + e.offset;
117
124
  },
118
125
  ["onPageScroll"],
119
- doDependenciesDiffer
126
+ doDependenciesDiffer,
120
127
  );
121
128
 
122
129
  const onPageSelected = useCallback(
@@ -127,7 +134,7 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
127
134
  setRenderIndex(i);
128
135
  onIndexChange?.(i);
129
136
  },
130
- [tabNames, index, focusedTab, onIndexChange]
137
+ [tabNames, index, focusedTab, onIndexChange],
131
138
  );
132
139
 
133
140
  const headerAnimStyle = useAnimatedStyle(() => ({
@@ -185,16 +192,15 @@ export const CollapseTabs: React.FC<CollapseTabsProps> = ({
185
192
  headerAnimStyle,
186
193
  ]}
187
194
  >
188
- <View
189
- pointerEvents="box-none"
190
- style={{ height: headerHeight }}
191
- >
195
+ <View pointerEvents="box-none" style={{ height: headerHeight }}>
192
196
  {renderHeader?.(headerProps)}
193
197
  </View>
194
198
  <View style={{ height: tabBarHeight }}>
195
- {renderTabBar
196
- ? renderTabBar(headerProps)
197
- : <DefaultTabBar {...headerProps} />}
199
+ {renderTabBar ? (
200
+ renderTabBar(headerProps)
201
+ ) : (
202
+ <DefaultTabBar {...headerProps} />
203
+ )}
198
204
  </View>
199
205
  </Animated.View>
200
206
  </View>
package/src/FlatList.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { FlatList as RNFlatList, FlatListProps } from "react-native";
3
3
  import Animated, {
4
+ runOnJS,
4
5
  useAnimatedRef,
5
6
  useAnimatedScrollHandler,
6
7
  } from "react-native-reanimated";
@@ -8,7 +9,12 @@ import { useTabsContext } from "./hooks";
8
9
 
9
10
  type Props<T> = FlatListProps<T> & { name: string };
10
11
 
11
- export function FlatList<T>({ name, contentContainerStyle, ...rest }: Props<T>) {
12
+ export function FlatList<T>({
13
+ name,
14
+ contentContainerStyle,
15
+ onScroll: userOnScroll,
16
+ ...rest
17
+ }: Props<T>) {
12
18
  const {
13
19
  headerHeight,
14
20
  tabBarHeight,
@@ -23,14 +29,25 @@ export function FlatList<T>({ name, contentContainerStyle, ...rest }: Props<T>)
23
29
  setRef(name, ref);
24
30
  }, [name, ref, setRef]);
25
31
 
26
- const handler = useAnimatedScrollHandler({
27
- onScroll: (event) => {
28
- if (focusedTab.value !== name) return;
29
- const map = { ...scrollY.value };
30
- map[name] = event.contentOffset.y;
31
- scrollY.value = map;
32
+ const handler = useAnimatedScrollHandler(
33
+ {
34
+ onScroll: (event) => {
35
+ if (focusedTab.value === name) {
36
+ const map = { ...scrollY.value };
37
+ map[name] = event.contentOffset.y;
38
+ scrollY.value = map;
39
+ }
40
+ if (userOnScroll) {
41
+ if ((userOnScroll as any).worklet === true) {
42
+ (userOnScroll as any)(event);
43
+ } else {
44
+ runOnJS(userOnScroll as any)(event);
45
+ }
46
+ }
47
+ },
32
48
  },
33
- });
49
+ [name, userOnScroll],
50
+ );
34
51
 
35
52
  return (
36
53
  <Animated.FlatList
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { ScrollViewProps, ScrollView as RNScrollView } from "react-native";
3
3
  import Animated, {
4
+ runOnJS,
4
5
  useAnimatedRef,
5
6
  useAnimatedScrollHandler,
6
7
  } from "react-native-reanimated";
@@ -12,6 +13,7 @@ export function ScrollView({
12
13
  name,
13
14
  contentContainerStyle,
14
15
  children,
16
+ onScroll: userOnScroll,
15
17
  ...rest
16
18
  }: Props) {
17
19
  const {
@@ -28,14 +30,25 @@ export function ScrollView({
28
30
  setRef(name, ref);
29
31
  }, [name, ref, setRef]);
30
32
 
31
- const handler = useAnimatedScrollHandler({
32
- onScroll: (event) => {
33
- if (focusedTab.value !== name) return;
34
- const map = { ...scrollY.value };
35
- map[name] = event.contentOffset.y;
36
- scrollY.value = map;
33
+ const handler = useAnimatedScrollHandler(
34
+ {
35
+ onScroll: (event) => {
36
+ if (focusedTab.value === name) {
37
+ const map = { ...scrollY.value };
38
+ map[name] = event.contentOffset.y;
39
+ scrollY.value = map;
40
+ }
41
+ if (userOnScroll) {
42
+ if ((userOnScroll as any).worklet === true) {
43
+ (userOnScroll as any)(event);
44
+ } else {
45
+ runOnJS(userOnScroll as any)(event);
46
+ }
47
+ }
48
+ },
37
49
  },
38
- });
50
+ [name, userOnScroll],
51
+ );
39
52
 
40
53
  return (
41
54
  <Animated.ScrollView