Skip to content

[Bug]: Text is flickering when inputting the characters inside the bottom sheet. #2235

@balkrishna-1996

Description

@balkrishna-1996

Version

v5

Reanimated Version

v3

Gesture Handler Version

v2

Platforms

Android

What happened?

Text is flickering when using the react native TextInput inside the bottom sheet.

Screen.Recording.2025-04-09.at.12.44.56.PM.mov

Reproduction steps

  • 1.--> Opening the bottom sheet where i have putted some input fields on button click.
  • 2.-->Created one common component like base modal.
  • 3.-->Calling the base modal in my component where i am showing the input boxes and buttons.
  • 4.-->Inside the basemodal component i have used the bottom sheet.

import {
Pressable,
Image,
TouchableOpacity,
TextInput,
BackHandler,
Platform,
Keyboard,
} from "react-native";
import React, {
useCallback,
useRef,
useState,
memo,
useMemo,
useEffect,
} from "react";
import {
BaseModal,
HStack,
Icon,
Spacer,
VStack,
Text,
CollectionVisibility,
CollectionCoverImageTooltip,
GlobalButton,
} from "@components";
import {
ADD_COLLECTION_ICON,
CLOSE_ICON,
CREATE_COLLECTION_BG,
DELETE_ICON,
ICON_CROP,
INFO_ICON,
REACTANGLE_DOTTED_SQUARE,
UPLOAD_IMAGE_ICON,
} from "src/assets/svg";
import {
Colors,
CustomSpacing,
dimensions,
phoneType,
safeAreaHeight,
} from "@Styles";
import styles from "./CreateWishListCollection.style";
import ToggleSwitch from "toggle-switch-react-native";
import ImagePicker from "react-native-image-crop-picker";
import { useNavigation } from "@react-navigation/native";
import { observer } from "mobx-react-lite";
import { useStores } from "@store/root.store";
import LinearGradient from "react-native-linear-gradient";
import i18n from "@services/i18n";

const CollectionsName = memo(
({ labeltitle, onCollectionNamePress, isActive }) => (
<TouchableOpacity
onPress={() => onCollectionNamePress("collectionName", labeltitle)}
>

{labeltitle}


)
);

