Created
          September 12, 2025 18:55 
        
      - 
      
- 
        Save SyedTayyabUlMazhar/00de8fe02b620c8bf87205d167bbb096 to your computer and use it in GitHub Desktop. 
    A modal that extends react-native-modal. Includes presenting modes(sheet, modal). Uses Portal as well.
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import React, { useEffect, useId, useState } from "react"; | |
| import { | |
| BackHandler, | |
| Dimensions, | |
| LayoutChangeEvent, | |
| Platform, | |
| TouchableOpacity, | |
| View, | |
| } from "react-native"; | |
| import ReactNativeModal from "react-native-modal"; | |
| import styles from "./style"; | |
| import { BaseModalProps } from "./types"; | |
| import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"; | |
| import { COLORS } from "@/constants/Colors"; | |
| import { Portal } from "@rn-primitives/portal"; | |
| import { useBoolean } from "@/hooks"; | |
| const Config = { | |
| modal: { | |
| animationInTiming: 50, | |
| animationOutTiming: 200, | |
| animationIn: "fadeIn", | |
| animationOut: "fadeOut", | |
| swipeDirection: undefined, | |
| style: undefined, | |
| containerStyle: styles.modalContainer, | |
| ...(Platform.OS === "android" | |
| ? { deviceHeight: Dimensions.get("screen").height } | |
| : {}), | |
| }, | |
| sheet: { | |
| animationInTiming: 400, | |
| animationOutTiming: 700, | |
| animationIn: "slideInUp", | |
| animationOut: "slideOutDown", | |
| swipeDirection: "down", | |
| style: styles.sheet, | |
| containerStyle: styles.sheetContainer, | |
| }, | |
| } as const; | |
| export const BaseModal = (props: BaseModalProps) => { | |
| const { | |
| modalState, | |
| mode, | |
| children, | |
| height, | |
| minHeight, | |
| maxHeight, | |
| name, | |
| portal, | |
| closeButtonStyle, | |
| } = props; | |
| const id = useId(); | |
| const isSheet = mode === "sheet"; | |
| const { containerStyle, style, ...config } = Config[mode]; | |
| const shouldShowModalContent = useBoolean(); | |
| const [contentHeight, setContentHeight] = useState(0); | |
| const shouldCalculateContentHeight = isSheet && !contentHeight; | |
| const close = () => { | |
| modalState.setFalse(); | |
| }; | |
| const onLayout = (e: LayoutChangeEvent) => { | |
| setContentHeight(e.nativeEvent.layout.height); | |
| }; | |
| useEffect(() => { | |
| const backHandler = BackHandler.addEventListener( | |
| "hardwareBackPress", | |
| () => { | |
| close(); | |
| return true; | |
| } | |
| ); | |
| return () => backHandler.remove(); | |
| }, []); | |
| const renderModal = () => { | |
| return ( | |
| <ReactNativeModal | |
| backdropOpacity={0.4} | |
| onBackdropPress={close} | |
| isVisible={modalState.value} | |
| backdropTransitionOutTiming={1} | |
| coverScreen={name ? false : true} | |
| hideModalContentWhileAnimating | |
| onModalWillShow={shouldShowModalContent.setTrue} | |
| onModalWillHide={shouldShowModalContent.setFalse} | |
| backdropTransitionInTiming={1} | |
| statusBarTranslucent | |
| {...config} | |
| {...(isSheet | |
| ? { | |
| swipeThreshold: contentHeight * 0.4, | |
| onSwipeComplete: close, | |
| propagateSwipe: true, | |
| scrollOffset: 1, | |
| scrollTo: () => {}, | |
| } | |
| : {})} | |
| style={[style, props.style]} | |
| > | |
| {shouldShowModalContent.value ? ( | |
| <View | |
| style={[ | |
| containerStyle, | |
| props.contentContainerStyle, | |
| { height, minHeight, maxHeight }, | |
| ]} | |
| onLayout={shouldCalculateContentHeight ? onLayout : undefined} | |
| > | |
| {!isSheet ? ( | |
| <TouchableOpacity | |
| style={[styles.closeButton, closeButtonStyle]} | |
| onPress={close} | |
| > | |
| <FontAwesomeIcon | |
| icon={["far", "xmark"]} | |
| size={20} | |
| color={COLORS.BLACK} | |
| /> | |
| </TouchableOpacity> | |
| ) : null} | |
| {isSheet ? <View style={styles.handleIndicator} /> : null} | |
| {children} | |
| </View> | |
| ) : null} | |
| </ReactNativeModal> | |
| ); | |
| }; | |
| if (name || portal) { | |
| return <Portal name={"portal-" + id}>{renderModal()}</Portal>; | |
| } | |
| return renderModal(); | |
| }; | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import { COLORS } from "@/constants/Colors"; | |
| import { scale, verticalScale } from "@/utilities/Metrics"; | |
| import { StyleSheet } from "react-native"; | |
| const styles = StyleSheet.create({ | |
| closeButton: { | |
| position: "absolute", | |
| top: verticalScale(16), | |
| right: scale(16), | |
| zIndex: 1 | |
| }, | |
| sheet: { | |
| margin: 0, | |
| justifyContent: "flex-end", | |
| }, | |
| handleIndicator: { | |
| backgroundColor: COLORS.SHEET_HANDLE, | |
| width: scale(42), | |
| height: scale(6), | |
| borderRadius: 8, | |
| opacity: 0.2, | |
| marginTop: verticalScale(24), | |
| marginBottom: verticalScale(24), | |
| alignSelf: "center", | |
| }, | |
| modalContainer: { | |
| backgroundColor: COLORS.WHITE, | |
| borderRadius: scale(15), | |
| paddingHorizontal: scale(24), | |
| paddingVertical: verticalScale(48), | |
| }, | |
| sheetContainer: { | |
| backgroundColor: COLORS.WHITE, | |
| borderTopLeftRadius: scale(15), | |
| borderTopRightRadius: scale(15), | |
| paddingHorizontal: scale(24), | |
| paddingBottom: verticalScale(48), | |
| }, | |
| }); | |
| export default styles; | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import { UseBooleanReturn } from "@/hooks/useBoolean"; | |
| import React from "react"; | |
| import { StyleProp, ViewStyle } from "react-native"; | |
| export type BaseModalProps = React.PropsWithChildren<{ | |
| modalState: UseBooleanReturn; | |
| mode: "sheet" | "modal"; | |
| contentContainerStyle?: StyleProp<ViewStyle>; | |
| height?: ViewStyle["height"]; | |
| minHeight?: ViewStyle["minHeight"]; | |
| maxHeight?: ViewStyle["maxHeight"]; | |
| /** | |
| * @deprecated use {@link BaseModalProps.portal} instead. | |
| * | |
| * For use with Portal, if provided, the modal will be rendered inside a Portal | |
| * and cover screen will be set to false | |
| */ | |
| name?: string; | |
| portal?: boolean; | |
| closeButtonStyle?: StyleProp<ViewStyle>; | |
| /** | |
| * Style prop passed to the react-native-modal | |
| */ | |
| style?: StyleProp<ViewStyle>; | |
| }>; | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment