import React, { useCallback, useEffect, useRef, useState } from 'react' import { ReduceMotion } from 'react-native-reanimated' import Carousel, { ICarouselInstance } from 'react-native-reanimated-carousel' import { CarouselRenderItemInfo, TCarouselProps } from 'react-native-reanimated-carousel/lib/typescript/types' import { Dimensions, LayoutChangeEvent } from 'react-native' import { useConditionalState } from '@codeleap/hooks' import { AnyRecord, IJSX, StyledComponentProps, useNestedStylesByKey } from '@codeleap/styles' import { useStylesFor } from '../../hooks' import { MobileStyleRegistry } from '../../Registry' import { View } from '../View' import { PageProps, PagerProps } from './types' import { PagerDots } from './PagerDots' import { PagerItem } from './PagerItem' export * from './styles' export * from './types' export * from './PagerDots' const window = Dimensions.get('screen') export function Pager(props: PagerProps) { const { pages, page, onChangePage, initialPage, style, showDots, renderItem, footer, width: carouselWidth, height: carouselHeight, autoCalculateFooterHeight, removeFixedHeight, removeFixedWidth, ...rest } = { ...Pager.defaultProps, ...props, } const carouselRef = useRef(null) const [currentPage, setCurrentPage] = useConditionalState(page, onChangePage, { initialValue: initialPage }) const [footerHeight, setFooterHeight] = useState(0) const [loaded, setLoaded] = useState(!showDots && !footer ? true : false) const styles = useStylesFor(Pager.styleRegistryName, style) const dotStyles = useNestedStylesByKey('dot', styles) useEffect(() => { if (carouselRef.current?.getCurrentIndex?.() !== currentPage) { carouselRef.current?.scrollTo?.({ index: currentPage, animated: true }) } }, [currentPage]) const getItemInfo = useCallback((index: number) => { const info: Omit, 'item' | 'animationValue'> = { isFirst: index === 0, isLast: index === pages?.length - 1, isOnly: pages?.length === 1, index, } return info }, []) /** `animationValue` is a Reanimated SharedValue driven by the carousel's scroll position; it is forwarded to each item so children can react to swipe progress on the UI thread without JS round-trips. */ const customRenderItem = useCallback(({ item, index, animationValue }: CarouselRenderItemInfo) => { const info = getItemInfo(index) return ( ) }, [renderItem, getItemInfo]) const onFooterLayout = useCallback((event: LayoutChangeEvent) => { setFooterHeight(event.nativeEvent.layout.height) /** Deferred a frame so `height` is stable before the carousel becomes visible, preventing a layout flash. */ setTimeout(() => setLoaded(true), 0) }, []) const width = carouselWidth - removeFixedWidth const height = carouselHeight - ((autoCalculateFooterHeight ? footerHeight : 0) + removeFixedHeight) return ( {/* @ts-ignore */} } style={styles.carousel} /> {footer} {showDots ? ( ) : null} ) } Pager.styleRegistryName = 'Pager' Pager.elements = ['carousel', 'wrapper', 'footerWrapper', 'dot'] Pager.rootElement = 'wrapper' Pager.withVariantTypes = (styles: S) => { return Pager as (props: StyledComponentProps, typeof styles>) => IJSX } Pager.defaultProps = { width: window.width, height: window.height, showDots: true, autoCalculateFooterHeight: true, initialPage: 0, footer: null, removeFixedHeight: 0, removeFixedWidth: 0, } as Partial> MobileStyleRegistry.registerComponent(Pager)