const CreateWishListCollection = observer(
({ innerRef, open, close, type = "", navigation }) => {
const { newsfeedStore, shopStore, authStore } = useStores();
const [keyboardHeight, setKeyboardHeight] = useState(0);
if (!navigation) {
navigation = useNavigation();
}
const coverImageTooltipRef = useRef();
const visibilityTooltipRef = useRef();
const [isPrivate, setPrivete] = useState(false);
const [coverImage, setCoverImage] = useState(null);

const [collectionData, setCollectionData] = useState({
  collectionName: "",
  collectionDescription: "",
});

const collectionNames = useMemo(
  () => ["Makeup Daily", "Skincare Routine", "Exfoliate"],
  []
);

const updateCollectionData = (key, text) => {
  setCollectionData((prevData) => ({
    ...prevData,
    [key]: text,
  }));
};

const handleKeyboardDidShow = (event) => {
  setKeyboardHeight(event.endCoordinates.height);
};

const handleKeyboardDidHide = () => {
  setKeyboardHeight(0);
};

useEffect(() => {
  const showListener = Keyboard.addListener(
    "keyboardDidShow",
    handleKeyboardDidShow
  );
  const hideListener = Keyboard.addListener(
    "keyboardDidHide",
    handleKeyboardDidHide
  );

  return () => {
    showListener.remove();
    hideListener.remove();
  };
}, []);

useEffect(() => {
  resetData();
}, [newsfeedStore.createCollectionData]);

const resetData = () => {
  setCollectionData({
    collectionName: "",
    collectionDescription: "",
  });
  setCoverImage(null);
  if (type === "wishlist") {
    setPrivete(true);
  } else {
    setPrivete(false);
  }
};

const handleClose = () => {
  resetData();
  shopStore.setWishlistedProduct(null);
  shopStore.setSelectedProductToCollection(false);
  setTimeout(() => {
    close();
  }, 100);
};

useEffect(() => {
  if (type === "wishlist") {
    setPrivete(true);
  }
}, [type]);

useEffect(() => {
  const backHandler = BackHandler.addEventListener(
    "hardwareBackPress",
    handleBackButton
  );

  return () => backHandler.remove();
}, []);

const handleBackButton = () => {
  close();
};

const toggleSwitch = () => setPrivete((previousState) => !previousState);

const hanldeUploadImage = async () => {
  try {
    const image = await ImagePicker.openPicker({
      cropping: true,
      width: dimensions.screenWidth,
      height: dimensions.screenWidth / 3,
      showCropFrame: true,
      cropping: true,
    }).then((image) => {
      const data = {
        name: "",
        filename: image.filename,
        filepath: image.path,
        filetype: image.mime,
        sourceURL: image?.sourceURL,
      };
      return data;
    });
    setCoverImage(image);
  } catch (e) {}
};

const openCropImage = () => {
  ImagePicker.openCropper({
    path:
      Platform.OS === "android"
        ? coverImage?.filepath
        : coverImage?.sourceURL,
    width: dimensions.screenWidth,
    height: dimensions.screenWidth / 3,
  })
    .then((image) => {
      const data = {
        name: "",
        filename: image.filename,
        filepath: image.path,
        filetype: image.mime,
        sourceURL: image?.sourceURL,
      };
      setCoverImage(data);
    })
    .catch((err) => {});
};

const handleNext = () => {
  newsfeedStore.updateCreateCollectionData({
    is_private: isPrivate,
    description: collectionData?.collectionDescription,
    url: coverImage || "",
    title: collectionData?.collectionName,
  });
  if (Platform.OS === "ios") {
    newsfeedStore.setCreateCollectionRef(innerRef);
  }
  resetData();
  close();
  if (Platform.OS === "android") {
    navigation.navigate("CollectionAddProduct", {
      type: type,
    });
  } else {
    setTimeout(() => {
      navigation.navigate("CollectionAddProduct", {
        type: type,
      });
    }, 500);
  }
};

const openVisibilityToolTip = useCallback(() => {
  visibilityTooltipRef.current.present();
}, []);

const closeVisibilityToolTip = useCallback(() => {
  visibilityTooltipRef.current.close();
}, []);

const openCoverImageToolTip = useCallback(() => {
  coverImageTooltipRef.current.present();
}, []);

const closeCoverImageToolTip = useCallback(() => {
  coverImageTooltipRef.current.close();
}, []);

return (
  <BaseModal
    innerRef={innerRef}
    enableDynamicSizing
    scrollable={true}
    onClose={handleClose}
    withToast={false}
    topInset={safeAreaHeight[phoneType()].top}
    keyboardShouldPersistTaps={"handled"}
    scrollViewcontentContainerStyle={{
      paddingBottom: Platform.OS === "ios" ? keyboardHeight : 0,
    }}
    applyKeyboardAvoidingView={false}
    disablePandown={true}
  >
    <VStack>
      <HStack justifyContent="space-between" horizontal={CustomSpacing(16)}>
        <Text style={styles.heading}>
          {type === "wishlist"
            ? "create collection on wishlist"
            : i18n.t("CREATE_COLLECTION")}
        </Text>

        <Pressable onPress={handleClose}>
          <Icon
            xml={CLOSE_ICON}
            height={16}
            width={16}
            fill={Colors.BLACK_NEUTRAL_900}
          />
        </Pressable>
      </HStack>
    </VStack>
    <Spacer height={CustomSpacing(24)} />

    <VStack>
      {!(type === "wishlist") && (
        <LinearGradient
          colors={[Colors.LAVENDER_BLUSH_12, Colors.SEASHELL, Colors.WHITE]}
          style={styles.gradient}
        />
      )}

      <VStack horizontal={CustomSpacing(16)}>
        {!(type === "wishlist") && (
          <VStack style={styles.headerView}>
            <VStack style={{ position: "absolute", top: 0 }}>
              <Icon
                xml={CREATE_COLLECTION_BG}
                height={72}
                width={dimensions.screenWidth - CustomSpacing(32)}
              />
            </VStack>
            <HStack>
              <VStack style={styles.collectionBg}>
                <Icon xml={ADD_COLLECTION_ICON} height={27} width={27} />
              </VStack>
              <Spacer width={CustomSpacing(16)} />
              <Text style={styles.subText}>
                {i18n.t("CREATE_COLLECTION_BANNER")}
              </Text>
            </HStack>
          </VStack>
        )}

        <HStack justifyContent="space-between">
          <Text style={styles.inputHeadingText}>
            {i18n.t("Collection Name")}
          </Text>

          <HStack alignment="center">
            <Text
              style={
                collectionData?.collectionName?.length >= 100
                  ? [styles.countText, { color: Colors.NOTIF_RED }]
                  : styles.countText
              }
            >
              {collectionData?.collectionName?.length}/
            </Text>
            <Text
              style={
                collectionData?.collectionName?.length >= 100
                  ? [styles.countText, { color: Colors.NOTIF_RED }]
                  : collectionData?.collectionName?.length > 0
                  ? [styles.countText, { color: Colors.NOTIF_GREEN }]
                  : styles.countText
              }
            >
              100
            </Text>
          </HStack>
        </HStack>
        <Spacer height={CustomSpacing(4)} />
        <TextInput
          style={styles.collectionInput}
          containerStyle={styles.textInputContainer}
          onChangeText={(text) =>
            updateCollectionData("collectionName", text)
          }
          value={collectionData.collectionName}
          maxLength={100}
        />
        {collectionData.collectionName &&
          collectionData.collectionName?.length < 4 && (
            <Text style={styles.errortext}>
              {i18n.t("Title Min 4 Char")}
            </Text>
          )}
        {!(type === "wishlist") && (
          <HStack top={CustomSpacing(8)}>
            {collectionNames.map((name) => (
              <CollectionsName
                key={name}
                labeltitle={name}
                onCollectionNamePress={updateCollectionData}
                isActive={collectionData.collectionName === name}
              />
            ))}
          </HStack>
        )}

        <Spacer height={CustomSpacing(24)} />
        <HStack justifyContent="space-between">
          <Text style={styles.inputHeadingText}>
            {i18n.t("Description")}
          </Text>
          <HStack alignment="center">
            <Text
              style={
                collectionData?.collectionDescription?.length >= 500
                  ? [styles.countText, { color: Colors.NOTIF_RED }]
                  : styles.countText
              }
            >
              {collectionData?.collectionDescription?.length}/
            </Text>
            <Text
              style={
                collectionData?.collectionDescription?.length >= 500
                  ? [styles.countText, { color: Colors.NOTIF_RED }]
                  : collectionData?.collectionDescription?.length > 0
                  ? [styles.countText, { color: Colors.NOTIF_GREEN }]
                  : styles.countText
              }
            >
              500
            </Text>
          </HStack>
        </HStack>
        <Spacer height={CustomSpacing(4)} />
        <TextInput
          placeholder={i18n.t("Description Placeholder")}
          placeholderTextColor={Colors.CHARCOAL_200}
          style={styles.descriptionInput}
          containerStyle={styles.textInputContainer}
          multiline={true}
          numberOfLines={4}
          textAlignVertical={"top"}
          defaultValue={collectionData.collectionDescription}
          onChange={({ nativeEvent }) =>
            updateCollectionData("collectionDescription", nativeEvent.text)
          }
          maxLength={500}
        />
        {collectionData.collectionDescription &&
          collectionData.collectionDescription?.length < 4 && (
            <Text style={styles.errortext}>
              {i18n.t("Description Min 4 Char")}
            </Text>
          )}
      </VStack>
      {authStore.user?.is_expert_reviewer && (
        <>
          <Spacer height={CustomSpacing(24)} />
          <VStack horizontal={CustomSpacing(16)}>
            <HStack alignment="center">
              <Text style={styles.inputHeadingText}>
                {i18n.t("Cover Image (optional)")}
              </Text>
              <Spacer width={CustomSpacing(4)} />
              <Pressable onPress={openCoverImageToolTip}>
                <VStack top={CustomSpacing(3)}>
                  <Icon
                    xml={INFO_ICON}
                    height={15}
                    width={15}
                    fill={Colors.FLAMINGO_400}
                  />
                </VStack>
              </Pressable>
            </HStack>
            <Spacer height={CustomSpacing(2)} />
            <VStack>
              <HStack alignment="flex-start">
                {coverImage ? (
                  <VStack>
                    <Image
                      style={styles.image}
                      source={{
                        uri: coverImage?.filepath,
                      }}
                    />
                  </VStack>
                ) : (
                  <VStack style={styles.imageBox}>
                    <VStack style={styles.iconStyle}>
                      <Icon
                        xml={REACTANGLE_DOTTED_SQUARE}
                        height={50}
                        width={140}
                      />
                    </VStack>
                    <Icon xml={UPLOAD_IMAGE_ICON} height={20} width={24} />
                  </VStack>
                )}
                <Spacer width={CustomSpacing(16)} />
                <VStack>
                  {coverImage ? (
                    <HStack>
                      <Pressable
                        style={styles.uploadImage}
                        onPress={hanldeUploadImage}
                      >
                        <Text style={styles.uploadText}>
                          {i18n.t("CHANGE IMAGE")}
                        </Text>
                      </Pressable>
                      <Spacer width={CustomSpacing(8)} />
                      <Pressable onPress={openCropImage}>
                        <Icon xml={ICON_CROP} height={17} width={17} />
                      </Pressable>

                      <Spacer width={CustomSpacing(8)} />
                      <Pressable onPress={() => setCoverImage(null)}>
                        <Icon xml={DELETE_ICON} height={15} width={12} />
                      </Pressable>
                    </HStack>
                  ) : (
                    <Pressable
                      style={styles.uploadImage}
                      onPress={hanldeUploadImage}
                    >
                      <Text style={styles.uploadText}>
                        {i18n.t("Upload Image")}
                      </Text>
                    </Pressable>
                  )}
                  <Spacer height={CustomSpacing(6)} />
                  <Text style={styles.countText}>
                    {i18n.t("Ratio Image")} 3:1
                  </Text>
                </VStack>
              </HStack>
            </VStack>
          </VStack>
        </>
      )}
      <Spacer height={CustomSpacing(24)} />
      <VStack horizontal={CustomSpacing(16)}>
        <HStack alignment="center">
          <Text style={styles.inputHeadingText}>
            {i18n.t("Visibility")}
          </Text>
          <Spacer width={CustomSpacing(5)} />
          <Pressable onPress={openVisibilityToolTip}>
            <Icon
              xml={INFO_ICON}
              height={15}
              width={15}
              fill={Colors.FLAMINGO_400}
            />
          </Pressable>
        </HStack>
        <Spacer height={CustomSpacing(8)} />
        <HStack>
          <ToggleSwitch
            onColor={Colors.ROSSIE_300}
            offColor={Colors.NEUTRAL_200}
            onToggle={toggleSwitch}
            isOn={!isPrivate}
            thumbOnStyle={{
              backgroundColor: Colors.FLAMINGO_700,
            }}
            thumbOffStyle={{
              backgroundColor: Colors.CHARCOAL_400,
            }}
          />
          <Spacer width={CustomSpacing(8)} />
          <Text style={styles.privateText}>
            {!isPrivate ? i18n.t("Public") : i18n.t("Private")}
          </Text>
        </HStack>
      </VStack>
      <VStack style={styles.bottom}>
        <GlobalButton
          label={i18n.t("NEXT")}
          type={"primary"}
          onPress={handleNext}
          disabled={
            !collectionData.collectionName ||
            !collectionData.collectionDescription ||
            collectionData.collectionName?.length < 4 ||
            collectionData.collectionName?.length > 100 ||
            collectionData.collectionDescription?.length < 4 ||
            collectionData.collectionDescription?.length > 500
          }
        />
      </VStack>
      <CollectionVisibility
        innerRef={visibilityTooltipRef}
        close={closeVisibilityToolTip}
        isVN={authStore?.country === "Vietnam"}
      />
      <CollectionCoverImageTooltip
        innerRef={coverImageTooltipRef}
        close={closeCoverImageToolTip}
      />
    </VStack>
  </BaseModal>
);

}
);

export default CreateWishListCollection;

import React from "react";
import BottomSheet from "./BottomSheet";
import BottomSheetV2 from "./BottomSheetV2";
import { Text } from "@components";
// import PropTypes from "prop-types";

/**

  • @author Chandra Santoso chandra.santoso@sociolla.com
  • @name BaseModal
  • @Property {string} type - type of modal "bottom" | ...soon will update @todo @chandra
  • @Property {object} innerRef - useRef from source screen
  • @Property {array} snapList - array of snap size list
  • @Property {any} children - any react component to pass
  • @Property {boolean} enableDynamicSizing - enableDynamicSizing for children component
  • @Property {boolean} disablePandown - disablePandown for children component
  • @example
  • const exampleRef = useRef(null)
  • <BaseModal innerRef={exampleRef} snapList={["25%","50%""]} enableDynamicSizing={true}>
  • Example Children

*/

const BaseModal = ({
type = "bottom",
innerRef,
snapList = ["50%"],
enableDynamicSizing = false,
children = Empty Modal,
disablePandown = false,
footerComponent,
scrollable,
buttonParams,
headerParams,
isLoading,
onClose,
onDismiss,
stickyHeaderIndices,
showsVerticalScrollIndicator,
bottomInset,
bottomRerender,
headerSection,
handleIndicatorStyle,
footerButton,
backgroundStyle,
handleStyle,
disableBackdropPress = false,
withToast = true,
topInset = 0,
onOpen = () => {},
bottomSafeAreaHeight,
keyboardShouldPersistTaps,
automaticallyAdjustKeyboardInsets,
scrollViewcontentContainerStyle,
applyKeyboardAvoidingView,
alwaysBounceVertical,
bounces,
disableHardwareBackPress = false,
...props
}) => {
if (type === "bottom") {
return (
<BottomSheet
innerRef={innerRef}
enableDynamicSizing={enableDynamicSizing}
snapList={snapList}
disablePandown={disablePandown}
footerComponent={footerComponent}
scrollable={scrollable}
buttonParams={buttonParams}
footerButton={footerButton}
headerParams={headerParams}
isLoading={isLoading}
handleClose={onClose}
handleDismiss={onDismiss}
headerSection={headerSection}
stickyHeaderIndices={stickyHeaderIndices}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
bottomInset={bottomInset}
bottomRerender={bottomRerender}
handleIndicatorStyle={handleIndicatorStyle}
backgroundStyle={backgroundStyle}
handleStyle={handleStyle}
disableBackdropPress={disableBackdropPress}
withToast={withToast}
topInset={topInset}
onOpen={onOpen}
bottomSafeAreaHeight={bottomSafeAreaHeight}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
automaticallyAdjustKeyboardInsets={automaticallyAdjustKeyboardInsets}
scrollViewcontentContainerStyle={scrollViewcontentContainerStyle}
applyKeyboardAvoidingView={applyKeyboardAvoidingView}
alwaysBounceVertical={alwaysBounceVertical}
bounces={bounces}
disableHardwareBackPress={disableHardwareBackPress}
{...props}
>
{children}

);
} else if (type === "v2") {
return (
<BottomSheetV2
innerRef={innerRef}
enableDynamicSizing={enableDynamicSizing}
snapList={snapList}
disablePandown={disablePandown}
footerComponent={footerComponent}
scrollable={scrollable}
buttonParams={buttonParams}
footerButton={footerButton}
headerParams={headerParams}
isLoading={isLoading}
handleClose={onClose}
headerSection={headerSection}
stickyHeaderIndices={stickyHeaderIndices}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
bottomInset={bottomInset}
bottomRerender={bottomRerender}
handleIndicatorStyle={handleIndicatorStyle}
backgroundStyle={backgroundStyle}
handleStyle={handleStyle}
disableBackdropPress={disableBackdropPress}
withToast={withToast}
topInset={topInset}
onOpen={onOpen}
bottomSafeAreaHeight={bottomSafeAreaHeight}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
automaticallyAdjustKeyboardInsets={automaticallyAdjustKeyboardInsets}
scrollViewcontentContainerStyle={scrollViewcontentContainerStyle}
applyKeyboardAvoidingView={applyKeyboardAvoidingView}
{...props}
>
{children}

);
}
};

