import React, { useCallback, useState } from 'react'
import { LayoutChangeEvent, StyleSheet, View } from 'react-native'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, { FadeOut, FadeIn } from 'react-native-reanimated'
import { inputOverlayManager } from './store'
import { DetectorProps, InterceptorProps, LayoutProps } from './types'
export * from './store'
/**
* Wraps a screen area so that tapping anywhere outside an `InputOverlay.Layout` closes all open overlays.
* `.runOnJS(true)` is mandatory because `inputOverlayManager.closeAll` mutates a nanostores atom on the JS thread.
*/
const Detector = ({ children, style, withWrapper = true }: DetectorProps) => {
const tapGesture = Gesture.Tap()
.onEnd((event) => {
inputOverlayManager.closeAll()
})
.runOnJS(true)
if (!withWrapper) {
{children}
}
return (
{children}
)
}
/**
* Stops tap events from bubbling up to the `Detector` layer — without this, tapping inside a dropdown would immediately close it.
* RNGH does not bubble gestures natively; this works because both gestures are `Tap` and RNGH resolves same-type competing gestures by which `GestureDetector` is closer in the tree.
*/
const Interceptor = ({ children }: InterceptorProps) => {
const tapGesture = Gesture.Tap()
.onEnd(() => {
// Intercepts the tap to prevent it from propagating to the parent
// Does nothing, just consumes the event
})
.runOnJS(true)
return (
{children}
)
}
const Layout = (props: LayoutProps) => {
const {
style,
children,
hideOverlay = false,
isOpen,
animationDuration = 150,
mode = 'overlay',
position,
gap,
content,
id,
} = props
const [inputHeight, setInputHeight] = useState(0)
const handleLayout = useCallback((event: LayoutChangeEvent) => {
const height = event?.nativeEvent?.layout?.height ?? 0
setInputHeight(height)
}, [])
const isOverlayMode = mode === 'overlay'
const shouldShowOverlay = isOpen && !hideOverlay
const overlayStyle = isOverlayMode ? {
position: 'absolute' as const,
zIndex: 1,
[position]: 0,
top: inputHeight + gap,
} : {
marginTop: gap,
}
return (
{children}
{shouldShowOverlay ? (
{content}
) : null}
)
}
const styles = StyleSheet.create({
detectorContainer: {
flex: 1,
},
layoutContainer: {
position: 'relative',
},
})
export const InputOverlay = {
Detector,
Interceptor,
Layout,
}