Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: forward ref for flatlist and scrollview #99

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
11aa73c
refactor: polish the api a bit
andreialecu Feb 3, 2021
99189dc
refactor: remove containerRef and refMap
andreialecu Feb 3, 2021
c01df8f
fixup! refactor: remove containerRef and refMap
andreialecu Feb 3, 2021
9d288b0
fixup! refactor: remove containerRef and refMap
andreialecu Feb 3, 2021
aee6f2e
refactor: use add <Tab> component
andreialecu Feb 3, 2021
3ec551d
fixup! refactor: use add <Tab> component
andreialecu Feb 3, 2021
6bbecf0
fixup! refactor: use add <Tab> component
PedroBern Feb 3, 2021
4caa10b
fixup! refactor: use add <Tab> component
PedroBern Feb 3, 2021
364494b
refactor: misc refactoring and fixes
andreialecu Feb 4, 2021
2884bba
feat: dynamic tabs
andreialecu Feb 4, 2021
208b42f
Merge branch 'main' of github.com:PedroBern/react-native-collapsible-…
andreialecu Feb 4, 2021
233d598
refactor: work around some typing issues
andreialecu Feb 4, 2021
264cf00
fix: attempt at fixing lazy sometimes opening with 0 opacity
andreialecu Feb 4, 2021
b0cdcff
fix: minor tabbar tweaks
andreialecu Feb 4, 2021
f6a2f69
fix: infinite loop
andreialecu Feb 4, 2021
29568ef
refactor: handle window resize separately
PedroBern Feb 4, 2021
b383af9
fix: lazy
andreialecu Feb 4, 2021
c42f0db
Merge branch 'refactor-apicleanup3' of github.com:andreialecu/react-n…
andreialecu Feb 4, 2021
7379635
refactor: simplify dynamic refs
andreialecu Feb 4, 2021
b92a772
Merge branch 'main' into refactor-apicleanup3
PedroBern Feb 4, 2021
622bc40
Merge branch 'main' into refactor-apicleanup3
PedroBern Feb 4, 2021
5fc5cb8
refactor: fix TabBar and TabItem types
PedroBern Feb 5, 2021
d9c27a5
fix: bug when removing dynamic tab
andreialecu Feb 5, 2021
6cfdaf4
refactor: better handling of null context
andreialecu Feb 5, 2021
eeb94cc
fix: memoize tabbar in example so it doesn't flicker
andreialecu Feb 5, 2021
f4ebdfe
feat: dynamic tabbar
andreialecu Feb 5, 2021
00977b3
fixup! feat: dynamic tabbar
andreialecu Feb 5, 2021
8cfc596
fix: opacity stuck as 0 on lazy tab (sometimes)
andreialecu Feb 5, 2021
1bea47b
docs: add shuffle tabs button
andreialecu Feb 5, 2021
fb3b1b2
fix: add null check
andreialecu Feb 5, 2021
b0fbcb7
docs: more complex dynamic tabs example
andreialecu Feb 5, 2021
ec513cd
fix: rework refs to fix scroll sync
andreialecu Feb 5, 2021
4c9cfce
fix: sync scroll position on dynamic tabs
andreialecu Feb 5, 2021
72b4e54
fix: handle ios overscroll
PedroBern Feb 5, 2021
63fa2dc
fix: cancel snapping animations on drag
andreialecu Feb 5, 2021
fc97773
fixup! docs: more complex dynamic tabs example
PedroBern Feb 5, 2021
9fa6958
fix: navigation bar zindex on ios
andreialecu Feb 5, 2021
5f7826d
refactor: clamp scroll on ios to scrollview bounds
andreialecu Feb 5, 2021
0e4c8c7
chore: fix docs script
PedroBern Feb 5, 2021
2fdd9fa
docs: update readme
PedroBern Feb 5, 2021
6284a69
refactor: remove unused code
PedroBern Feb 5, 2021
49e972a
docs: update documentation
PedroBern Feb 5, 2021
bf79c36
chore: allow to override props in the docs
PedroBern Feb 6, 2021
2b9c462
refactor: add undescore to internal context variables and improve docs
PedroBern Feb 6, 2021
2f88ef5
refactor: remove createCollapsibleTabs
andreialecu Feb 6, 2021
393f746
docs: inline quickstart example (makes it clearer nesting is supported)
andreialecu Feb 6, 2021
84afde8
fixup! docs: inline quickstart example (makes it clearer nesting is s…
andreialecu Feb 6, 2021
f9b546a
feat: forward ref for flatlist and scrollview
andreialecu Feb 6, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ module.exports = {
// 'react-native/no-color-literals': 'warn',
// 'react-native/no-raw-text': 'warn',
// 'react-native/no-single-element-style-arrays': 'warn',
'import/no-default-export': 'error',
},
plugins: ['react-native-globals', 'react-native'],
extends: 'universe/native',
extends: ['universe/native'],
env: {
'react-native-globals/all': true,
},
Expand Down
182 changes: 74 additions & 108 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ Then, add Reanimated v2, [follow the official installation guide](https://docs.s
```tsx
import React from 'react'
import { View, StyleSheet, ListRenderItem } from 'react-native'
import {
RefComponent,
ContainerRef,
createCollapsibleTabs,
} from 'react-native-collapsible-tab-view'
import { useAnimatedRef } from 'react-native-reanimated'
import { createCollapsibleTabs } from 'react-native-collapsible-tab-view'

type TabNames = 'A' | 'B'

Expand All @@ -94,24 +89,17 @@ const { useTabsContext, ...Tabs } = createCollapsibleTabs<TabNames>()
const HEADER_HEIGHT = 250

const Example: React.FC = () => {
const containerRef = useAnimatedRef<ContainerRef>()
const tabARef = useAnimatedRef<RefComponent>()
const tabBRef = useAnimatedRef<RefComponent>()

const [refMap] = React.useState({
A: tabARef,
B: tabBRef,
})

return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={Header}
headerHeight={HEADER_HEIGHT} // optional
refMap={refMap}
>
<ScreenA />
<ScreenB />
<Tabs.Tab name="A">
<ScreenA />
</Tabs.Tab>
<Tabs.Tab name="B">
<ScreenB />
</Tabs.Tab>
</Tabs.Container>
)
}
Expand Down Expand Up @@ -217,35 +205,21 @@ use like this:
Basic usage looks like this:

```tsx
import {
RefComponent,
ContainerRef,
TabBarProps,
} from 'react-native-collapsible-tab-view'
import { useAnimatedRef } from 'react-native-reanimated'

type MyTabs = 'article' | 'contacts' | 'albums'

const MyHeader: React.FC<TabBarProps<MyTabs>> = (props) => {...}
import { createCollapsibleTabs } from 'react-native-collapsible-tab-view'

const Example: React.FC<Props> = () => {
const containerRef = useAnimatedRef<ContainerRef>()
const tab0Ref = useAnimatedRef<RefComponent>()
const tab1Ref = useAnimatedRef<RefComponent>()
type TabNames = 'A' | 'B'

const [refMap] = React.useState({
tab0: tab0Ref,
tab1: tab1Ref,
})
const Tabs = createCollapsibleTabs<TabNames>()

const Example = () => {
return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={MyHeader}
headerHeight={HEADER_HEIGHT} // optional
refMap={refMap}
>
{ components returning Tabs.ScrollView || Tabs.FlatList }
<Tabs.Container HeaderComponent={MyHeader}>
<Tabs.Tab name="A">
<ScreenA />
</Tabs.Tab>
<Tabs.Tab name="B">
<ScreenB />
</Tabs.Tab>
</Tabs.Container>
)
}
Expand All @@ -255,26 +229,47 @@ const Example: React.FC<Props> = () => {

|name|type|default|description|
|:----:|:----:|:----:|:----:|
|HeaderComponent|`((props: TabBarProps<string>) => ReactElement<any, string \| ((props: any) => ReactElement<any, any> \| null) \| (new (props: any) => Component<any, any, any>)>) \| undefined`|||
|TabBarComponent|`((props: TabBarProps<string>) => ReactElement<any, string \| ((props: any) => ReactElement<any, any> \| null) \| (new (props: any) => Component<any, any, any>)>) \| undefined`|`null`||
|HeaderComponent|`((props: TabBarProps<T>) => React.ReactElement) \| null \| undefined`|||
|TabBarComponent|`((props: TabBarProps<T>) => React.ReactElement) \| null \| undefined`|`MaterialTabBar`||
|cancelLazyFadeIn|`boolean \| undefined`|||
|cancelTranslation|`boolean \| undefined`|||
|containerRef|`RefObject<ContainerRef>`|||
|containerStyle|`StyleProp<ViewStyle>`|||
|diffClampEnabled|`boolean \| undefined`|`false`||
|headerContainerStyle|`StyleProp<AnimateStyle<ViewStyle>>`|||
|headerHeight|`number \| undefined`||Is optional, but will optimize the first render.|
|initialTabName|`string \| undefined`|||
|lazy|`boolean \| undefined`||If lazy, will mount the screens only when the tab is visited. There is a default fade in transition.|
|minHeaderHeight|`number \| undefined`|`0`|Header minimum height when collapsed|
|onIndexChange|`OnTabChangeCallback<string> \| undefined`||Callback fired when the index changes. It receives the previous and current index and tabnames.|
|pagerProps|`Pick<FlatListProps<number>, "ItemSeparatorComponent" \| "ListEmptyComponent" \| "ListFooterComponent" \| "ListFooterComponentStyle" \| "ListHeaderComponent" \| ... 128 more ... \| "persistentScrollbar"> \| undefined`||Props passed to the horiztontal flatlist. If you want for example to disable swiping, you can pass `{ scrollEnabled: false }`|
|refMap|`Record<string, Ref>`|||
|onIndexChange|`(data: { prevIndex: number index: number prevTabName: T tabName: T }) => void`||Callback fired when the index changes. It receives the previous and current index and tabnames.|
|pagerProps|`Omit<FlatListProps<number>, 'data' \| 'keyExtractor' \| 'renderItem' \| 'horizontal' \| 'pagingEnabled' \| 'onScroll' \| 'showsHorizontalScrollIndicator' \| 'getItemLayout'>`||Props passed to the horiztontal flatlist. If you want for example to disable swiping, you can pass `{ scrollEnabled: false }`|
|snapEnabled|`boolean \| undefined`|`false`||
|snapThreshold|`number \| undefined`|`0.5`|Percentage of header height to make the snap effect. A number between 0 and 1.|
|tabBarHeight|`number \| undefined`||Is optional, but will optimize the first render.|


### Tabs.Tab

Wrap your screens with `Tabs.Tab`. Basic usage looks like this:

```tsx
<Tabs.Container ...>
<Tabs.Tab name="A" label="First Tab">
<ScreenA />
</Tabs.Tab>
<Tabs.Tab name="B">
<ScreenA />
</Tabs.Tab>
</Tabs.Container>
```

#### Props

|name|type|
|:----:|:----:|
|label|`string \| undefined`|
|name|`string`|


### Tabs.Lazy

Typically used internally, but if you want to mix lazy and regular screens you can wrap the lazy ones with this component.
Expand Down Expand Up @@ -309,28 +304,18 @@ const { focusedTab, ...rest } = useTabsContext()

|name|type|default|description|
|:----:|:----:|:----:|:----:|
|accDiffClamp|`SharedValue<number>`|||
|accScrollY|`SharedValue<number>`|||
|accDiffClamp|`SharedValue<number>`||DiffClamp value. It's the current visible header height if `diffClampEnabled={true}`.|
|containerHeight|`number \| undefined`|||
|diffClampEnabled|`boolean`|`false`||
|endDrag|`SharedValue<number>`||Used internally.|
|focusedTab|`SharedValue<string>`||Name of the current focused tab.|
|headerHeight|`number`|||
|headerScrollDistance|`SharedValue<number>`|||
|index|`SharedValue<number>`|||
|indexDecimal|`SharedValue<number>`|||
|isGliding|`SharedValue<boolean>`|||
|isScrolling|`SharedValue<number>`|||
|isSnapping|`SharedValue<boolean>`|||
|offset|`SharedValue<number>`|||
|oldAccScrollY|`SharedValue<number>`|||
|refMap|`Record<string, Ref>`|||
|scrollX|`SharedValue<number>`||Scroll x position of the tabs container.|
|scrollY|`SharedValue<number[]>`|||
|index|`SharedValue<number>`||Current index of the pager.|
|indexDecimal|`SharedValue<number>`||Index value, including decimal points. Use this to interpolate tab indicators.|
|refMap|`Record<ReactText, Ref<RefComponent>>`||Object containing the ref of each scrollable component.|
|scrollY|`SharedValue<number[]>`||Array of the scroll y position of each tab.|
|scrollYCurrent|`SharedValue<number>`||Scroll position of current tab.|
|snapEnabled|`boolean`|`false`||
|snapThreshold|`number`|`0.5`||
|snappingTo|`SharedValue<number>`||Used internally.|
|tabBarHeight|`number`|||
|tabNames|`SharedValue<string[]>`||Tab names, same as the keys of `refMap`.|

Expand All @@ -356,63 +341,44 @@ You can use this to get the progessViewOffset and pass to the refresh control of
### MaterialTabBar

Basic usage looks like this:

```tsx
import {
RefComponent,
ContainerRef,
TabBarProps,
} from 'react-native-collapsible-tab-view'
import { useAnimatedRef } from 'react-native-reanimated'
type MyTabs = 'article' | 'contacts' | 'albums'
const MyHeader: React.FC<TabBarProps<MyTabs>> = (props) => {...}
const Example: React.FC<Props> = () => {
const containerRef = useAnimatedRef<ContainerRef>();
const tab0Ref = useAnimatedRef<RefComponent>();
const tab1Ref = useAnimatedRef<RefComponent>();
const [refMap] = React.useState({
tab0: tab0Ref,
tab1: tab1Ref,
});
return (
<Tabs.Container
containerRef={containerRef}
HeaderComponent={MyHeader}
headerHeight={HEADER_HEIGHT} // optional
refMap={refMap}
TabBarComponent={(props) => (
<MaterialTabBar
{...props}
activeColor="red"
inactiveColor="yellow"
labelStyle={{ fontSize: 14 }}
/>
)}
>
{components returning Tabs.ScrollView || Tabs.FlatList}
</Tabs.Container>
);
};
<Tabs.Container
...
TabBarComponent={(props) => (
<MaterialTabBar
{...props}
activeColor="red"
inactiveColor="yellow"
inactiveOpacity={1}
labelStyle={{ fontSize: 14 }}
/>
)}
>
{...}
</Tabs.Container>
```

#### Props

|name|type|default|description|
|:----:|:----:|:----:|:----:|
|TabItemComponent|`((props: MaterialTabItemProps<any>) => ReactElement<any, string \| ((props: any) => ReactElement<any, any> \| null) \| (new (props: any) => Component<any, any, any>)>) \| undefined`|`null`|React component to render as tab bar item|
|TabItemComponent|`(props: MaterialTabItemProps<N>) => React.ReactElement`|`MaterialTabItem`|React component to render as tab bar item|
|activeColor|`string \| undefined`||Color applied to the label when active|
|containerRef|`RefObject<ContainerRef>`|||
|contentContainerStyle|`StyleProp<ViewStyle>`||Style to apply to the inner container for tabs|
|focusedTab|`SharedValue<any>`|||
|getLabelText|`((name: any) => string) \| undefined`|`(name) => name.toUpperCase()`|Function to compute the tab item label text|
|focusedTab|`SharedValue<T>`|||
|getLabelText|`((name: T) => string) \| undefined`|`(name) => String(name).toUpperCase()`|Function to compute the tab item label text|
|inactiveColor|`string \| undefined`||Color applied to the label when inactive|
|index|`SharedValue<number>`|||
|indexDecimal|`SharedValue<number>`|||
|indicatorStyle|`StyleProp<AnimateStyle<ViewStyle>>`||Style to apply to the active indicator.|
|labelStyle|`StyleProp<AnimateStyle<TextStyle>>`||Style to apply to the tab item label|
|onTabPress|`(name: any) => void`|||
|refMap|`Record<any, Ref>`|||
|onTabPress|`(name: T) => void`|||
|scrollEnabled|`boolean \| undefined`|`false`|Indicates whether the tab bar should contain horizontal scroll, when enabled the tab width is dynamic|
|style|`StyleProp<ViewStyle>`||Style to apply to the tab bar container.|
|tabNames|`T[]`|||
|tabProps|`TabsWithProps<T>`|||
|tabStyle|`StyleProp<ViewStyle>`||Style to apply to the individual tab items in the tab bar.|


Expand All @@ -431,13 +397,13 @@ Any additional props are passed to the pressable component.
|indexDecimal|`SharedValue<number>`|||
|label|`string`|||
|labelStyle|`StyleProp<AnimateStyle<TextStyle>>`||Style to apply to the tab item label|
|name|`any`|||
|name|`T`|||
|onLayout|`(((event: LayoutChangeEvent) => void) & ((event: LayoutChangeEvent) => void)) \| undefined`||Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}.|
|onPress|`(name: any) => void`|||
|onPress|`(name: T) => void`|||
|pressColor|`string \| undefined`|`#DDDDDD`||
|pressOpacity|`number \| undefined`|`Platform.OS === 'ios' ? 0.2 : 1`||
|scrollEnabled|`boolean \| undefined`|||
|style|`false \| ViewStyle \| RegisteredStyle<ViewStyle> \| RecursiveArray<false \| ViewStyle \| RegisteredStyle<ViewStyle> \| null \| undefined> \| ... 15 more ... \| undefined`||Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.|
|style|`StyleProp<ViewStyle>`||Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.|



Expand Down
51 changes: 47 additions & 4 deletions documentation/getCoreComponentsAPI.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { API, OverrideProps, Prop } from './types'

const fs = require('fs')
const path = require('path')
const docgen = require('react-docgen-typescript')

const generateMarkdown = require('./mdGenerator')
const { maybeOverrideProps } = require('./utils')

const options = {
savePropValueAsString: true,
propFilter: (prop: any, component: any) => {
propFilter: (prop: Prop, component: any) => {
if (
prop.parent ||
component.name === 'Tabs.FlatList' ||
component.name === 'Tabs.ScrollView'
component.name === 'Tabs.ScrollView' ||
prop.name.startsWith('_')
) {
return false
}
Expand All @@ -37,6 +41,39 @@ const paths = {
),
}

const overrideProps: OverrideProps = {
'Tabs.Container': {
HeaderComponent: {
type: {
name:
'((props: TabBarProps<T>) => React.ReactElement) | null | undefined',
},
defaultValue: null,
},
TabBarComponent: {
type: {
name:
'((props: TabBarProps<T>) => React.ReactElement) | null | undefined',
},
defaultValue: { value: 'MaterialTabBar' },
},
pagerProps: {
type: {
name:
"Omit<FlatListProps<number>, 'data' | 'keyExtractor' | 'renderItem' | 'horizontal' | 'pagingEnabled' | 'onScroll' | 'showsHorizontalScrollIndicator' | 'getItemLayout'>",
},
defaultValue: null,
},
onIndexChange: {
type: {
name:
'(data: { prevIndex: number index: number prevTabName: T tabName: T }) => void',
},
defaultValue: null,
},
},
}

// copy createCollapsibleTabs
const getCoreComponents = () => {
fs.copyFileSync(paths.createCollapsibleTabs, paths.createCollapsibleTabs_tmp)
Expand Down Expand Up @@ -65,7 +102,7 @@ const getCoreComponents = () => {

// export everything
data +=
'export { Container, Lazy, FlatList, ScrollView, UseTabsContext, UseCollapsibleStyle }'
'export { Container, Tab, Lazy, FlatList, ScrollView, UseTabsContext, UseCollapsibleStyle }'

return data
}
Expand All @@ -74,7 +111,13 @@ const getCoreComponentsAPI = () => {
const core = getCoreComponents()
fs.writeFileSync(paths.createCollapsibleTabs_tmp, core)
const core_docs = docs.parse(paths.createCollapsibleTabs_tmp)
const md = core_docs.map((c: string) => generateMarkdown(c)).join('\n')

const md = core_docs
.map((c: API) => {
return generateMarkdown(maybeOverrideProps(c, overrideProps))
})
.join('\n')

fs.unlink(paths.createCollapsibleTabs_tmp, (err: any) => {
if (err) throw err
})
Expand Down
Loading