import { useCallback, useMemo } from 'use-memo-one'
import React, { useEffect, useState, memo } from 'react'
import { Easing, Animated, StyleSheet, TouchableWithoutFeedback, Platform } from 'react-native'
import type { SharedProps, ModalfyParams, ModalStackItem, ModalPendingClosingAction } from '../types'
import StackItem from './StackItem'
import { defaultOptions, getStackItemOptions, sh } from '../utils'
type Props
= SharedProps
const ModalStack =
(props: Props
) => {
const { stack } = props
const [hasChangedBackdropColor, setBackdropColorStatus] = useState(false)
const [backdropClosedItems, setBackdropClosedItems] = useState([])
const [openActionCallbacks, setOpenActionCallbacks] = useState([])
const { opacity, translateY } = useMemo(
() => ({
opacity: new Animated.Value(0),
translateY: new Animated.Value(sh(100)),
}),
[],
)
const { backBehavior, backdropAnimationDuration, backdropColor, backdropOpacity } = useMemo(
() => getStackItemOptions(Array.from(stack.openedItems).pop(), stack),
[stack],
)
const hideBackdrop = useCallback(() => {
Animated.timing(opacity, {
toValue: 0,
easing: Easing.inOut(Easing.ease),
duration: backdropAnimationDuration,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) translateY.setValue(sh(100))
})
}, [backdropAnimationDuration, opacity, translateY])
useEffect(() => {
if (stack.openedItemsSize && backdropColor && backdropColor !== 'black' && !hasChangedBackdropColor) {
setBackdropColorStatus(true)
}
}, [backdropColor, hasChangedBackdropColor, stack.openedItemsSize])
useEffect(() => {
const scrollY = Platform.OS === 'web' ? window.scrollY ?? document.documentElement.scrollTop : 0
if (stack.openedItemsSize) {
translateY.setValue(scrollY)
Animated.timing(opacity, {
toValue: 1,
easing: Easing.in(Easing.ease),
duration: backdropAnimationDuration,
useNativeDriver: true,
}).start()
} else hideBackdrop()
}, [backdropAnimationDuration, opacity, stack.openedItemsSize, translateY])
const renderStackItem = (stackItem: ModalStackItem, index: number) => {
const position = stack.openedItemsSize - index
const pendingClosingAction: ModalPendingClosingAction | undefined = stack.pendingClosingActions
.values()
.next().value
const hasPendingClosingAction = position === 1 && pendingClosingAction?.currentModalHash === stackItem.hash
return (
{
// @ts-ignore
props.openModal(...args)
setOpenActionCallbacks(state => [...state, stackItem.hash])
}}
hideBackdrop={hideBackdrop}
wasOpenCallbackCalled={openActionCallbacks.includes(stackItem.hash)}
wasClosedByBackdropPress={backdropClosedItems.includes(stackItem.hash)}
pendingClosingAction={hasPendingClosingAction ? pendingClosingAction : undefined}
/>
)
}
const renderStack = () => {
if (!stack.openedItemsSize) return null
return [...stack.openedItems].map(renderStackItem)
}
const onBackdropPress = () => {
if (backBehavior === 'none') return
const currentItem = [...stack.openedItems].slice(-1)[0]
if (stack.openedItemsSize === 1) hideBackdrop()
setBackdropClosedItems([...backdropClosedItems, currentItem?.hash])
}
const renderBackdrop = () => {
const onPress = () => onBackdropPress()
const backgroundColor =
stack.openedItemsSize && backdropColor ? backdropColor : hasChangedBackdropColor ? 'transparent' : 'black'
return (
)
}
return (
{renderBackdrop()}
{renderStack()}
)
}
const styles = StyleSheet.create({
container: {
...Platform.select({
android: {
...StyleSheet.absoluteFillObject,
zIndex: 0,
},
ios: {
...StyleSheet.absoluteFillObject,
zIndex: 0,
},
}),
},
containerWeb: {
...StyleSheet.absoluteFillObject,
overflow: 'hidden',
height: '100vh',
width: '100vw',
zIndex: 0,
},
backdrop: {
...StyleSheet.absoluteFillObject,
},
})
export default memo(
ModalStack,
(prevProps, nextProps) =>
prevProps.stack.openedItemsSize === nextProps.stack.openedItemsSize &&
prevProps.stack.pendingClosingActionsSize === nextProps.stack.pendingClosingActionsSize,
)