@orionarm/react-native-collapse-tabs 1.0.1 → 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.
Files changed (69) hide show
  1. package/README.md +245 -1
  2. package/lib/CollapseTabs.d.ts +1 -5
  3. package/lib/CollapseTabs.d.ts.map +1 -1
  4. package/lib/CollapseTabs.js +124 -11
  5. package/lib/CollapseTabs.js.map +1 -0
  6. package/lib/FlatList.d.ts +8 -0
  7. package/lib/FlatList.d.ts.map +1 -0
  8. package/lib/FlatList.js +23 -0
  9. package/lib/ScrollView.d.ts +9 -0
  10. package/lib/ScrollView.d.ts.map +1 -0
  11. package/lib/ScrollView.js +25 -0
  12. package/lib/Tab.d.ts +8 -0
  13. package/lib/Tab.d.ts.map +1 -0
  14. package/lib/Tab.js +12 -0
  15. package/lib/TabBar.d.ts +4 -0
  16. package/lib/TabBar.d.ts.map +1 -0
  17. package/lib/TabBar.js +49 -0
  18. package/lib/context/index.d.ts +5 -0
  19. package/lib/context/index.d.ts.map +1 -0
  20. package/lib/context/index.js +2 -0
  21. package/lib/hooks/index.d.ts +3 -0
  22. package/lib/hooks/index.d.ts.map +1 -0
  23. package/lib/hooks/index.js +8 -0
  24. package/lib/index.d.ts +7 -2
  25. package/lib/index.d.ts.map +1 -1
  26. package/lib/index.js +6 -5
  27. package/lib/index.js.map +1 -0
  28. package/lib/lib/CollapseTabs.d.ts +4 -0
  29. package/lib/lib/CollapseTabs.d.ts.map +1 -0
  30. package/lib/lib/CollapseTabs.js +156 -0
  31. package/lib/lib/CollapseTabs.js.map +1 -0
  32. package/lib/lib/FlatList.d.ts +8 -0
  33. package/lib/lib/FlatList.d.ts.map +1 -0
  34. package/lib/lib/FlatList.js +59 -0
  35. package/lib/lib/ScrollView.d.ts +9 -0
  36. package/lib/lib/ScrollView.d.ts.map +1 -0
  37. package/lib/lib/ScrollView.js +61 -0
  38. package/lib/lib/Tab.d.ts +8 -0
  39. package/lib/lib/Tab.d.ts.map +1 -0
  40. package/lib/lib/Tab.js +19 -0
  41. package/lib/lib/TabBar.d.ts +4 -0
  42. package/lib/lib/TabBar.d.ts.map +1 -0
  43. package/lib/lib/TabBar.js +89 -0
  44. package/lib/lib/context/index.d.ts +5 -0
  45. package/lib/lib/context/index.d.ts.map +1 -0
  46. package/lib/lib/context/index.js +8 -0
  47. package/lib/lib/hooks/index.d.ts +3 -0
  48. package/lib/lib/hooks/index.d.ts.map +1 -0
  49. package/lib/lib/hooks/index.js +11 -0
  50. package/lib/lib/index.d.ts +8 -0
  51. package/lib/lib/index.d.ts.map +1 -0
  52. package/lib/lib/index.js +15 -0
  53. package/lib/lib/index.js.map +1 -0
  54. package/lib/lib/types/types.d.ts +57 -0
  55. package/lib/lib/types/types.d.ts.map +1 -0
  56. package/lib/lib/types/types.js +2 -0
  57. package/lib/types/types.d.ts +57 -0
  58. package/lib/types/types.d.ts.map +1 -0
  59. package/lib/types/types.js +1 -0
  60. package/package.json +9 -5
  61. package/src/CollapseTabs.tsx +219 -8
  62. package/src/FlatList.tsx +47 -0
  63. package/src/ScrollView.tsx +54 -0
  64. package/src/Tab.tsx +15 -0
  65. package/src/TabBar.tsx +86 -0
  66. package/src/context/index.tsx +8 -0
  67. package/src/hooks/index.ts +9 -0
  68. package/src/index.tsx +14 -2
  69. package/src/types/types.ts +85 -0
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,8 +1,4 @@
1
1
  import React from "react";
2
- import { ViewStyle } from "react-native";
3
- export interface CollapseTabsProps {
4
- children?: React.ReactNode;
5
- style?: ViewStyle;
6
- }
2
+ import { CollapseTabsProps } from "./types/types";
7
3
  export declare const CollapseTabs: React.FC<CollapseTabsProps>;
8
4
  //# sourceMappingURL=CollapseTabs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollapseTabs.d.ts","sourceRoot":"","sources":["../src/CollapseTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAoB,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAKpD,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"}
@@ -1,12 +1,125 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
1
+ import React, { useCallback, useMemo, useRef, useState, } from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import PagerView from "react-native-pager-view";
4
+ import Animated, { useAnimatedReaction, useAnimatedStyle, useEvent, useHandler, useSharedValue, withTiming, } from "react-native-reanimated";
5
+ import { Context } from "./context";
6
+ import { DefaultTabBar } from "./TabBar";
7
+ const AnimatedPagerView = Animated.createAnimatedComponent(PagerView);
8
+ export const CollapseTabs = ({ children, initialTabName, headerHeight, tabBarHeight, minHeaderHeight = 0, renderHeader, renderTabBar, containerStyle, headerContainerStyle, pagerProps, onIndexChange, }) => {
9
+ const tabs = useMemo(() => React.Children.toArray(children).filter((c) => React.isValidElement(c)), [children]);
10
+ const tabNames = useMemo(() => tabs.map((t) => t.props.name), [tabs]);
11
+ const initialIndex = Math.max(0, initialTabName ? tabNames.indexOf(initialTabName) : 0);
12
+ const headerScrollDistance = headerHeight - minHeaderHeight;
13
+ const index = useSharedValue(initialIndex);
14
+ const indexDecimal = useSharedValue(initialIndex);
15
+ const focusedTab = useSharedValue(tabNames[initialIndex] ?? "");
16
+ const initialScrollY = useMemo(() => {
17
+ const m = {};
18
+ tabNames.forEach((n) => (m[n] = 0));
19
+ return m;
20
+ }, [tabNames]);
21
+ const scrollY = useSharedValue(initialScrollY);
22
+ const headerTranslateY = useSharedValue(0);
23
+ useAnimatedReaction(() => {
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
+ }
37
+ }, [headerScrollDistance]);
38
+ const refMap = useRef({}).current;
39
+ const setRef = useCallback((key, ref) => {
40
+ refMap[key] = ref;
41
+ return ref;
42
+ }, [refMap]);
43
+ const containerRef = useRef(null);
44
+ const onTabPress = useCallback((name) => {
45
+ const i = tabNames.indexOf(name);
46
+ if (i < 0)
47
+ return;
48
+ containerRef.current?.setPage(i);
49
+ }, [tabNames]);
50
+ const [renderIndex, setRenderIndex] = useState(initialIndex);
51
+ const { doDependenciesDiffer } = useHandler({});
52
+ const pageScrollHandler = useEvent((e) => {
53
+ "worklet";
54
+ indexDecimal.value = e.position + e.offset;
55
+ }, ["onPageScroll"], doDependenciesDiffer);
56
+ const onPageSelected = useCallback((e) => {
57
+ const i = e.nativeEvent.position;
58
+ index.value = i;
59
+ focusedTab.value = tabNames[i] ?? "";
60
+ setRenderIndex(i);
61
+ onIndexChange?.(i);
62
+ }, [tabNames, index, focusedTab, onIndexChange]);
63
+ const headerAnimStyle = useAnimatedStyle(() => ({
64
+ transform: [{ translateY: headerTranslateY.value }],
65
+ }));
66
+ const ctxValue = {
67
+ headerHeight,
68
+ tabBarHeight,
69
+ minHeaderHeight,
70
+ headerScrollDistance,
71
+ tabNames,
72
+ index,
73
+ indexDecimal,
74
+ focusedTab,
75
+ scrollY,
76
+ headerTranslateY,
77
+ setRef,
78
+ refMap,
79
+ containerRef,
80
+ };
81
+ const headerProps = {
82
+ tabNames,
83
+ focusedTab,
84
+ index,
85
+ indexDecimal,
86
+ onTabPress,
87
+ };
88
+ return (<Context.Provider value={ctxValue}>
89
+ <View style={[styles.container, containerStyle]}>
90
+ <AnimatedPagerView ref={containerRef} style={StyleSheet.absoluteFill} initialPage={initialIndex} onPageScroll={pageScrollHandler} onPageSelected={onPageSelected} {...pagerProps}>
91
+ {tabs.map((tab, i) => (<View key={tab.props.name} style={styles.page} collapsable={false}>
92
+ {tab}
93
+ </View>))}
94
+ </AnimatedPagerView>
95
+
96
+ <Animated.View pointerEvents="box-none" style={[
97
+ styles.headerContainer,
98
+ { height: headerHeight + tabBarHeight },
99
+ headerContainerStyle,
100
+ headerAnimStyle,
101
+ ]}>
102
+ <View pointerEvents="box-none" style={{ height: headerHeight }}>
103
+ {renderHeader?.(headerProps)}
104
+ </View>
105
+ <View style={{ height: tabBarHeight }}>
106
+ {renderTabBar
107
+ ? renderTabBar(headerProps)
108
+ : <DefaultTabBar {...headerProps}/>}
109
+ </View>
110
+ </Animated.View>
111
+ </View>
112
+ </Context.Provider>);
4
113
  };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CollapseTabs = void 0;
7
- const react_1 = __importDefault(require("react"));
8
- const react_native_1 = require("react-native");
9
- const CollapseTabs = ({ children, style, }) => {
10
- return <react_native_1.View> {children}</react_native_1.View>;
11
- };
12
- exports.CollapseTabs = CollapseTabs;
114
+ const styles = StyleSheet.create({
115
+ container: { flex: 1, overflow: "hidden" },
116
+ page: { flex: 1 },
117
+ headerContainer: {
118
+ position: "absolute",
119
+ top: 0,
120
+ left: 0,
121
+ right: 0,
122
+ zIndex: 10,
123
+ backgroundColor: "#fff",
124
+ },
125
+ });
@@ -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,23 @@
1
+ import React, { useEffect } from "react";
2
+ import Animated, { useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
3
+ import { useTabsContext } from "./hooks";
4
+ export function FlatList({ name, contentContainerStyle, ...rest }) {
5
+ const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = useTabsContext();
6
+ const ref = useAnimatedRef();
7
+ useEffect(() => {
8
+ setRef(name, ref);
9
+ }, [name, ref, setRef]);
10
+ const handler = useAnimatedScrollHandler({
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;
17
+ },
18
+ });
19
+ return (<Animated.FlatList {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
20
+ { paddingTop: headerHeight + tabBarHeight },
21
+ contentContainerStyle,
22
+ ]}/>);
23
+ }
@@ -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,25 @@
1
+ import React, { useEffect } from "react";
2
+ import Animated, { useAnimatedRef, useAnimatedScrollHandler, } from "react-native-reanimated";
3
+ import { useTabsContext } from "./hooks";
4
+ export function ScrollView({ name, contentContainerStyle, children, ...rest }) {
5
+ const { headerHeight, tabBarHeight, scrollY, focusedTab, setRef, } = useTabsContext();
6
+ const ref = useAnimatedRef();
7
+ useEffect(() => {
8
+ setRef(name, ref);
9
+ }, [name, ref, setRef]);
10
+ const handler = useAnimatedScrollHandler({
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;
17
+ },
18
+ });
19
+ return (<Animated.ScrollView {...rest} ref={ref} onScroll={handler} scrollEventThrottle={16} contentContainerStyle={[
20
+ { paddingTop: headerHeight + tabBarHeight },
21
+ contentContainerStyle,
22
+ ]}>
23
+ {children}
24
+ </Animated.ScrollView>);
25
+ }
package/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/Tab.js ADDED
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
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 const Tab = ({ children }) => {
8
+ return <View style={styles.container}>{children}</View>;
9
+ };
10
+ const styles = StyleSheet.create({
11
+ container: { flex: 1 },
12
+ });
@@ -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"}
package/lib/TabBar.js ADDED
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import { Pressable, StyleSheet, View } from "react-native";
3
+ import Animated, { interpolate, useAnimatedStyle, } from "react-native-reanimated";
4
+ export const DefaultTabBar = ({ tabNames, indexDecimal, onTabPress, }) => {
5
+ return (<View style={styles.container}>
6
+ {tabNames.map((name, i) => (<TabBarItem key={name} name={name} index={i} indexDecimal={indexDecimal} onPress={() => onTabPress(name)}/>))}
7
+ <Indicator count={tabNames.length} indexDecimal={indexDecimal}/>
8
+ </View>);
9
+ };
10
+ const TabBarItem = ({ name, index, indexDecimal, onPress }) => {
11
+ const labelStyle = useAnimatedStyle(() => ({
12
+ opacity: interpolate(indexDecimal.value, [index - 1, index, index + 1], [0.6, 1, 0.6], "clamp"),
13
+ }));
14
+ return (<Pressable style={styles.item} onPress={onPress}>
15
+ <Animated.Text style={[styles.label, labelStyle]}>{name}</Animated.Text>
16
+ </Pressable>);
17
+ };
18
+ const Indicator = ({ count, indexDecimal }) => {
19
+ const style = useAnimatedStyle(() => ({
20
+ transform: [
21
+ { translateX: (indexDecimal.value * 100) / count + "%" },
22
+ ],
23
+ width: `${100 / count}%`,
24
+ }));
25
+ return <Animated.View style={[styles.indicator, style]}/>;
26
+ };
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ flexDirection: "row",
30
+ backgroundColor: "#fff",
31
+ },
32
+ item: {
33
+ flex: 1,
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ },
37
+ label: {
38
+ fontSize: 14,
39
+ fontWeight: "500",
40
+ color: "#333",
41
+ },
42
+ indicator: {
43
+ position: "absolute",
44
+ bottom: 0,
45
+ left: 0,
46
+ height: 2,
47
+ backgroundColor: "#2D8CFF",
48
+ },
49
+ });
@@ -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,2 @@
1
+ import React from "react";
2
+ export const Context = React.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,8 @@
1
+ import { useContext } from "react";
2
+ import { Context } from "../context";
3
+ export function useTabsContext() {
4
+ const c = useContext(Context);
5
+ if (!c)
6
+ throw new Error("useTabsContext must be used inside <CollapseTabs>");
7
+ return c;
8
+ }
package/lib/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- export { CollapseTabs } from './CollapseTabs';
2
- export type { CollapseTabsProps } from './CollapseTabs';
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";
3
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
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/index.js CHANGED
@@ -1,5 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CollapseTabs = void 0;
4
- var CollapseTabs_1 = require("./CollapseTabs");
5
- Object.defineProperty(exports, "CollapseTabs", { enumerable: true, get: function () { return CollapseTabs_1.CollapseTabs; } });
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";
@@ -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,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"}