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(exoflex): simple tab component without lazy load #376

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5e834b1
feat(exoflex): simplest tab component
oshimayoan Feb 13, 2020
b15a5a8
build(exoflex): install react-native-reanimated
oshimayoan Feb 13, 2020
fbf8b37
feat(exoflex): supports tab indicator animation for native only
oshimayoan Feb 13, 2020
827fe80
refactor(exoflex): change TabView props implementation
oshimayoan Feb 14, 2020
fa122b2
feat(exoflex): show scene with no lazy load and change scene using tab
oshimayoan Feb 17, 2020
e7c3fc8
feat(exoflex): custom hooks for Tab
oshimayoan Feb 17, 2020
9eab84b
feat(exoflex): change tab content using swipe
oshimayoan Feb 17, 2020
6a34a65
feat(exoflex): lazyload tab
oshimayoan Feb 17, 2020
1c38392
fix(exoflex): tab content styling fix
oshimayoan Feb 18, 2020
bd6ed0b
fix(exoflex): change changeTabIndex to jumpTo
oshimayoan Feb 18, 2020
f42034e
feat(exoflex): prop to toggle swipe functionality
oshimayoan Feb 18, 2020
458bfef
feat(exoflex): custom tab bar styling
oshimayoan Feb 18, 2020
5fd6cfd
feat(exoflex): custom tab indicator styling
oshimayoan Feb 18, 2020
cb82538
fix(exoflex): rename useTabSwipe to useTab
oshimayoan Feb 18, 2020
efb2a80
fix(exoflex): disable swipe feature on web
oshimayoan Feb 18, 2020
d87e2b6
fix(exoflex): remove unused prop from TabBarItem
oshimayoan Feb 18, 2020
cedf836
feat(exoflex): web and native accessibility
oshimayoan Feb 18, 2020
8a2e6bd
fix(exoflex): styling on web
oshimayoan Feb 18, 2020
5b88213
refactor(exoflex): rewrite tab content using gesture and reanimated
oshimayoan Feb 19, 2020
f409610
fix(exoflex): move TabContent logic to useTab hooks
oshimayoan Mar 4, 2020
72486d7
test(exoflexx): mock RNGH and react-native-reanimated
oshimayoan Mar 6, 2020
9de6e2a
test(exoflex): tabview without lazy load
oshimayoan Mar 6, 2020
af8969f
chore(exoflex): uninstall react-native-tab-view
oshimayoan Mar 6, 2020
14de536
chore(exoflex): tab view example
oshimayoan Mar 6, 2020
49df71e
fix(exofle): fix web
oshimayoan Mar 9, 2020
9be1e67
fix(exoflex): fix after rebase
oshimayoan Mar 17, 2020
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
2 changes: 2 additions & 0 deletions packages/exoflex/example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ let extraNodeModules = {};
'react-native',
'react',
'react-native-svg',
'react-native-reanimated',
'react-native-gesture-handler',
'expo-font',
'@babel/runtime',
...dependencies,
Expand Down
91 changes: 91 additions & 0 deletions packages/exoflex/example/src/examples/TabExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from 'react';
import { StyleSheet, View, Image, ScrollView } from 'react-native';
import { TabView, Button } from 'exoflex';

const imageUri =
'https://images.unsplash.com/photo-1581963857936-fae00e457fb3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80';

let images = [];
for (let i = 0; i < 100; i++) {
images.push(
<Image
key={i.toString()}
source={{ uri: imageUri }}
style={{ width: '100%', height: 200, marginVertical: 10 }}
/>,
);
}

const FirstRoute = () => (
<ScrollView
contentContainerStyle={[
{
backgroundColor: '#ff4081',
padding: 10,
paddingHorizontal: 20,
},
]}
>
{images}
</ScrollView>
);

const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);

const ThirdRoute = (props: {jumpTo: (index: number) => void}) => {
return (
<View style={[styles.scene, { backgroundColor: 'skyblue' }]}>
<Button onPress={() => props.jumpTo(0)} style={{ marginTop: 100 }}>
Jump to Tab 1
</Button>
</View>
);
};