// BaseModal.defaultProps = {
// type: "bottom",
// snapList: ["50%"],
// children: Empty Modal,
// enableDynamicSizing: false,
// disablePandown: false,
// onOpen: () => {},
// };

// BaseModal.propTypes = {
// type: PropTypes.string,
// innerRef: PropTypes.object.isRequired,
// snapList: PropTypes.array,
// children: PropTypes.any.isRequired,
// enableDynamicSizing: PropTypes.bool,
// disablePandown: PropTypes.bool,
// footerComponent: PropTypes.any,
// scrollable: PropTypes.bool,
// buttonParams: PropTypes.shape({
// buttonStyle: PropTypes.object,
// typeButton: PropTypes.oneOf([
// "outline",
// "secondary",
// "outline",
// "filter",
// "filter_active",
// "form_footer",
// "disabled",
// "primary",
// ]),
// onPress: PropTypes.func,
// disabled: PropTypes.bool,
// labelStyle: PropTypes.object,
// labelButton: PropTypes.string,
// }),
// headerParams: PropTypes.shape({
// title: PropTypes.string,
// }),
// isLoading: PropTypes.bool,
// onClose: PropTypes.func,
// onDismiss: PropTypes.func,
// onOpen: PropTypes.func,
// stickyHeaderIndices: PropTypes.array,
// showsVerticalScrollIndicator: PropTypes.bool,
// bottomInset: PropTypes.number,
// bottomRerender: PropTypes.array,
// handleIndicatorStyle: PropTypes.object,
// backgroundStyle: PropTypes.object,
// bottomSafeAreaHeight: PropTypes.bool,
// };

export default BaseModal;

import React, { useMemo, useCallback, useState, useEffect } from "react";
import {
BottomSheetModal,
BottomSheetBackdrop,
BottomSheetView,
BottomSheetScrollView,
BottomSheetFooter,
ANIMATION_CONFIGS,
SCREEN_WIDTH,
} from "@gorhom/bottom-sheet";
import { ReduceMotion } from "react-native-reanimated";
import {
GlobalButton,
HStack,
Icon,
LoadingOverlay,
Spacer,
Text,
VStack,
} from "@components";
import { Colors, CustomSpacing } from "@Styles";
import {
BackHandler,
Keyboard,
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
} from "react-native";
import { CLOSE_ICON } from "src/assets/svg";
import Toast from "react-native-toast-message";
import toastConfig from "../Toast/toastConfig";
import { FONT_FAMILY, FONT_SIZE } from "@styles/fontConstants";
import RenderHTML from "react-native-render-html";
// import PropTypes from "prop-types";

/**

  • @author Chandra Santoso chandra.santoso@sociolla.com
  • @name BottomSheet
  • @Property {object} innerRef - useRef from source screen
  • @Property {array} snapList - array of snap size list
  • @Property {any} children - any react component to pass
  • @Property {boolean} enableDynamicSizing - enableDynamicSizing for children component
  • @Property {boolean} disablePandown - disablePandown for children component
  • @example
  • const exampleRef = useRef(null)
  • <BottomSheet innerRef={exampleRef} snapList={["25%","50%""]} enableDynamicSizing={true}>
  • Example Children

*/

const bottomSheetAnimationConfig = {
...ANIMATION_CONFIGS,
reduceMotion: ReduceMotion.Never,
};

const tagStyle = {
li: {
top: CustomSpacing(-2),
},
p: {
fontSize: CustomSpacing(16),
fontFamily: FONT_FAMILY.LATO_BOLD,
lineHeight: CustomSpacing(22),
padding: 0,
margin: 0,
marginBottom: CustomSpacing(4),
},
b: {
fontFamily: FONT_FAMILY.LATO_BOLD,
fontSize: CustomSpacing(16),
},
};

const defaultTextProps = {
numberOfLines: 1,
};
const HTML = ({ data, withEllipsis = true, baseStyle = {} }) => {
let url = data;

if (withEllipsis) {
const splitDataTitle = data?.split("

");
if (splitDataTitle?.length > 1) {
url = ${splitDataTitle[0]}...</p>;
}
}

if (!url) {
return null;
}
return (
<RenderHTML
systemFonts={[FONT_FAMILY.LATO_BOLD]}
defaultTextProps={defaultTextProps}
tagsStyles={tagStyle}
contentWidth={SCREEN_WIDTH}
source={{ html: url }}
baseStyle={baseStyle}
/>
);
};

const BottomSheet = ({
innerRef,
snapList = ["50%"],
enableDynamicSizing = false,
disablePandown = false,
children = Empty Modal,
footerComponent,
scrollable = false,
isLoading = false,
buttonParams = null,
footerButton,
handleClose,
handleDismiss,
stickyHeaderIndices = [],
showsVerticalScrollIndicator = true,
bottomInset = 20,
bottomRerender = [],
handleIndicatorStyle = {},
backgroundStyle = {},
headerParams,
headerSection,
handleStyle = {},
disableBackdropPress,
keyboardBehavior = "fillParent",
withToast,
topInset,
onOpen,
bottomSafeAreaHeight,
keyboardShouldPersistTaps = "never",
mockedPaddingToModal = false,
automaticallyAdjustKeyboardInsets = false,
toastPosition = "top",
scrollViewcontentContainerStyle = {},
applyKeyboardAvoidingView = true,
bounces = true,
alwaysBounceVertical = true,
disableHardwareBackPress = false,
...props
}) => {
const [currentIndex, setCurrentIndex] = useState(-1);
const snapPoints = useMemo(() => snapList, []);
const hideBottomSheet = () => {
Keyboard.dismiss();
innerRef.current?.dismiss();
if (handleClose) {
handleClose();
}
};

const onBackPress = () => {
if (innerRef !== null && !disableHardwareBackPress) {
innerRef.current?.dismiss();
return true;
}
};

useEffect(() => {
if (currentIndex !== -1) {
if (onOpen) {
onOpen();
}
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
onBackPress
);

  return () => backHandler.remove();
}

}, [currentIndex]);

const onBackClose = (index) => {
setCurrentIndex(index);
};

const renderToastConfig = () => {
return (
<VStack style={{ zIndex: 99 }}>
<Toast
config={toastConfig}
topOffset={toastConfig === "top" ? 0 : CustomSpacing(8)}
visibilityTime={2000}
/>

);
};

const renderBackdrop = useCallback(
(props) => (
<BottomSheetBackdrop
{...props}
pressBehavior={"collapse"}
disappearsOnIndex={-1}
appearsOnIndex={0}
onPress={!disableBackdropPress ? hideBottomSheet : null}
opacity={0.5}
keyboardBehavior={keyboardBehavior}
>


{withToast && toastPosition === "top" && renderToastConfig()}


),
[disableBackdropPress]
);

const renderButton = () => {
if (footerButton) {
return footerButton();
} else if (buttonParams) {
return (
<>



</>
);
}
};
const renderFooter = useCallback(
(props) => (
<BottomSheetFooter {...props} bottomInset={bottomInset}>
{footerComponent}

),
[...bottomRerender, footerComponent]
);

const renderHeaderSection = () => {
if (headerSection) {
return headerSection;
}
};
return (
<BottomSheetModal
animationConfigs={bottomSheetAnimationConfig}
ref={innerRef}
index={0}
snapPoints={enableDynamicSizing ? [] : snapPoints}
backdropComponent={renderBackdrop}
enableDynamicSizing={enableDynamicSizing}
enablePanDownToClose={disablePandown ? false : true}
footerComponent={renderFooter}
handleIndicatorStyle={[
{ height: CustomSpacing(3), backgroundColor: Colors.BRIGHT_GRAY },
handleIndicatorStyle,
]}
backgroundStyle={backgroundStyle}
handleStyle={handleStyle}
topInset={topInset}
onChange={onBackClose}
keyboardBehavior={Platform.OS === "ios" ? "interactive" : "height"}
keyboardBlurBehavior="restore"
onDismiss={handleDismiss}
{...props}
>

{headerParams && (

<HStack justifyContent={"space-between"}>
{headerParams?.useHtml ? (

) : (
<Text
numberOfLines={1}
style={{ ...{ letterSpacing: 0 }, ...headerParams?.titleStyle }}
type="readmoreModal"
>
{headerParams?.title || ""}

)}

<Icon
fill={headerParams?.iconColor || Colors.BLACK_NEUTRAL_900}
xml={CLOSE_ICON}
width={16}
height={16}
/>



)}
{withToast && toastPosition === "bottom" && renderToastConfig()}
{renderHeaderSection()}
{scrollable ? (
applyKeyboardAvoidingView ? (
<>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<BottomSheetScrollView
stickyHeaderIndices={stickyHeaderIndices}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
scrollEnabled={!mockedPaddingToModal}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
automaticallyAdjustKeyboardInsets={
automaticallyAdjustKeyboardInsets
}
contentContainerStyle={scrollViewcontentContainerStyle}
>
{children}
{mockedPaddingToModal && (
<>
{headerParams && }
{buttonParams && }
</>
)}


{renderButton()}
</>
) : (
<>
<BottomSheetScrollView
stickyHeaderIndices={stickyHeaderIndices}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
scrollEnabled={!mockedPaddingToModal}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
automaticallyAdjustKeyboardInsets={
automaticallyAdjustKeyboardInsets
}
contentContainerStyle={scrollViewcontentContainerStyle}
bounces={bounces}
alwaysBounceVertical={false}
>
{children}
{mockedPaddingToModal && (
<>
{headerParams && }
{buttonParams && }
</>
)}

        {renderButton()}
      </>
    )
  ) : (
    <BottomSheetView>
      {children}
      {renderButton()}
      {bottomSafeAreaHeight && <Spacer bottomSafeAreaHeight />}
    </BottomSheetView>
  )}
</BottomSheetModal>

);
};

// BottomSheet.defaultProps = {
// snapList: ["50%"],
// children: Empty Modal,
// enableDynamicSizing: false,
// disablePandown: false,
// scrollable: false,
// buttonParams: null,
// isLoading: false,
// stickyHeaderIndices: [],
// showsVerticalScrollIndicator: true,
// bottomInset: 20,
// bottomRerender: [],
// handleIndicatorStyle: {},
// backgroundStyle: {},
// handleStyle: {},
// scrollViewcontentContainerStyle: {},
// };

// BottomSheet.propTypes = {
// innerRef: PropTypes.object.isRequired,
// snapList: PropTypes.array.isRequired,
// children: PropTypes.any.isRequired,
// enableDynamicSizing: PropTypes.bool,
// disablePandown: PropTypes.bool,
// footerComponent: PropTypes.any,
// scrollable: PropTypes.bool,
// isLoading: PropTypes.bool,
// onClose: PropTypes.func,
// onOpen: PropTypes.func,
// handleDismiss: PropTypes.func,
// buttonParams: PropTypes.shape({
// buttonStyle: PropTypes.object,
// typeButton: PropTypes.oneOf([
// "outline",
// "secondary",
// "outline",
// "filter",
// "filter_active",
// "form_footer",
// "disabled",
// "primary",
// ]),
// }),
// handleClose: PropTypes.func,
// stickyHeaderIndices: PropTypes.array,
// showsVerticalScrollIndicator: PropTypes.bool,
// bottomInset: PropTypes.number,
// bottomRerender: PropTypes.array,
// handleIndicatorStyle: PropTypes.object,
// backgroundStyle: PropTypes.object,
// headerParams: PropTypes.shape({
// title: PropTypes.string,
// }),
// keyboardBehavior: PropTypes.string,
// bottomSafeAreaHeight: PropTypes.bool,
// };

export default BottomSheet;

-----package.json----

"dependencies": {
"@babel/plugin-proposal-decorators": "^7.22.15",
"@callstack/react-theme-provider": "^3.0.9",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@gorhom/bottom-sheet": "^5.1.2",
"@kichiyaki/react-native-barcode-generator": "^0.6.7",
"@likashefqet/react-native-image-zoom": "^4.1.0",
"@native-html/iframe-plugin": "^2.6.1",
"@notifee/react-native": "^9.1.8",
"@openspacelabs/react-native-zoomable-view": "^2.1.6",
"@react-native-assets/slider": "^7.2.1",
"@react-native-async-storage/async-storage": "^1.19.3",
"@react-native-camera-roll/camera-roll": "^7.9.0",
"@react-native-clipboard/clipboard": "^1.16.2",
"@react-native-community/image-editor": "^4.2.0",
"@react-native-community/netinfo": "^11.3.2",
"@react-native-community/push-notification-ios": "^1.11.0",
"@react-native-firebase/analytics": "^19.2.2",
"@react-native-firebase/app": "^19.2.2",
"@react-native-firebase/crashlytics": "^19.2.2",
"@react-native-firebase/messaging": "^19.2.2",
"@react-native-firebase/remote-config": "^19.2.2",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^6.6.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.x",
"@react-navigation/stack": "^6.4.1",
"@shopify/flash-list": "^1.7.5",
"axios": "^1.5.0",
"babel-plugin-inline-import": "^3.0.0",
"google-libphonenumber": "^3.2.40",
"i18next": "^23.5.1",
"mobx": "^6.10.2",
"mobx-persist": "^0.4.1",
"mobx-react-lite": "^4.0.4",
"numbro": "^2.4.0",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"prop-types": "^15.8.1",
"react": "18.3.1",
"react-i18next": "^13.2.2",
"react-native": "0.77.1",
"react-native-animatable": "^1.3.3",
"react-native-animated-dots-carousel": "^1.0.2",
"react-native-applifecycle": "^1.0.0",
"react-native-avoid-softinput": "^7.0.1",
"react-native-awesome-slider": "^2.5.3",
"react-native-branch": "6.2.1",
"react-native-calendars": "^1.1303.0",
"react-native-collapsible": "^1.6.1",
"react-native-config": "^1.4.6",
"react-native-contacts": "^8.0.4",
"react-native-copilot": "^3.3.3",
"react-native-date-picker": "5.0.10",
"react-native-device-info": "^14.0.4",
"react-native-error-boundary": "^1.2.4",
"react-native-fast-image": "^8.6.3",
"react-native-fbsdk-next": "^13.0.0",
"react-native-fs": "2.16.1",
"react-native-geolocation-service": "^5.3.1",
"react-native-gesture-handler": "2.24.0",
"react-native-image-crop-picker": "^0.41.2",
"react-native-image-picker": "^7.1.0",
"react-native-image-viewing": "^0.2.2",
"react-native-insider": "^6.4.5-nh",
"react-native-keychain": "^8.1.3",
"react-native-linear-gradient": "^2.8.3",
"react-native-localize": "^3.3.0",
"react-native-maps": "1.14.0",
"react-native-network-logger": "^1.15.0",
"react-native-pager-view": "^6.7.0",
"react-native-permissions": "^3.9.2",
"react-native-popover-view": "^6.1.0",
"react-native-qrcode-svg": "^6.2.0",
"react-native-reanimated": "^3.17.1",
"react-native-reanimated-zoom": "^0.3.3",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^5.3.0",
"react-native-screens": "4.9.2",
"react-native-shake": "^4.0.2",
"react-native-share": "^12.0.9",
"react-native-size-matters": "^0.4.0",
"react-native-skeleton-placeholder": "^5.2.4",
"react-native-sms": "^1.11.0",
"react-native-splash-screen": "^3.3.0",
"react-native-svg": "^15.11.2",
"react-native-svg-transformer": "^1.1.0",
"react-native-tab-view": "^4.0.8",
"react-native-toast-message": "^2.2.0",
"react-native-tracking-transparency": "^0.1.2",
"react-native-url-polyfill": "^2.0.0",
"react-native-user-agent": "^2.3.1",
"react-native-video": "^6.11.0",
"react-native-video-duration": "^0.1.2",
"react-native-view-shot": "^3.8.0",
"react-native-vision-camera": "^4.6.4",
"react-native-webview": "^13.13.4",
"react-native-youtube-iframe": "^2.3.0",
"rn-fetch-blob": "^0.12.0",
"rn-placeholder": "^3.0.3",
"sanitize-html": "^2.13.0",
"socket.io-client": "^2.3.1",
"text-clipper": "^2.2.0",
"toggle-switch-react-native": "^3.3.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.16.5",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "15.0.1",
"@react-native-community/cli-platform-android": "15.0.1",
"@react-native-community/cli-platform-ios": "15.0.1",
"@react-native/babel-preset": "0.77.1",
"@react-native/eslint-config": "0.77.1",
"@react-native/metro-config": "0.77.1",
"@react-native/typescript-config": "0.77.1",
"@tsconfig/react-native": "^3.0.0",
"@types/jest": "^29.5.13",
"@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^29.6.3",
"babel-plugin-module-resolver": "^5.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.3.0",
"eslint-config-sociolla": "git+ssh://git@bitbucket.org/Sociolla/eslint-config-sociolla.git",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-react-native": "^4.0.0",
"husky": "^8.0.0",
"jest": "^29.6.3",
"lint-staged": "^15.5.0",
"prettier": "2.4.1",
"react-native-flipper-performance-plugin": "^0.4.0",
"react-test-renderer": "18.3.1",
"reactotron-react-native": "^5.1.12",
"typescript": "5.0.4"
},
"engines": {
"node": ">=18"
},

Reproduction sample

NA

Relevant log output

Text should not flicker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginvalidThis doesn't seem right

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions