import React from 'react'
import { View } from '../View'
import { Scroll } from '../Scroll'
import { TypeGuards } from '@codeleap/types'
import { Backdrop } from '../Backdrop'
import { useBackButton } from '../../utils/hooks'
import { Text } from '../Text'
import { Touchable } from '../Touchable'
import { ModalHeaderProps, ModalProps } from './types'
import { AnyRecord, AppIcon, useNestedStylesByKey, IJSX, StyledComponentProps } from '@codeleap/styles'
import { MobileStyleRegistry } from '../../Registry'
import { useStylesFor } from '../../hooks'
import { FadeIn, FadeOut } from 'react-native-reanimated'
import { Platform, Modal as RNModal } from 'react-native'
import { FullWindowOverlay } from 'react-native-screens'
import { Button } from '../Button'
export * from './styles'
export * from './types'
/**
* On iOS, React Native's `Modal` renders into a separate UIWindow that sits below the system keyboard, causing keyboard-aware layouts to break inside modals.
* `FullWindowOverlay` from react-native-screens renders at the top of the main UIWindow's view hierarchy instead, which the keyboard responder chain can reach.
* Android does not have this issue, so the standard `RNModal` is used there.
*/
const Overlay = Platform.select({
ios: FullWindowOverlay,
android: RNModal as any,
})
const DefaultHeader = (props: ModalHeaderProps) => {
const {
styles,
buttonStyles,
title = null,
showClose = false,
description = null,
closable, debugName,
closeIconName = 'x',
toggle,
} = props
return <>
{(title || showClose || description) && (
{TypeGuards.isString(title) ? (
) : (
title
)}
{(showClose && closable) ? (
) : null}
{TypeGuards.isString(description) ? (
) : (
description
)}
)}
>
}
export const Modal = (modalProps: ModalProps) => {
const {
visible,
closable,
footer,
children,
toggle = () => null,
dismissOnBackdrop,
header,
debugName,
scroll,
renderHeader: Header,
scrollProps = {},
closeOnHardwareBackPress,
style,
boxExiting,
boxEntering,
withPortal,
...props
} = {
...Modal.defaultProps,
...modalProps,
}
const styles = useStylesFor(Modal.styleRegistryName, style)
const buttonStyles = useNestedStylesByKey('closeButton', styles)
const ScrollComponent = scroll ? Scroll : View
const scrollStyle = scroll ? styles?.scroll : styles?.innerWrapper
/** Returning `true` from the back-button handler is required to signal to Android that the event was consumed; without it the OS will still perform the default back navigation. */
useBackButton(() => {
if (visible && closeOnHardwareBackPress) {
toggle()
return true
}
}, [visible, toggle, closeOnHardwareBackPress])
return (
{dismissOnBackdrop ? (
{ })}
debounce={400}
debugName={'Modal backdrop touchable'}
style={styles?.backdropTouchable}
android_ripple={null}
noFeedback
/>) : null}
{
visible ? (
{header ? header : (
)}
{children}
{footer ? (
{typeof footer === 'string' ? : footer}
) : null}
) : null
}
)
}
Modal.styleRegistryName = 'Modal'
Modal.elements = ['wrapper', 'box', 'backdrop', 'innerWrapper', 'scroll', 'body', 'footer', 'header', 'title', 'description', 'closeButton']
Modal.rootElement = 'wrapper'
Modal.withVariantTypes = (styles: S) => {
return Modal as (props: StyledComponentProps) => IJSX
}
Modal.defaultProps = {
closeIconName: 'close' as AppIcon,
closable: true,
dismissOnBackdrop: true,
scroll: true,
closeOnHardwareBackPress: true,
boxEntering: FadeIn.duration(100).delay(50).build(),
boxExiting: FadeOut.duration(100).build(),
withPortal: true,
renderHeader: DefaultHeader,
} as Partial
MobileStyleRegistry.registerComponent(Modal)