react-native-ui-lib
Version:
[](https://stand-with-ukraine.pp.ua)
102 lines (99 loc) • 3.67 kB
JavaScript
import React, { useCallback, useContext, useMemo, useEffect } from 'react';
import TabBarContext from "./TabBarContext";
import Reanimated, { runOnJS, useAnimatedReaction, useAnimatedRef, useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
import { Constants } from "../../commons/new";
const FIX_RTL = Constants.isRTL;
/**
* @description: TabController's Page Carousel
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/TabControllerScreen/index.tsx
* @notes: You must pass `asCarousel` flag to TabController and render your TabPages inside a PageCarousel
*/
function PageCarousel(props) {
const {
onMomentumScrollEnd,
style,
...others
} = props;
const carousel = useAnimatedRef();
const {
itemsCount,
currentPage,
targetPage,
pageWidth,
// carouselOffset,
setCurrentIndex
} = useContext(TabBarContext);
const initialOffset = useMemo(() => ({
x: currentPage.value * pageWidth,
y: 0
}), []);
const indexChangeReason = useSharedValue(undefined);
const scrollToInitial = useCallback(() => {
if (Constants.isAndroid && currentPage.value) {
scrollToItem(currentPage.value);
}
}, []);
const calcOffset = useCallback(offset => {
'worklet';
return FIX_RTL ? pageWidth * (itemsCount - 1) - offset : offset;
}, [pageWidth, itemsCount]);
const scrollHandler = useAnimatedScrollHandler({
onScroll: e => {
// carouselOffset.value = e.contentOffset.x;
const xOffset = calcOffset(e.contentOffset.x);
const newIndex = xOffset / pageWidth;
if (indexChangeReason.value === 'byPress') {
// Scroll was immediate and not by gesture
/* Round is for android when offset value has fraction */
// targetPage.value = withTiming(Math.round(newIndex));
indexChangeReason.value = undefined;
} else {
targetPage.value = newIndex;
}
},
onMomentumEnd: e => {
const xOffset = calcOffset(e.contentOffset.x);
const newPage = Math.round(xOffset / pageWidth);
indexChangeReason.value = 'byScroll';
setCurrentIndex(newPage);
}
});
const scrollToItem = useCallback(index => {
if (indexChangeReason.value === 'byScroll') {
indexChangeReason.value = undefined;
} else {
indexChangeReason.value = 'byPress';
}
const actualIndex = FIX_RTL ? itemsCount - index - 1 : index;
carousel.current?.scrollTo({
x: actualIndex * pageWidth,
animated: false
});
}, [pageWidth, itemsCount]);
useAnimatedReaction(() => {
return currentPage.value;
}, (currIndex, prevIndex) => {
if (prevIndex !== null && currIndex !== prevIndex) {
runOnJS(scrollToItem)(currIndex);
}
});
useEffect(() => {
carousel.current?.scrollTo({
x: currentPage.value * pageWidth,
animated: false
});
}, [pageWidth]);
const handleOnMomentumScrollEnd = useCallback(event => {
onMomentumScrollEnd?.(event);
}, [onMomentumScrollEnd]);
const _style = useMemo(() => {
return [{
width: pageWidth
}, style];
}, [pageWidth, style]);
return <Reanimated.ScrollView {...others} style={_style} ref={carousel} horizontal pagingEnabled showsHorizontalScrollIndicator={false} onScroll={scrollHandler} scrollEventThrottle={16} contentOffset={initialOffset} // iOS only
onLayout={scrollToInitial} // Android only
onMomentumScrollEnd={handleOnMomentumScrollEnd} // TODO: workaround for useAnimatedScrollHandler.onMomentumEnd not being called (https://github.com/software-mansion/react-native-reanimated/issues/2735)
/>;
}
export default PageCarousel;