const FourthRoute = () => (
<View style={[styles.scene, { backgroundColor: 'green' }]} />
);

const FifthRoute = () => (
<View style={[styles.scene, { backgroundColor: 'goldenrod' }]} />
);


function TabExample() {
const [index, setIndex] = useState(0);

let scenes = [
{ title: 'First', scene: FirstRoute },
{ title: 'Second', scene: SecondRoute },
{ title: 'Third', scene: ThirdRoute },
{ title: 'Fourth', scene: FourthRoute },
{ title: 'Fifth', scene: FifthRoute },
];

return (
<TabView
lazyLoad={false}
enableSwipe
activeIndex={index}
scenes={scenes}
onIndexChange={setIndex}
// NOTE: Intentionally left these commented for easier development purpose
// tabItemStyle={{ backgroundColor: 'white' }}
// tabItemTextStyle={{ color: 'red' }}
// tabIndicatorStyle={{ backgroundColor: 'brown' }}
// style={{ backgroundColor: 'white' }}
/>
);
}

TabExample.title = 'Tab';

let styles = StyleSheet.create({
scene: {
flex: 1,
},
});

export default TabExample;
2 changes: 2 additions & 0 deletions packages/exoflex/example/src/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import RadioButtonGroupExample from './RadioButtonGroupExample';
import RichRadioButtonExample from './RichRadioButtonExample';
import SegmentedControlExample from './SegmentedControlExample';
import SwitchExample from './SwitchExample';
import TabExample from './TabExample';
import TextInputExample from './TextInputExample';
import ToastExample from './ToastExample';
import TypographyExample from './TypographyExample';
Expand Down Expand Up @@ -54,6 +55,7 @@ export let EXAMPLES = {
segmentedcontrol: SegmentedControlExample,
slider: SliderExample,
switch: SwitchExample,
tab: TabExample,
textinput: TextInputExample,
toast: ToastExample,
typography: TypographyExample,
Expand Down
2 changes: 1 addition & 1 deletion packages/exoflex/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
preset: './jestPresets/jest-preset',
modulePathIgnorePatterns: ['<rootDir>/lib/', '<rootDir>/example/'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|react-native-paper|react-native-safe-area-view|react-native-animation-hooks|react-native-svg|react-native-calendars|react-native-multi-slider|react-native-collapsible|react-native-modal-datetime-picker|react-native-vector-icons)/)',
'node_modules/(?!(react-native|react-native-paper|react-native-safe-area-view|react-native-animation-hooks|react-native-svg|react-native-calendars|react-native-multi-slider|react-native-collapsible|react-native-modal-datetime-picker|react-native-vector-icons|react-native-reanimated|react-native-gesture-handler)/)',
],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|css|styl)$':
Expand Down
4 changes: 4 additions & 0 deletions packages/exoflex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"expo-font": "*",
"react": "^16.8.0",
"react-native": "^0.59.0 || ^0.60.0 || ^0.61.0",
"react-native-gesture-handler": ">=1.1.0",
"react-native-reanimated": ">=1.4.0",
"react-native-svg": ">=9.3.3"
},
"dependencies": {
Expand Down Expand Up @@ -63,6 +65,8 @@
"react": "16.12.0",
"react-dom": "16.12.0",
"react-native": "0.61.5",
"react-native-gesture-handler": "1.5.6",
"react-native-reanimated": "1.7.0",
"react-native-svg": "9.13.5",
"react-native-testing-library": "1.12.0",
"react-native-vector-icons": "6.6.0",
Expand Down
65 changes: 65 additions & 0 deletions packages/exoflex/src/components/Tab/TabBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react';
import {
View,
StyleSheet,
ViewStyle,
StyleProp,
TextStyle,
} from 'react-native';
import TabItem from './TabBarItem';
import TabIndicator from './TabIndicator';

type TabBarProps = {
activeIndex: number;
titles: Array<string>;
onTabPress: (index: number) => void;
style?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
indicatorStyle?: StyleProp<ViewStyle>;
};

export default function TabBar(props: TabBarProps) {
let {
activeIndex,
titles,
onTabPress,
style,
textStyle,
indicatorStyle,
} = props;

let [width, setWidth] = useState(0);

let indicatorWidth = width / titles.length;

return (
<View
accessibilityRole="tablist"
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
style={styles.tabBar}
>
<TabIndicator
width={indicatorWidth}
activeIndex={activeIndex}
maxIndex={titles.length}
style={indicatorStyle}
/>
{titles.map((title, index) => (
<TabItem
key={index}
title={title}
onPress={() => onTabPress(index)}
style={style}
textStyle={textStyle}
/>
))}
</View>
);
}

const styles = StyleSheet.create({
tabBar: {
height: 50,
flexDirection: 'row',
},
});
56 changes: 56 additions & 0 deletions packages/exoflex/src/components/Tab/TabBarItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import {
TouchableOpacity,
StyleSheet,
StyleProp,
TextStyle,
ViewStyle,
} from 'react-native';
import color from 'color';
import Text from '../Text';

import useTheme from '../../helpers/useTheme';

type TabBarItemProps = {
title: string;
onPress: () => void;
style?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
};

export default function TabBarItem(props: TabBarItemProps) {
let { onPress, title, style, textStyle } = props;
let { colors } = useTheme();

let customStyle = StyleSheet.flatten(style);
let backgroundColor = customStyle?.backgroundColor || colors.primary;

let activeOpacity = color(backgroundColor).isLight() ? 0.5 : 0.8;

let textColorStyle = {
color: color(backgroundColor).isLight() ? colors.text : colors.surface,
} as TextStyle;

return (
<TouchableOpacity
accessible
accessibilityLabel={`Tab ${title}`}
accessibilityHint={`Change the screen to ${title}`}
accessibilityRole="tab"
activeOpacity={activeOpacity}
onPress={onPress}
style={[styles.item, { backgroundColor }, style]}
>
<Text style={[textColorStyle, textStyle]}>{title}</Text>
</TouchableOpacity>
);
}

const styles = StyleSheet.create({
item: {
flex: 1,
padding: 10,
justifyContent: 'center',
alignItems: 'center',
},
});
85 changes: 85 additions & 0 deletions packages/exoflex/src/components/Tab/TabContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState, useEffect } from 'react';
import { View, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native';
import Animated from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';

import { useTab } from './useTab';

import { TabScenes } from './types';
import { IS_WEB } from '../../constants/platforms';

type TabContentProps = {
lazyLoad: boolean;
enableSwipe: boolean;
activeIndex: number;
scenes: TabScenes;
onIndexChange: (index: number) => void;
};

export default function TabContent(props: TabContentProps) {
let {
scenes,
// lazyLoad = false,
enableSwipe = true,
activeIndex,
onIndexChange,
} = props;

let [width, setWidth] = useState(0);
let [x, setX] = useState(new Animated.Value<number>(0));

let totalScene = scenes.length;

let { transformStyle, onPanGestureEvent, onHandlerStateChange } = useTab({
x,
width,
activeIndex,
onIndexChange,
totalScene,
});

useEffect(() => {
let newLeft = new Animated.Value(width * activeIndex * -1);
setX(newLeft);
}, [width, activeIndex]);

let onLayout = (e: LayoutChangeEvent) => setWidth(e.nativeEvent.layout.width);

return (
<View onLayout={onLayout} style={styles.root}>
<PanGestureHandler
enabled={IS_WEB ? false : enableSwipe}
minDist={50}
onGestureEvent={onPanGestureEvent}
onHandlerStateChange={onHandlerStateChange}
>
<Animated.View
style={
[
styles.container,
transformStyle,
IS_WEB && { left: x },
] as ViewStyle
}
>
{scenes.map(({ scene, title }) => (
<View style={{ flex: 1 }} key={title}>
{React.createElement(scene, { jumpTo: onIndexChange })}
</View>
))}
</Animated.View>
</PanGestureHandler>
</View>
);
}

const styles = StyleSheet.create({
root: {
flex: 1,
overflow: 'hidden',
},
container: {
flexDirection: 'row',
height: '100%',
},
});
Loading