react-native-ui-lib
Version:
<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a
113 lines (101 loc) • 3.9 kB
JavaScript
// TODO: support commented props
import React, { useMemo, useEffect, useState, useCallback } from 'react';
import _ from 'lodash';
import { useAnimatedReaction, useSharedValue, withTiming, runOnJS } from 'react-native-reanimated';
import { useOrientation } from "../../hooks";
import { Constants, asBaseComponent } from "../../commons/new";
import { LogService } from "../../services";
import TabBarContext from "./TabBarContext";
import TabBar from "./TabBar";
import TabBarItem, { TabControllerItemProps } from "./TabBarItem";
import TabPage from "./TabPage";
import PageCarousel from "./PageCarousel";
export { TabControllerItemProps }; // TODO: should migrate selectedIndex to initialIndex (and make this prop uncontrolled)
/**
* @description: A performant solution for a tab controller with lazy load mechanism
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/TabControllerScreen/index.tsx
* @notes: This component is based on react-native-gesture-handler
* @important: On Android, if using react-native-navigation, make sure to wrap your screen with gestureHandlerRootHOC
* @importantLink: https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html#with-wix-react-native-navigation-https-githubcom-wix-react-native-navigation
*/
function TabController({
initialIndex = 0,
selectedIndex,
asCarousel = false,
items,
onChangeIndex = _.noop,
carouselPageWidth,
children
}) {
const [screenWidth, setScreenWidth] = useState(Constants.windowWidth);
if (items?.length < 2) {
console.warn('TabController component expect a minimum of 2 items');
}
useOrientation({
onOrientationChange: () => {
setScreenWidth(Constants.windowWidth);
}
});
const pageWidth = useMemo(() => {
return carouselPageWidth || screenWidth;
}, [carouselPageWidth, screenWidth]);
const ignoredItems = useMemo(() => {
return _.filter(items, item => item.ignore);
}, [items]);
/* backwards compatibility for `selectedIndex` prop. this line eventually should be removed */
initialIndex = selectedIndex || initialIndex;
/* currentPage - static page index */
const currentPage = useSharedValue(initialIndex);
/* targetPage - transitioned page index (can be a fraction when transitioning between pages) */
const targetPage = useSharedValue(initialIndex); // const carouselOffset = useSharedValue(initialIndex * Math.round(pageWidth));
const setCurrentIndex = useCallback(index => {
'worklet';
currentPage.value = index;
}, []);
useEffect(() => {
if (!_.isUndefined(selectedIndex)) {
LogService.deprecationWarn({
component: 'TabController',
oldProp: 'selectedIndex',
newProp: 'initialIndex'
});
}
}, [selectedIndex]);
useEffect(() => {
setCurrentIndex(initialIndex);
}, [initialIndex]);
useAnimatedReaction(() => {
return currentPage.value;
}, (value, prevValue) => {
if (value !== prevValue) {
targetPage.value = withTiming(value);
prevValue !== null && runOnJS(onChangeIndex)(value, prevValue);
}
});
const context = useMemo(() => {
return {
/* Pass Props */
initialIndex,
asCarousel,
pageWidth,
/* Items */
items,
ignoredItems,
itemsCount: items.length - ignoredItems.length,
/* Animated Values */
targetPage,
currentPage,
// carouselOffset,
containerWidth: screenWidth,
/* Callbacks */
onChangeIndex,
setCurrentIndex
};
}, [initialIndex, asCarousel, items, onChangeIndex, screenWidth]);
return <TabBarContext.Provider value={context}>{children}</TabBarContext.Provider>;
}
TabController.TabBar = TabBar;
TabController.TabBarItem = TabBarItem;
TabController.TabPage = TabPage;
TabController.PageCarousel = PageCarousel;
export default asBaseComponent(TabController);