Răsfoiți Sursa

Feature: BottomSheet completed.

lfabl 2 luni în urmă
părinte
comite
f25b0a6914

+ 4 - 0
example/package.json

@@ -11,6 +11,9 @@
     },
     "dependencies": {
         "@expo/metro-runtime": "~55.0.6",
+        "@react-navigation/elements": "^2.9.14",
+        "@react-navigation/native": "^7.2.2",
+        "@react-navigation/native-stack": "^7.14.11",
         "expo": "~55.0.4",
         "expo-font": "~55.0.4",
         "expo-status-bar": "~55.0.4",
@@ -20,6 +23,7 @@
         "react-native": "0.83.2",
         "react-native-portalize": "^1.0.7",
         "react-native-safe-area-context": "^5.7.0",
+        "react-native-screens": "^4.24.0",
         "react-native-svg": "^15.15.3",
         "react-native-web": "~0.21.0"
     },

+ 5 - 93
example/src/index.tsx

@@ -1,98 +1,18 @@
-import {
-    useRef
-} from "react";
-import {
-    StyleSheet,
-    View
-} from "react-native";
-import {
-    type IBottomSheetRef,
-    setupNCoreUIKit,
-    NCoreUIKitTheme,
-    BottomSheet,
-    SelectBox,
-    Button,
-    Text
-} from "ncore-ui-kit-mobile";
+import Navigation from "./navigation";
 import {
     useFonts
 } from "expo-font";
-import TextInput from "../../src/components/textInput";
-import packageJSON from "../package.json";
+import {
+    setupNCoreUIKit
+} from "ncore-ui-kit-mobile";
 
 const NCoreUIKitBase = setupNCoreUIKit({
     initialSelectedGapPropagation: "compact",
     initialSelectedTheme: "dark"
 });
 
-const X = [{
-    t: "x",
-    p: "y",
-    n: 90
-}];
-
 const App = () => {
-    const {
-        colors
-    } = NCoreUIKitTheme.useContext();
-
-    const bottomSheetRef = useRef<IBottomSheetRef>(null);
-
-    return <View
-        style={[
-            styles.container,
-            {
-                backgroundColor: colors.content.container.default
-            }
-        ]}
-    >
-        <SelectBox
-            keyExtractor={(data, index) => `${data.t}-${index}`}
-            titleExtractor={(data) => data.t}
-            subTitle="Deneme Subtitle"
-            hintText="Test deneme"
-            isShowSubTitle={true}
-            initialSelectedItems={[{
-                __title: X[0]?.t as string,
-                __key: `${X[0]?.t}-0`,
-                __originalIndex: 0
-            }]}
-            title="Deneme Box"
-            isRequired={true}
-            data={X}
-        />
-        <TextInput
-            variant="hidden"
-        />
-        <Text>Version: v{packageJSON.version}</Text>
-        <Button
-            onPress={() => {
-                bottomSheetRef.current?.open();
-            }}
-            type="success"
-            title="Ahmet"
-            variant="filled"
-        />
-        <BottomSheet
-            isCanFullScreenOnSwipe={true}
-            ref={bottomSheetRef}
-            snapPoint={300}
-        >
-            <View
-                style={{
-                    backgroundColor: "red",
-                    height: 2400
-                }}
-            />
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-            <Text>Deneme 123</Text>
-        </BottomSheet>
-    </View>;
+    return <Navigation/>;
 };
 
 const ContextAPI = () => {
@@ -115,12 +35,4 @@ const ContextAPI = () => {
         <App/>
     </NCoreUIKitBase.Provider>;
 };
-
-const styles = StyleSheet.create({
-    container: {
-        justifyContent: "center",
-        alignItems: "center",
-        flex: 1
-    }
-});
 export default ContextAPI;

+ 32 - 0
example/src/navigation/index.tsx

@@ -0,0 +1,32 @@
+import {
+    NavigationContainer
+} from "@react-navigation/native";
+import {
+    createNativeStackNavigator
+} from "@react-navigation/native-stack";
+import Home from "../pages/home";
+import TestSubPage from "../pages/testSubPage";
+
+const RootStack = createNativeStackNavigator();
+
+const RootNav = () => {
+    return <RootStack.Navigator
+        initialRouteName="Home"
+    >
+        <RootStack.Screen
+            name="Home"
+            component={Home}
+        />
+        <RootStack.Screen
+            name="TestSubPage"
+            component={TestSubPage}
+        />
+    </RootStack.Navigator>;
+};
+
+const Navigation = () => {
+    return <NavigationContainer>
+        <RootNav/>
+    </NavigationContainer>;
+};
+export default Navigation;

+ 120 - 0
example/src/pages/home/index.tsx

@@ -0,0 +1,120 @@
+import {
+    useRef
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    type IBottomSheetRef,
+    getNCoreUIKitVersion,
+    NCoreUIKitTheme,
+    BottomSheet,
+    TextInput,
+    SelectBox,
+    Button,
+    Text
+} from "ncore-ui-kit-mobile";
+import {
+    useNavigation
+} from "@react-navigation/native";
+
+const X = [{
+    t: "x",
+    p: "y",
+    n: 90
+}];
+
+const Home = () => {
+    const {
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const bottomSheetRef = useRef<IBottomSheetRef>(null);
+
+    const navigation = useNavigation();
+
+    return <View
+        style={[
+            stylesheet.container,
+            {
+                backgroundColor: colors.content.container.default
+            }
+        ]}
+    >
+        <SelectBox
+            keyExtractor={(data, index) => `${data.t}-${index}`}
+            titleExtractor={(data) => data.t}
+            subTitle="Deneme Subtitle"
+            hintText="Test deneme"
+            isShowSubTitle={true}
+            initialSelectedItems={[{
+                __title: X[0]?.t as string,
+                __key: `${X[0]?.t}-0`,
+                __originalIndex: 0
+            }]}
+            title="Deneme Box"
+            isRequired={true}
+            data={X}
+        />
+        <TextInput
+            variant="hidden"
+        />
+        <Text>Version: v{getNCoreUIKitVersion()}</Text>
+        <Button
+            onPress={() => {
+                navigation.navigate("TestSubPage");
+            }}
+            type="success"
+            title="Open Test Sub Page"
+            variant="filled"
+        />
+        <Button
+            onPress={() => {
+                bottomSheetRef.current?.open();
+            }}
+            type="success"
+            title="Open BottomSheet"
+            variant="filled"
+        />
+        <BottomSheet
+            isWorkAsFullScreen={true}
+            ref={bottomSheetRef}
+            renderHeader={() => {
+                return <View
+                    style={[
+                        {
+                            backgroundColor: "blue",
+                            padding: 20
+                        }
+                    ]}
+                >
+                    <Text>Burası header.</Text>
+                </View>;
+            }}
+            // snapPoint={300}
+            key="ahmet"
+        >
+            <Button
+                onPress={() => {
+                    navigation.navigate("TestSubPage");
+                }}
+                title="Git."
+            />
+            <View
+                style={{
+                    backgroundColor: "red",
+                    height: 2400
+                }}
+            />
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+        </BottomSheet>
+    </View>;
+};
+export default Home;

+ 12 - 0
example/src/pages/home/stylesheet.ts

@@ -0,0 +1,12 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        justifyContent: "center",
+        alignItems: "center",
+        flex: 1
+    }
+});
+export default stylesheet;

+ 61 - 0
example/src/pages/testSubPage/index.tsx

@@ -0,0 +1,61 @@
+import {
+    useRef
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    type IBottomSheetRef,
+    PageContainer,
+    BottomSheet,
+    Button,
+    Text
+} from "ncore-ui-kit-mobile";
+
+const TestSubPage = () => {
+    const bottomSheetRef = useRef<IBottomSheetRef>(null);
+
+    return <PageContainer
+        style={[
+            stylesheet.container
+        ]}
+    >
+        <Text>This is test sub page.</Text>
+        <Button
+            onPress={() => {
+                bottomSheetRef.current?.open();
+            }}
+            type="success"
+            title="Ahmet"
+            variant="filled"
+        />
+        <BottomSheet
+            isCanFullScreenOnSwipe={true}
+            isWorkWithPortal={false}
+            ref={bottomSheetRef}
+            snapPoint={300}
+        >
+            <Button
+                onPress={() => {
+
+                }}
+                title="Git."
+            />
+            <View
+                style={{
+                    backgroundColor: "red",
+                    height: 2400
+                }}
+            />
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+            <Text>Deneme 123</Text>
+        </BottomSheet>
+    </PageContainer>;
+};
+export default TestSubPage;

+ 10 - 0
example/src/pages/testSubPage/stylesheet.ts

@@ -0,0 +1,10 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flex: 1
+    }
+});
+export default stylesheet;

+ 139 - 42
src/components/bottomSheet/index.tsx

@@ -36,6 +36,7 @@ import {
 import Modal from "../modal";
 
 const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
+    renderHeader: RenderHeaderComponent,
     renderBottom: RenderBottomComponent,
     isForceFullScreenOnSwipe = false,
     isCanFullScreenOnSwipe = false,
@@ -44,12 +45,16 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     isWrapSafeareaContext = true,
     backgroundColor = "default",
     isWorkAsFullScreen = false,
+    isWorkWithPortal = true,
     isCloseOnOverlay = true,
     handleContainerSpacing,
     isActive: isActiveProp,
     handleBackgroundColor,
     isAutoHeight = false,
     isShowHandle = true,
+    isSwipeClose = true,
+    isCanSwipe = true,
+    onOverlayPressed,
     snapPoint,
     children,
     onClosed,
@@ -57,6 +62,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     onClose,
     onOpen,
     style,
+    key,
     ...props
 }, ref) => {
     const {
@@ -82,27 +88,29 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     let bottomSafeArea = isWrapSafeareaContext ? bottom : 0;
     let topSafeArea = isWrapSafeareaContext ? top : 0;
 
-    if(isWorkAsFullScreen) {
-        bottomSafeArea = 0;
+    if(isForceFullScreenOnSwipe) {
         topSafeArea = 0;
     }
 
-    if(isForceFullScreenOnSwipe) {
+    if(!isWorkWithPortal) {
+        bottomSafeArea = 0;
         topSafeArea = 0;
     }
 
     const scrollViewRef = useRef<ComponentRef<ScrollView>>(null);
     const modalRef = useRef<IModalRef>(null);
 
-    const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : windowHeight)).current;
+    const containerHeightRef = useRef(windowHeight);
+
+    const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : containerHeightRef.current)).current;
     const animatedHeight = useRef(new Animated.Value(
         isAutoHeight ? 0 : snapPoint ?? 0
     )).current;
 
     const TOP_GRAB_AREA = 140;
 
-    const maxHeight = useRef(isWorkAsFullScreen ? windowHeight : windowHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0));
-    const heightValue = useRef(isWorkAsFullScreen ? windowHeight : snapPoint ?? 0);
+    const maxHeight = useRef(isWorkAsFullScreen ? containerHeightRef.current - (isWrapSafeareaContext ? topSafeArea : 0) : containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0));
+    const heightValue = useRef(isWorkAsFullScreen ? containerHeightRef.current : snapPoint ?? 0);
     const initialTranslateY = useRef(0);
     const translateYValue = useRef(0);
     const contentHeight = useRef(-1);
@@ -115,6 +123,11 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
 
     const gestureStartY = useRef(0);
 
+    const isCanFullScreenOnSwipeRef = useRef(isCanFullScreenOnSwipe);
+    const isWorkAsFullScreenRef = useRef(isWorkAsFullScreen);
+    const isSwipeCloseRef = useRef(isSwipeClose);
+    const isCanSwipeRef = useRef(isCanSwipe);
+
     if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
         maxHeight.current = snapPoint;
     }
@@ -132,10 +145,26 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         []
     );
 
+    useEffect(() => {
+        isCanSwipeRef.current = isCanSwipe;
+    }, [isCanSwipe]);
+
+    useEffect(() => {
+        isSwipeCloseRef.current = isSwipeClose;
+    }, [isSwipeClose]);
+
+    useEffect(() => {
+        isCanFullScreenOnSwipeRef.current = isCanFullScreenOnSwipe;
+    }, [isCanFullScreenOnSwipe]);
+
+    useEffect(() => {
+        isWorkAsFullScreenRef.current = isWorkAsFullScreen;
+    }, [isWorkAsFullScreen]);
+
     useEffect(() => {
         if(isMeasured && contentHeight.current !== -1) {
             if(isCanFullScreenOnSwipe && !isWorkAsFullScreen) {
-                maxHeight.current = windowHeight - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
+                maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
             } else if(isAutoHeight || !snapPoint) {
                 maxHeight.current = contentHeight.current;
             }
@@ -151,6 +180,23 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         isMeasured
     ]);
 
+    useEffect(() => {
+        if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
+            maxHeight.current = snapPoint;
+        } else if (!isWorkAsFullScreen) {
+            maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0);
+        } else {
+            maxHeight.current = containerHeightRef.current - (isWrapSafeareaContext ? topSafeArea : 0);
+        }
+    }, [
+        isForceFullScreenOnSwipe,
+        isCanFullScreenOnSwipe,
+        isWrapSafeareaContext,
+        isWorkAsFullScreen,
+        topSafeArea,
+        snapPoint
+    ]);
+
     useEffect(() => {
         const listenerAHeightId = animatedHeight.addListener(({
             value
@@ -237,8 +283,10 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     const panResponder = useRef(
         PanResponder.create({
             onStartShouldSetPanResponder: () => false,
-            onMoveShouldSetPanResponderCapture: () => true,
+            onMoveShouldSetPanResponderCapture: () => isCanSwipeRef.current ? true : false,
             onPanResponderGrant: (evt) => {
+                if(!isCanSwipeRef.current) return;
+
                 gestureStartY.current = evt.nativeEvent.pageY;
 
                 animatedTranslateY.stopAnimation((currentY) => {
@@ -263,6 +311,8 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 animatedTranslateY.setValue(0);
             },
             onPanResponderMove: (_, gestureState) => {
+                if(!isCanSwipeRef.current) return;
+
                 const {
                     dy
                 } = gestureState;
@@ -303,13 +353,11 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 if (dy < 0) {
                     let currentDelta = delta;
 
-                    if (initialT > 0) {
-                        const usedForT = Math.min(currentDelta, initialT);
-
-                        animatedTranslateY.setValue(-usedForT);
+                    const effectiveInitialT = Math.max(0, initialT);
+                    const usedForT = Math.min(currentDelta, effectiveInitialT);
 
-                        currentDelta -= usedForT;
-                    }
+                    animatedTranslateY.setValue(-usedForT);
+                    currentDelta -= usedForT;
 
                     if (currentDelta > 0) {
                         if (initialH < pivot) {
@@ -321,7 +369,10 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                             currentDelta -= usedForH;
                         }
 
-                        if (currentDelta > 0) {
+                        const isRestoringHeight = initialH < pivot;
+                        const canScroll = isCanFullScreenOnSwipeRef.current || isWorkAsFullScreenRef.current || !isRestoringHeight;
+
+                        if (currentDelta > 0 && canScroll) {
                             const remainingScroll = maxS - initialS;
                             if (remainingScroll > 0) {
                                 const usedForS = Math.min(currentDelta, remainingScroll);
@@ -339,7 +390,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                         }
 
                         if (currentDelta > 0) {
-                            if (isCanFullScreenOnSwipe) {
+                            if (isCanFullScreenOnSwipeRef.current) {
                                 const totalUsedBefore = (initialH < pivot ? (pivot - initialH) : 0);
 
                                 animatedHeight.setValue(totalUsedBefore + currentDelta);
@@ -390,6 +441,8 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 }
             },
             onPanResponderEnd: (_, gestureState) => {
+                if(!isCanSwipeRef.current) return;
+
                 const isAtTop = scrollOffset.current <= 1;
                 const isAtTavan = heightValue.current >= maxHeight.current - 1;
                 const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
@@ -423,12 +476,12 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 if (currentT <= 0.5) {
                     let toValue = pivot;
 
-                    if (isFastSwipeUp && isCanFullScreenOnSwipe) {
+                    if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
                         toValue = tavan;
                     } else if (isFastSwipeDown) {
                         toValue = pivot;
                     } else {
-                        if (!isCanFullScreenOnSwipe) {
+                        if (!isCanFullScreenOnSwipeRef.current) {
                             toValue = pivot;
                         } else {
                             const totalRange = tavan - pivot;
@@ -454,40 +507,55 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                     let toValueT = 0;
                     let isClosing = false;
 
-                    if (isFastSwipeDown) {
-                        toValueT = pivot + 100;
-
-                        isClosing = true;
+                    if (!isSwipeCloseRef.current) {
+                        Animated.spring(animatedHeight, {
+                            useNativeDriver: false,
+                            toValue: pivot,
+                            friction: 10,
+                            tension: 40
+                        }).start();
+
+                        Animated.timing(animatedTranslateY, {
+                            useNativeDriver: false,
+                            duration: 300,
+                            toValue: 0
+                        }).start();
                     } else {
-                        if (currentT > pivot * 0.5) {
+                        if (isFastSwipeDown) {
                             toValueT = pivot + 100;
 
                             isClosing = true;
                         } else {
-                            toValueT = 0;
+                            if (currentT > pivot * 0.5) {
+                                toValueT = pivot + 100;
 
-                            isClosing = false;
-                        }
-                    }
+                                isClosing = true;
+                            } else {
+                                toValueT = 0;
 
-                    Animated.timing(animatedTranslateY, {
-                        useNativeDriver: false,
-                        toValue: toValueT,
-                        duration: 300
-                    }).start(({
-                        finished
-                    }) => {
-                        if (finished && isClosing) {
-                            setIsActive(false);
-
-                            if (onClosed) onClosed();
+                                isClosing = false;
+                            }
                         }
-                    });
+
+                        Animated.timing(animatedTranslateY, {
+                            useNativeDriver: false,
+                            toValue: toValueT,
+                            duration: 300
+                        }).start(({
+                            finished
+                        }) => {
+                            if (finished && isClosing) {
+                                setIsActive(false);
+
+                                if (onClosed) onClosed();
+                            }
+                        });
+                    }
                 }
             },
             onPanResponderTerminationRequest: () => false,
             onShouldBlockNativeResponder: () => true
-        }),
+        })
     ).current;
 
     const renderView = () => {
@@ -513,6 +581,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 style
             ]}
         >
+            {renderHeader()}
             <ScrollView
                 onContentSizeChange={(w, h) => {
                     scrollViewContentHeight.current = h;
@@ -524,9 +593,9 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 onMoveShouldSetResponderCapture={() => false}
                 showsHorizontalScrollIndicator={false}
                 showsVerticalScrollIndicator={false}
+                scrollEnabled={!isCanSwipe}
                 scrollEventThrottle={1}
                 overScrollMode="never"
-                scrollEnabled={false}
                 ref={scrollViewRef}
                 bounces={false}
             >
@@ -537,6 +606,14 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         </Animated.View>;
     };
 
+    const renderHeader = () => {
+        if(!RenderHeaderComponent) {
+            return null;
+        }
+
+        return <RenderHeaderComponent/>;
+    };
+
     const renderBottom = () => {
         if(!RenderBottomComponent) {
             return null;
@@ -546,7 +623,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     };
 
     const renderHandle = () => {
-        if(!isShowHandle) {
+        if(!isShowHandle || isWorkAsFullScreen) {
             return null;
         }
 
@@ -587,15 +664,35 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     };
 
     return <Modal
+        isWorkWithPortal={isWorkWithPortal}
         isContentRequired={false}
+        key={`${key}-modal`}
         isActive={isActive}
+        alignContent="free"
         isAnimated={false}
         ref={modalRef}
+        onContainerLayout={(e) => {
+            const containerLayoutHeight = e.nativeEvent.layout.height;
+
+            if (containerLayoutHeight && containerLayoutHeight !== containerHeightRef.current) {
+                containerHeightRef.current = containerLayoutHeight;
+
+                if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
+                    maxHeight.current = snapPoint;
+                } else if (!isWorkAsFullScreen) {
+                    maxHeight.current = containerLayoutHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeareaContext ? topSafeArea : 0);
+                } else {
+                    maxHeight.current = containerLayoutHeight;
+                }
+            }
+        }}
         overlayProps={{
             onStartShouldSetResponderCapture: () => false,
             onMoveShouldSetResponderCapture: () => false
         }}
         onOverlayPress={() => {
+            if(onOverlayPressed) onOverlayPressed();
+
             if(isCloseOnOverlay) {
                 closeAnimation();
             }

+ 6 - 0
src/components/bottomSheet/type.ts

@@ -19,20 +19,26 @@ interface IBottomSheetProps {
     isForceFullScreenOnSwipe?: boolean;
     isCanFullScreenOnSwipe?: boolean;
     isWrapSafeareaContext?: boolean;
+    renderHeader?: () => ReactNode;
     renderBottom?: () => ReactNode;
+    onOverlayPressed?: () => void;
     isWorkAsFullScreen?: boolean;
     style?: StyleProp<ViewStyle>;
     isCloseOnOverlay?: boolean;
+    isWorkWithPortal?: boolean;
+    isSwipeClose?: boolean;
     isShowHandle?: boolean;
     isAutoHeight?: boolean;
     handleHeight?: number;
     onClosed?: () => void;
     onOpened?: () => void;
+    isCanSwipe?: boolean;
     onClose?: () => void;
     children?: ReactNode;
     onOpen?: () => void;
     snapPoint?: number;
     isActive?: boolean;
+    key: string;
 }
 export type {
     IBottomSheetProps as default

+ 31 - 18
src/components/modal/index.tsx

@@ -36,7 +36,9 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
     isContentRequired = true,
     isOverlayVisible = true,
     alignContent = "center",
+    isWorkWithPortal = true,
     isActive: isActiveProp,
+    onContainerLayout,
     isAnimated = true,
     onOverlayPress,
     contentStyle,
@@ -129,6 +131,28 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
         </TouchableWithoutFeedback>;
     };
 
+    const renderContainer = () => {
+        return <Animated.View
+            onLayout={(e) => {
+                if (onContainerLayout) onContainerLayout(e);
+            }}
+            style={[
+                style,
+                stylesheet.container,
+                {
+                    justifyContent: alignContent === "center" ? "center" : undefined,
+                    alignItems: alignContent === "center" ? "center" : "baseline",
+                    transform: [{
+                        scale: scaleAnim
+                    }]
+                }
+            ]}
+        >
+            {renderOverlay()}
+            {isContentRequired ? renderContent() : children}
+        </Animated.View>;
+    };
+
     const renderContent = () => {
         if(!children) {
             return null;
@@ -144,27 +168,16 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
         </View>;
     };
 
+    const renderWithPortal = () => {
+        return <Portal>
+            {renderContainer()}
+        </Portal>;
+    };
+
     if(!isActive) {
         return null;
     }
 
-    return <Portal>
-        <Animated.View
-            style={[
-                style,
-                stylesheet.container,
-                {
-                    justifyContent: alignContent === "center" ? "center" : undefined,
-                    alignItems: alignContent === "center" ? "center" : "baseline",
-                    transform: [{
-                        scale: scaleAnim
-                    }]
-                }
-            ]}
-        >
-            {renderOverlay()}
-            {isContentRequired ? renderContent() : children}
-        </Animated.View>
-    </Portal>;
+    return isWorkWithPortal ? renderWithPortal() : renderContainer();
 };
 export default forwardRef(Modal);

+ 3 - 0
src/components/modal/type.ts

@@ -2,6 +2,7 @@ import {
     type ReactNode
 } from "react";
 import {
+    type LayoutChangeEvent,
     type StyleProp,
     type ViewProps,
     type ViewStyle
@@ -20,10 +21,12 @@ export type ModalInternalProps = {
     }) => void;
     contentStyle?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
     style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    onContainerLayout?: (e: LayoutChangeEvent) => void;
     alignContent?: ModalAlignContentProp;
     isDisabledOverlay?: boolean;
     isContentRequired?: boolean;
     isOverlayVisible?: boolean;
+    isWorkWithPortal?: boolean;
     overlayProps?: ViewProps;
     isAnimated?: boolean;
     children?: ReactNode;

+ 1 - 1
src/components/pageContainer/index.tsx

@@ -16,7 +16,7 @@ import {
 
 const PageContainer: FC<IPageContainerProps> = ({
     backgroundColor = "default" as keyof NCoreUIKit.ContainerContentColors,
-    isWrapSafeareaContext = true,
+    isWrapSafeareaContext = false,
     safeAreaViewBackgroundColor,
     isScrollable = false,
     safeAreaViewStyle,

+ 3 - 0
src/core/index.tsx

@@ -11,6 +11,9 @@ import {
     SafeAreaProvider
 } from "react-native-safe-area-context";
 import CoreContext from "../context";
+import packageJSON from "../../package.json";
+
+export const getNCoreUIKitVersion = () => packageJSON.version;
 
 export class NCoreUIKitBase<T extends NCoreUIKitConfig> {
     NCoreUIKitContext: CoreContext<T>;

+ 1 - 0
src/index.tsx

@@ -1,4 +1,5 @@
 export {
+    getNCoreUIKitVersion,
     setupNCoreUIKit
 } from "./core";
 

+ 212 - 4
yarn.lock

@@ -3605,6 +3605,87 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@react-navigation/core@npm:^7.17.2":
+  version: 7.17.2
+  resolution: "@react-navigation/core@npm:7.17.2"
+  dependencies:
+    "@react-navigation/routers": "npm:^7.5.3"
+    escape-string-regexp: "npm:^4.0.0"
+    fast-deep-equal: "npm:^3.1.3"
+    nanoid: "npm:^3.3.11"
+    query-string: "npm:^7.1.3"
+    react-is: "npm:^19.1.0"
+    use-latest-callback: "npm:^0.2.4"
+    use-sync-external-store: "npm:^1.5.0"
+  peerDependencies:
+    react: ">= 18.2.0"
+  checksum: 10c0/df1889769e90f85b71605070818b22bd9967f5fd0572187d31e6a17386339336c8890b9c7ba505854382426cb3b5be8d03ea4684da4ad5be2cdae828302b98a0
+  languageName: node
+  linkType: hard
+
+"@react-navigation/elements@npm:^2.9.14":
+  version: 2.9.14
+  resolution: "@react-navigation/elements@npm:2.9.14"
+  dependencies:
+    color: "npm:^4.2.3"
+    use-latest-callback: "npm:^0.2.4"
+    use-sync-external-store: "npm:^1.5.0"
+  peerDependencies:
+    "@react-native-masked-view/masked-view": ">= 0.2.0"
+    "@react-navigation/native": ^7.2.2
+    react: ">= 18.2.0"
+    react-native: "*"
+    react-native-safe-area-context: ">= 4.0.0"
+  peerDependenciesMeta:
+    "@react-native-masked-view/masked-view":
+      optional: true
+  checksum: 10c0/c2602f5be41caaad8ea81a9bdfde654d4a89d83c627a910f5d166bd8cac2700399fce805f6bf15faaf06c2bbc776325269bc0ec1fbbe917b615e17a0a5d371e1
+  languageName: node
+  linkType: hard
+
+"@react-navigation/native-stack@npm:^7.14.11":
+  version: 7.14.11
+  resolution: "@react-navigation/native-stack@npm:7.14.11"
+  dependencies:
+    "@react-navigation/elements": "npm:^2.9.14"
+    color: "npm:^4.2.3"
+    sf-symbols-typescript: "npm:^2.1.0"
+    warn-once: "npm:^0.1.1"
+  peerDependencies:
+    "@react-navigation/native": ^7.2.2
+    react: ">= 18.2.0"
+    react-native: "*"
+    react-native-safe-area-context: ">= 4.0.0"
+    react-native-screens: ">= 4.0.0"
+  checksum: 10c0/997070ab2350629c6c3b8cfe97bc0fd918caf260343f5cf0dbab061cc0ed6444f36af1c272949ce9893c7fc93b89f8ec1d3e0c6cfd81182d4c81cab0e816aadb
+  languageName: node
+  linkType: hard
+
+"@react-navigation/native@npm:^7.2.2":
+  version: 7.2.2
+  resolution: "@react-navigation/native@npm:7.2.2"
+  dependencies:
+    "@react-navigation/core": "npm:^7.17.2"
+    escape-string-regexp: "npm:^4.0.0"
+    fast-deep-equal: "npm:^3.1.3"
+    nanoid: "npm:^3.3.11"
+    use-latest-callback: "npm:^0.2.4"
+  peerDependencies:
+    react: ">= 18.2.0"
+    react-native: "*"
+  checksum: 10c0/3ca6e742da2ed4110b81fc008536ca62f07cdf49b368e9b7f73cbc25ad86603f87f14d08492bf1de2647ca6f4cf7141bec4fa3cd76961fa2fa22f83d0f805e83
+  languageName: node
+  linkType: hard
+
+"@react-navigation/routers@npm:^7.5.3":
+  version: 7.5.3
+  resolution: "@react-navigation/routers@npm:7.5.3"
+  dependencies:
+    nanoid: "npm:^3.3.11"
+  checksum: 10c0/85f6cb9ac71e0492845aa87637c7c745d85aa15e4ad7e71a8d910080f5d5a469dd348f59ffaaed8c488cb92708fae56350a0bfc7bc5750c65e12da1f0d4eca70
+  languageName: node
+  linkType: hard
+
 "@release-it/conventional-changelog@npm:10.0.1":
   version: 10.0.1
   resolution: "@release-it/conventional-changelog@npm:10.0.1"
@@ -6630,13 +6711,23 @@ __metadata:
   languageName: node
   linkType: hard
 
-"color-name@npm:~1.1.4":
+"color-name@npm:^1.0.0, color-name@npm:~1.1.4":
   version: 1.1.4
   resolution: "color-name@npm:1.1.4"
   checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95
   languageName: node
   linkType: hard
 
+"color-string@npm:^1.9.0":
+  version: 1.9.1
+  resolution: "color-string@npm:1.9.1"
+  dependencies:
+    color-name: "npm:^1.0.0"
+    simple-swizzle: "npm:^0.2.2"
+  checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404
+  languageName: node
+  linkType: hard
+
 "color-support@npm:^1.1.2":
   version: 1.1.3
   resolution: "color-support@npm:1.1.3"
@@ -6646,6 +6737,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"color@npm:^4.2.3":
+  version: 4.2.3
+  resolution: "color@npm:4.2.3"
+  dependencies:
+    color-convert: "npm:^2.0.1"
+    color-string: "npm:^1.9.0"
+  checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118
+  languageName: node
+  linkType: hard
+
 "comma-separated-tokens@npm:^1.0.0":
   version: 1.0.8
   resolution: "comma-separated-tokens@npm:1.0.8"
@@ -7314,7 +7415,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"decode-uri-component@npm:^0.2.0":
+"decode-uri-component@npm:^0.2.0, decode-uri-component@npm:^0.2.2":
   version: 0.2.2
   resolution: "decode-uri-component@npm:0.2.2"
   checksum: 10c0/1f4fa54eb740414a816b3f6c24818fbfcabd74ac478391e9f4e2282c994127db02010ce804f3d08e38255493cfe68608b3f5c8e09fd6efc4ae46c807691f7a31
@@ -9001,6 +9102,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"filter-obj@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "filter-obj@npm:1.1.0"
+  checksum: 10c0/071e0886b2b50238ca5026c5bbf58c26a7c1a1f720773b8c7813d16ba93d0200de977af14ac143c5ac18f666b2cfc83073f3a5fe6a4e996c49e0863d5500fccf
+  languageName: node
+  linkType: hard
+
 "finalhandler@npm:1.1.2":
   version: 1.1.2
   resolution: "finalhandler@npm:1.1.2"
@@ -10411,6 +10519,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"is-arrayish@npm:^0.3.1":
+  version: 0.3.4
+  resolution: "is-arrayish@npm:0.3.4"
+  checksum: 10c0/1fa672a2f0bedb74154440310f616c0b6e53a95cf0625522ae050f06626d1cabd1a3d8085c882dc45c61ad0e7df2529aff122810b3b4a552880bf170d6df94e0
+  languageName: node
+  linkType: hard
+
 "is-async-function@npm:^2.0.0":
   version: 2.1.1
   resolution: "is-async-function@npm:2.1.1"
@@ -12985,7 +13100,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"nanoid@npm:^3.3.7":
+"nanoid@npm:^3.3.11, nanoid@npm:^3.3.7":
   version: 3.3.11
   resolution: "nanoid@npm:3.3.11"
   bin:
@@ -13047,6 +13162,9 @@ __metadata:
   dependencies:
     "@expo/metro-runtime": "npm:~55.0.6"
     "@react-native/metro-config": "npm:0.84.1"
+    "@react-navigation/elements": "npm:^2.9.14"
+    "@react-navigation/native": "npm:^7.2.2"
+    "@react-navigation/native-stack": "npm:^7.14.11"
     "@types/react-native": "npm:0.73.0"
     expo: "npm:~55.0.4"
     expo-font: "npm:~55.0.4"
@@ -13059,6 +13177,7 @@ __metadata:
     react-native-monorepo-config: "npm:0.3.3"
     react-native-portalize: "npm:^1.0.7"
     react-native-safe-area-context: "npm:^5.7.0"
+    react-native-screens: "npm:^4.24.0"
     react-native-svg: "npm:^15.15.3"
     react-native-web: "npm:~0.21.0"
   languageName: unknown
@@ -14453,6 +14572,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"query-string@npm:^7.1.3":
+  version: 7.1.3
+  resolution: "query-string@npm:7.1.3"
+  dependencies:
+    decode-uri-component: "npm:^0.2.2"
+    filter-obj: "npm:^1.1.0"
+    split-on-first: "npm:^1.0.0"
+    strict-uri-encode: "npm:^2.0.0"
+  checksum: 10c0/a896c08e9e0d4f8ffd89a572d11f668c8d0f7df9c27c6f49b92ab31366d3ba0e9c331b9a620ee747893436cd1f2f821a6327e2bc9776bde2402ac6c270b801b2
+  languageName: node
+  linkType: hard
+
 "querystring-es3@npm:^0.2.0":
   version: 0.2.1
   resolution: "querystring-es3@npm:0.2.1"
@@ -14552,6 +14683,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-freeze@npm:^1.0.0":
+  version: 1.0.4
+  resolution: "react-freeze@npm:1.0.4"
+  peerDependencies:
+    react: ">=17.0.0"
+  checksum: 10c0/8f51257c261bfefff86f618e958683536248f708019632d309ee5ebdd52f25d3c130660d06fb6f0f4fdef79f00f8ec7177233a872c2321f7d46b7e77ccc522a1
+  languageName: node
+  linkType: hard
+
 "react-is@npm:^16.13.1":
   version: 16.13.1
   resolution: "react-is@npm:16.13.1"
@@ -14566,6 +14706,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-is@npm:^19.1.0":
+  version: 19.2.5
+  resolution: "react-is@npm:19.2.5"
+  checksum: 10c0/58c28ab2f7f868f9836c728fa164a16dcf20246ab84a3ae6a09e617b0e4d9bbb3acdddfdc40452f0134d27b121d3359227602a050c0be554cb20069cb52a67f4
+  languageName: node
+  linkType: hard
+
 "react-native-builder-bob@npm:0.40.13":
   version: 0.40.13
   resolution: "react-native-builder-bob@npm:0.40.13"
@@ -14648,6 +14795,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-native-screens@npm:^4.24.0":
+  version: 4.24.0
+  resolution: "react-native-screens@npm:4.24.0"
+  dependencies:
+    react-freeze: "npm:^1.0.0"
+    warn-once: "npm:^0.1.0"
+  peerDependencies:
+    react: "*"
+    react-native: "*"
+  checksum: 10c0/49299e694ed20cf5a8c0f86702eb77df03f5b7353d9b73e7415bb593ac5ff3aa4dfe76b1e9d8efb0fd926917e507f02f13e0a8791c8833910080454b66d99943
+  languageName: node
+  linkType: hard
+
 "react-native-svg@npm:15.15.3, react-native-svg@npm:^15.15.3":
   version: 15.15.3
   resolution: "react-native-svg@npm:15.15.3"
@@ -15628,6 +15788,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"sf-symbols-typescript@npm:^2.1.0":
+  version: 2.2.0
+  resolution: "sf-symbols-typescript@npm:2.2.0"
+  checksum: 10c0/3f3bbf33aaad19e619d6f169899b39e9fe9c5fd21f0d6d511100e36887606ad349109ddc6ff82933f2b8cbf437dd7105c2ae6b0059b291dc47f143b30c2074cc
+  languageName: node
+  linkType: hard
+
 "sha.js@npm:^2.4.0, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8":
   version: 2.4.12
   resolution: "sha.js@npm:2.4.12"
@@ -15762,6 +15929,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"simple-swizzle@npm:^0.2.2":
+  version: 0.2.4
+  resolution: "simple-swizzle@npm:0.2.4"
+  dependencies:
+    is-arrayish: "npm:^0.3.1"
+  checksum: 10c0/846c3fdd1325318d5c71295cfbb99bfc9edc4c8dffdda5e6e9efe30482bbcd32cf360fc2806f46ac43ff7d09bcfaff20337bb79f826f0e6a8e366efd3cdd7868
+  languageName: node
+  linkType: hard
+
 "sisteransi@npm:^1.0.5":
   version: 1.0.5
   resolution: "sisteransi@npm:1.0.5"
@@ -15953,6 +16129,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"split-on-first@npm:^1.0.0":
+  version: 1.1.0
+  resolution: "split-on-first@npm:1.1.0"
+  checksum: 10c0/56df8344f5a5de8521898a5c090023df1d8b8c75be6228f56c52491e0fc1617a5236f2ac3a066adb67a73231eac216ccea7b5b4a2423a543c277cb2f48d24c29
+  languageName: node
+  linkType: hard
+
 "split-string@npm:^3.0.1, split-string@npm:^3.0.2":
   version: 3.1.0
   resolution: "split-string@npm:3.1.0"
@@ -16128,6 +16311,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"strict-uri-encode@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "strict-uri-encode@npm:2.0.0"
+  checksum: 10c0/010cbc78da0e2cf833b0f5dc769e21ae74cdc5d5f5bd555f14a4a4876c8ad2c85ab8b5bdf9a722dc71a11dcd3184085e1c3c0bd50ec6bb85fffc0f28cf82597d
+  languageName: node
+  linkType: hard
+
 "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
   version: 4.2.3
   resolution: "string-width@npm:4.2.3"
@@ -17344,6 +17534,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"use-latest-callback@npm:^0.2.4":
+  version: 0.2.6
+  resolution: "use-latest-callback@npm:0.2.6"
+  peerDependencies:
+    react: ">=16.8"
+  checksum: 10c0/6523747b2d76f12a91cf80a3cd9803449571e9defa8db69e9a03b8199b211127d88c038063714fe31d3c2e63ca51a491bd05f4e34203795a1c692a5a44416610
+  languageName: node
+  linkType: hard
+
+"use-sync-external-store@npm:^1.5.0":
+  version: 1.6.0
+  resolution: "use-sync-external-store@npm:1.6.0"
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+  checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b
+  languageName: node
+  linkType: hard
+
 "use@npm:^3.1.0":
   version: 3.1.1
   resolution: "use@npm:3.1.1"
@@ -17468,7 +17676,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"warn-once@npm:0.1.1":
+"warn-once@npm:0.1.1, warn-once@npm:^0.1.0, warn-once@npm:^0.1.1":
   version: 0.1.1
   resolution: "warn-once@npm:0.1.1"
   checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7