import { PropsWithChildren, ReactElement, useState } from "react";
import {
View,
Text,
Image,
ViewStyle,
StyleSheet,
ScrollView,
TouchableWithoutFeedback,
ImageStyle,
} from "react-native";
interface Tab {
icon?: string;
label?: string;
value?: string | number;
}
type Props = PropsWithChildren<{
tabs: Array<string> | Array<Tab>;
css?: Array<ViewStyle>;
iconCss?: Array<ImageStyle>;
labelCss?: Array<ViewStyle>;
listCss?: Array<ViewStyle>;
lineCss?: Array<ViewStyle>;
active?: number;
onChange?: (value: number) => void;
children: Array<ReactElement>;
}>;
const childrenLoaded: boolean[] = [];
export function TabNav({
tabs,
css,
iconCss,
labelCss,
listCss,
lineCss,
active,
onChange,
children,
}: Props) {
const [activeIndex, setActiveIndex] = useState<number>(active ? active : 0);
if (childrenLoaded.length == 0 && children?.length) {
for (let i = 0; i < children.length; i++) childrenLoaded[i] = active == i;
}
const setActive = (index: number) => {
setActiveIndex(index);
if (childrenLoaded[index] == false) childrenLoaded[index] = true;
if (onChange) onChange(index);
};
return (
<View style={{ width: "100%", display: "flex", flexDirection: "column" }}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={[{ width: "100%", minHeight: 45 }, ...(css || [])]}
contentContainerStyle={{ width: "100%", height: "100%" }}>
<View
style={[
{
height: "100%",
minWidth: "100%",
display: "flex",
flexDirection: "row",
flexWrap: "nowrap",
},
...(listCss || []),
]}>
{tabs.map((item, index) => (
<TouchableWithoutFeedback
key={index}
onPress={() => setActive(index)}>
<View
style={{
height: "100%",
padding: 15,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}>
{}
{typeof item != "string" && item.icon && (
<Image
style={[{ width: 45, height: 45 }, ...(iconCss || [])]}
source={item.icon || require("@/assets/images/logo.png")}
/>
)}
{}
{(typeof item == "string" ||
(typeof item != "string" && item.label)) && (
<Text
style={[
{ fontWeight: "bold" },
...(labelCss || []),
activeIndex == index ? styles.active : null,
]}>
{typeof item == "string" ? item : (item as Tab).label}
</Text>
)}
{}
<View
style={[
styles.line,
{ opacity: activeIndex == index ? 1 : 0 },
...(lineCss || []),
]}
/>
</View>
</TouchableWithoutFeedback>
))}
</View>
</ScrollView>
{}
{children
? children.map((child, i) => {
return (
<View
style={{ display: i == activeIndex ? "flex" : "none" }}
key={i}>
{childrenLoaded[i] ? child : null}
</View>
);
})
: null}
</View>
);
}
const styles = StyleSheet.create({
active: {
color: "green",
},
line: {
height: 2,
width: 20,
backgroundColor: "green",
marginTop: 8,
},
});
使用举例
<TabNav tabs={["00", "11", "22"]}>
{}
<View>
<Text>0</Text>
</View>
{}
<View>
<Text>1</Text>
</View>
{}
<View>
<Text>2</Text>
</View>
</TabNav>
TabNavItem 可以是自定义组件,注意:如果是自定义组件,切换到对应的组件才会触发组件内部所有逻辑,且第二次切换后会直接使用缓存渲染(不再重新初始化组件);TabNavItem 需要对应 tabs 数组