Skip to content

Instantly share code, notes, and snippets.

@bang9
Created March 4, 2023 14:48
Show Gist options
  • Save bang9/05bc687d3133ddd90778d0a2397ab912 to your computer and use it in GitHub Desktop.
Save bang9/05bc687d3133ddd90778d0a2397ab912 to your computer and use it in GitHub Desktop.
Simple tab bar
import React from "react";
import { TabBarProvider } from "./tab-bar.context";
import TabIndicator from "./tab.indicator";
import TabView from "./tab.view";
import TabLabels from "./tab.labels";
import { StyleSheet, View } from "react-native";
import Styles from "../../libs/lib.styles";
type Props = {
labels: string[];
};
const TabBar: React.FC<Props> = ({ labels, children }) => {
return (
<TabBarProvider>
<View style={styles.labelContainer}>
<TabLabels labels={labels} />
<TabIndicator tabLength={React.Children.count(children)} />
</View>
<TabView>{children}</TabView>
</TabBarProvider>
);
};
const styles = StyleSheet.create({
labelContainer: {
borderColor: Styles.color.gray2,
borderBottomWidth: StyleSheet.hairlineWidth
}
});
export default TabBar;
import React, {useContext, useRef, useState} from "react";
import { Animated, ScrollView } from "react-native";
import Styles from "../../libs/lib.styles";
type Context = {
scrollAnimation: Animated.Value;
scrollRef: React.RefObject<ScrollView>;
tabWidth: number;
setTabWidth: React.Dispatch<React.SetStateAction<number>>;
goTo: (index: number) => void;
};
export const TabBarContext = React.createContext<Context>({
scrollRef: React.createRef<ScrollView>(),
scrollAnimation: new Animated.Value(0),
tabWidth: 0,
setTabWidth: () => {},
goTo: () => {}
});
export const TabBarProvider: React.FC = ({ children }) => {
const scrollAnimation = useRef(new Animated.Value(0)).current;
const scrollRef = useRef<ScrollView>(null);
const [tabWidth, setTabWidth] = useState<Context["tabWidth"]>(0);
const goTo = (index: number) => {
scrollRef.current?.scrollTo({ x: Styles.layout.deviceWidth * index });
};
return (
<TabBarContext.Provider value={{ tabWidth, setTabWidth, scrollRef, scrollAnimation, goTo }}>
{children}
</TabBarContext.Provider>
);
};
export const useTabBar = () => {
return useContext(TabBarContext);
};
import React from "react";
import Styles from "../../libs/lib.styles";
import { Animated } from "react-native";
import { useTabBar } from "./tab-bar.context";
type Props = {
tabLength: number;
};
const TabIndicator: React.FC<Props> = ({ tabLength }) => {
const { scrollAnimation, tabWidth } = useTabBar();
const indicatorPosition = scrollAnimation.interpolate({
inputRange: [0, Styles.layout.deviceWidth],
outputRange: [0, Styles.layout.deviceWidth / tabLength]
});
return (
<Animated.View
style={{
backgroundColor: "black",
width: tabWidth,
height: 2,
transform: [{ translateX: indicatorPosition }]
}}
/>
);
};
export default TabIndicator;
import React from "react";
import { Pressable, Text, View } from "react-native";
import Styles from "../../libs/lib.styles";
import { useTabBar } from "./tab-bar.context";
type Props = {
labels: string[];
};
const TabLabels: React.FC<Props> = ({ labels }) => {
const { tabWidth, goTo } = useTabBar();
return (
<View style={{ flexDirection: "row" }}>
{labels.map((label, index) => {
return (
<Pressable
key={index}
onPress={() => goTo(index)}
style={{
width: tabWidth,
height: Styles.layout.tabHeight,
alignItems: "center",
justifyContent: "center"
}}
>
<Text style={{ fontSize: Styles.font.medium }}>{label}</Text>
</Pressable>
);
})}
</View>
);
};
export default TabLabels;
import React, { useLayoutEffect } from "react";
import { Animated, StyleSheet, View } from "react-native";
import Styles from "../../libs/lib.styles";
import { useTabBar } from "./tab-bar.context";
const TabView: React.FC = ({ children }) => {
const { scrollAnimation, scrollRef, setTabWidth } = useTabBar();
useLayoutEffect(() => {
setTabWidth(Styles.layout.deviceWidth / React.Children.count(children));
}, []);
return (
<Animated.ScrollView
horizontal
ref={scrollRef}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollAnimation } } }], {
useNativeDriver: true
})}
snapToAlignment={"start"}
decelerationRate={"fast"}
snapToInterval={Styles.layout.deviceWidth}
showsHorizontalScrollIndicator={false}
style={styles.scrollView}
>
{React.Children.map(children, child => {
return <View style={styles.view}>{child}</View>;
})}
</Animated.ScrollView>
);
};
const styles = StyleSheet.create({
scrollView: {
height: "100%"
},
view: {
width: Styles.layout.deviceWidth
}
});
export default TabView;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment