Quellcode durchsuchen

Merge branch 'develop' of nibgat-community/ncore-ui-kit-mobile into main

Furkan Atakan BOZKURT vor 1 Monat
Ursprung
Commit
73a3ad8457
57 geänderte Dateien mit 4296 neuen und 131 gelöschten Zeilen
  1. 7 8
      example/app.json
  2. BIN
      example/assets/adaptive-icon.png
  3. BIN
      example/assets/android-icon-background.png
  4. BIN
      example/assets/android-icon-foreground.png
  5. BIN
      example/assets/android-icon-monochrome.png
  6. BIN
      example/assets/favicon.png
  7. BIN
      example/assets/icon.png
  8. BIN
      example/assets/partial-react-logo.png
  9. BIN
      example/assets/react-logo.png
  10. BIN
      example/assets/react-logo@2x.png
  11. BIN
      example/assets/react-logo@3x.png
  12. BIN
      example/assets/splash-icon-dark.png
  13. BIN
      example/assets/splash-icon.png
  14. BIN
      example/assets/splash.png
  15. 5 0
      example/package.json
  16. 6 45
      example/src/index.tsx
  17. 32 0
      example/src/navigation/index.tsx
  18. 7 0
      example/src/navigation/type.ts
  19. 348 0
      example/src/pages/home/index.tsx
  20. 12 0
      example/src/pages/home/stylesheet.ts
  21. 61 0
      example/src/pages/testSubPage/index.tsx
  22. 10 0
      example/src/pages/testSubPage/stylesheet.ts
  23. 3 3
      src/assets/svg/loadingIcon/index.tsx
  24. 851 0
      src/components/bottomSheet/index.tsx
  25. 28 0
      src/components/bottomSheet/stylesheet.ts
  26. 58 0
      src/components/bottomSheet/type.ts
  27. 2 1
      src/components/button/index.tsx
  28. 6 4
      src/components/button/stylesheet.ts
  29. 6 0
      src/components/button/type.ts
  30. 265 0
      src/components/checkBox/index.tsx
  31. 223 0
      src/components/checkBox/stylesheet.ts
  32. 70 0
      src/components/checkBox/type.ts
  33. 17 2
      src/components/index.ts
  34. 2 2
      src/components/loading/index.tsx
  35. 92 42
      src/components/modal/index.tsx
  36. 10 2
      src/components/modal/stylesheet.ts
  37. 15 0
      src/components/modal/type.ts
  38. 3 2
      src/components/pageContainer/index.tsx
  39. 6 0
      src/components/pageContainer/type.ts
  40. 848 0
      src/components/selectBox/index.tsx
  41. 245 0
      src/components/selectBox/stylesheet.ts
  42. 182 0
      src/components/selectBox/type.ts
  43. 368 0
      src/components/selectSheet/index.tsx
  44. 102 0
      src/components/selectSheet/stylesheet.ts
  45. 102 0
      src/components/selectSheet/type.ts
  46. 2 1
      src/components/text/index.tsx
  47. 6 0
      src/components/text/type.ts
  48. 23 12
      src/components/textInput/index.tsx
  49. 10 1
      src/components/textInput/stylesheet.ts
  50. 10 0
      src/components/textInput/type.ts
  51. 3 0
      src/core/index.tsx
  52. 6 0
      src/index.tsx
  53. 2 0
      src/types/index.ts
  54. 6 0
      src/utils/index.ts
  55. 10 0
      src/variants/locales/default.json
  56. 2 2
      src/variants/themes/default.json
  57. 224 4
      yarn.lock

+ 7 - 8
example/app.json

@@ -1,15 +1,15 @@
 {
     "expo": {
-        "name": "NcoreUiKitMobile Example",
+        "name": "NCore | Ui Kit - Mobile Example",
         "slug": "ncore-ui-kit-mobile-example",
         "version": "1.0.0",
         "orientation": "portrait",
         "icon": "./assets/icon.png",
-        "userInterfaceStyle": "light",
+        "userInterfaceStyle": "dark",
         "splash": {
             "image": "./assets/splash-icon.png",
             "resizeMode": "contain",
-            "backgroundColor": "#ffffff"
+            "backgroundColor": "#222222"
         },
         "ios": {
             "supportsTablet": true,
@@ -17,12 +17,11 @@
         },
         "android": {
             "adaptiveIcon": {
-                "backgroundColor": "#E6F4FE",
-                "foregroundImage": "./assets/android-icon-foreground.png",
-                "backgroundImage": "./assets/android-icon-background.png",
-                "monochromeImage": "./assets/android-icon-monochrome.png"
+                "backgroundColor": "#222222",
+                "foregroundImage": "./assets/icon.png"
             },
-            "package": "ncoreuikitmobile.example"
+            "package": "ncoreuikitmobile.example",
+            "softwareKeyboardLayoutMode": "resize"
         },
         "web": {
             "favicon": "./assets/favicon.png"

BIN
example/assets/adaptive-icon.png


BIN
example/assets/android-icon-background.png


BIN
example/assets/android-icon-foreground.png


BIN
example/assets/android-icon-monochrome.png


BIN
example/assets/favicon.png


BIN
example/assets/icon.png


BIN
example/assets/partial-react-logo.png


BIN
example/assets/react-logo.png


BIN
example/assets/react-logo@2x.png


BIN
example/assets/react-logo@3x.png


BIN
example/assets/splash-icon-dark.png


BIN
example/assets/splash-icon.png


BIN
example/assets/splash.png


+ 5 - 0
example/package.json

@@ -11,15 +11,20 @@
     },
     "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",
+        "lucide-react-native": "^1.8.0",
         "ncore-context": "^1.0.5",
         "react": "19.2.0",
         "react-dom": "19.2.0",
         "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"
     },

+ 6 - 45
example/src/index.tsx

@@ -1,49 +1,18 @@
-import {
-    StyleSheet,
-    View
-} from "react-native";
-import {
-    setupNCoreUIKit,
-    NCoreUIKitTheme,
-    Button,
-    Text
-} from "ncore-ui-kit-mobile";
+import Navigation from "./navigation";
 import {
     useFonts
 } from "expo-font";
-import packageJSON from "../package.json";
-import TextInput from "../../src/components/textInput";
+import {
+    setupNCoreUIKit
+} from "ncore-ui-kit-mobile";
 
 const NCoreUIKitBase = setupNCoreUIKit({
-    initialSelectedGapPropagation: "compact",
+    initialSelectedGapPropagation: "spacious",
     initialSelectedTheme: "dark"
 });
 
 const App = () => {
-    const {
-        colors
-    } = NCoreUIKitTheme.useContext();
-
-    return <View
-        style={[
-            styles.container,
-            {
-                backgroundColor: colors.content.container.default
-            }
-        ]}
-    >
-        <TextInput
-            variant="hidden"
-        />
-        <Text>Version: v{packageJSON.version}</Text>
-        <Button
-            onPress={() => {
-            }}
-            type="success"
-            title="Ahmet"
-            variant="filled"
-        />
-    </View>;
+    return <Navigation/>;
 };
 
 const ContextAPI = () => {
@@ -66,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;

+ 7 - 0
example/src/navigation/type.ts

@@ -0,0 +1,7 @@
+type RootStackParamList = {
+    TestSubPage: undefined;
+    Home: undefined;
+};
+export type {
+    RootStackParamList as default
+};

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

@@ -0,0 +1,348 @@
+import {
+    useRef,
+    useState
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    type IBottomSheetRef,
+    getNCoreUIKitVersion,
+    NCoreUIKitTheme,
+    BottomSheet,
+    TextInput,
+    SelectBox,
+    Button,
+    Text,
+    CheckBox
+} from "ncore-ui-kit-mobile";
+import {
+    useNavigation
+} from "@react-navigation/native";
+import type {
+    NativeStackNavigationProp
+} from "@react-navigation/native-stack";
+import type RootStackParamList from "../../navigation/type";
+
+const X = [
+    {
+        t: "xdfç.öhfdşkhdfşlhkfdşlhkdfşklhjmdfkhl3",
+        p: "ydfguıy8ı*pğ34erdx",
+        n: 90
+    },
+    {
+        t: "3t4uh5ıe6ıo6eıo6eoke6ıoke6ker6kr4ok",
+        p: "y3w46pjbd57ykdr",
+        n: 91
+    },
+    {
+        t: "xr*-0k4wkh-5w40k0*wek95ujk9*",
+        p: "y467o5*3evghe5",
+        n: 92
+    },
+    {
+        t: "x-40*tyg*0w4kyh*w0ko45u*ko0w",
+        p: "y3şçwl78570*fg",
+        n: 93
+    },
+    {
+        t: "*wrüsjh*ü9kws4*h0okdfhdhdfurdkex",
+        p: "yt7ıtot7ot",
+        n: 94
+    },
+    {
+        t: "x2-0*4woy-*w4opyu-*23wou*0wık*0jk",
+        p: "yasfa33",
+        n: 95
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    },
+    {
+        t: "x vmönöpwoı6-pğfş -*235*çda",
+        p: "ydfasdfasf",
+        n: 96
+    }
+];
+
+const Home = () => {
+    const {
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const bottomSheetRef = useRef<IBottomSheetRef>(null);
+
+    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
+
+    const [
+        isCheckboxActive,
+        setIsCheckboxActive
+    ] = useState<"partially" | "checked" | null>("checked");
+
+    return <View
+        style={[
+            stylesheet.container,
+            {
+                backgroundColor: colors.content.container.default
+            }
+        ]}
+    >
+        <SelectBox
+            keyExtractor={(data, index) => `${data.t}-${index}`}
+            titleExtractor={(data) => data.t}
+            subTitle="Deneme Subtitle"
+            // isWorkWithRealtime={false}
+            hintText="Test deneme"
+            isShowSubTitle={true}
+            isMultipleSelect={true}
+            maxChoice={-1}
+            minChoice={0}
+            initialSelectedItems={[{
+                __title: X[0]?.t as string,
+                __key: `${X[0]?.t}-0`
+            }]}
+            isSearchable={true}
+            title="Deneme Box"
+            isRequired={true}
+            data={X}
+            onMoreLoad={(props) => {
+                console.log("GELMİŞEEEE:", props);
+            }}
+        />
+        <CheckBox
+            isChecked={isCheckboxActive}
+            title="sadfasfsafasf"
+            isDisabled={false}
+            subTitle="asfgsaf"
+            onPress={() => {
+                setIsCheckboxActive(isCheckboxActive === "checked" ? null : isCheckboxActive === null ? "partially" : "checked");
+            }}
+        />
+        <TextInput
+            variant="hidden"
+        />
+        <Text>Version: v{getNCoreUIKitVersion()}</Text>
+        <Button
+            onPress={() => {
+                navigation.navigate("TestSubPage");
+            }}
+            title="Open Test Sub Page"
+            variant="filled"
+            spreadBehaviour="stretch"
+        />
+        <Button
+            onPress={() => {
+                bottomSheetRef.current?.open();
+            }}
+            type="success"
+            title="Open BottomSheet"
+            variant="filled"
+        />
+        <Button
+            title="Open Snackbar"
+            variant="filled"
+            type="danger"
+            onPress={() => {
+
+            }}
+        />
+        <BottomSheet
+            ref={bottomSheetRef}
+            renderHeader={() => {
+                return <View
+                    style={[
+                        {
+                            backgroundColor: "blue",
+                            padding: 20
+                        }
+                    ]}
+                >
+                    <Text>Burası header.</Text>
+                </View>;
+            }}
+            isCanFullScreenOnSwipe={true}
+            snapPoint={300}
+            key="ahmet"
+        >
+            <Button
+                onPress={() => {
+                    navigation.navigate("TestSubPage");
+                }}
+                title="Git."
+            />
+            {[
+                0,
+                1,
+                2,
+                3,
+                4,
+                5,
+                6,
+                7,
+                8,
+                9,
+                10,
+                11,
+                12,
+                13,
+                14,
+                15,
+                16,
+                17,
+                18,
+                19,
+                20,
+                21,
+                22,
+                23,
+                24,
+                25,
+                26,
+                27,
+                28,
+                29,
+                30,
+                31,
+                32,
+                33,
+                34,
+                35
+            ].map((it) => {
+                return <View
+                    key={`fds-${it}`}
+                    style={{
+                        backgroundColor: `rgb(${it * 10}, ${it > 30 ? it * 5 : "0"}, ${it > 20 ? it * 4 : "0"})`,
+                        height: 150
+                    }}
+                />;
+            })}
+            <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>
+            <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;

+ 3 - 3
src/assets/svg/loadingIcon/index.tsx

@@ -19,7 +19,7 @@ import {
 } from "react-native-svg";
 
 const SvgLoadingIcon = ({
-    color = "onPrimary",
+    color = "emphasized",
     customColor,
     size = 22,
     ...props
@@ -55,8 +55,8 @@ const SvgLoadingIcon = ({
             1
         ],
         outputRange: [
-            "360deg",
-            "0deg"
+            "0deg",
+            "-360deg"
         ]
     });
 

+ 851 - 0
src/components/bottomSheet/index.tsx

@@ -0,0 +1,851 @@
+import {
+    useImperativeHandle,
+    type ComponentRef,
+    forwardRef,
+    useEffect,
+    useState,
+    useRef
+} from "react";
+import {
+    type LayoutChangeEvent,
+    PanResponder,
+    ScrollView,
+    Animated,
+    Easing,
+    View
+} from "react-native";
+import type IBottomSheetProps from "./type";
+import type {
+    IBottomSheetRef
+} from "./type";
+import stylesheet from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    useSafeAreaInsets
+} from "react-native-safe-area-context";
+import type {
+    RefForwardingComponent
+} from "../../types";
+import type {
+    IModalRef
+} from "../modal/type";
+import {
+    windowHeight,
+    uuid
+} from "../../utils";
+import Modal from "../modal";
+
+const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
+    renderHeader: RenderHeaderComponent,
+    renderBottom: RenderBottomComponent,
+    isForceFullScreenOnSwipe = false,
+    isCanFullScreenOnSwipe = false,
+    handleContainerBackgroundColor,
+    handleHeight: handleHeightProp,
+    isWrapSafeAreaContext = true,
+    backgroundColor = "default",
+    isWorkAsFullScreen = false,
+    scrollEndThreshold = 0.85,
+    isWorkWithPortal = true,
+    isCloseOnOverlay = true,
+    handleContainerSpacing,
+    isActive: isActiveProp,
+    handleBackgroundColor,
+    isAutoHeight = false,
+    isSwipeClose = true,
+    isShowHandle = true,
+    isCanSwipe = true,
+    onOverlayPressed,
+    scrollViewProps,
+    scrollViewStyle,
+    onScrollEnd,
+    customTheme,
+    modalProps,
+    snapPoint,
+    customKey,
+    children,
+    onClosed,
+    onOpened,
+    onClose,
+    onOpen,
+    style,
+    ...props
+}, ref) => {
+    const {
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        bottom,
+        top
+    } = useSafeAreaInsets();
+
+    const [
+        isMeasured,
+        setIsMeasured
+    ] = useState(false);
+
+    const [
+        isActive,
+        setIsActive
+    ] = useState(isActiveProp === undefined ? false : isActiveProp);
+
+    const bottomSheetKey = useRef(customKey ? customKey : uuid());
+
+    let bottomSafeArea = isWrapSafeAreaContext ? bottom : 0;
+    let topSafeArea = isWrapSafeAreaContext ? top : 0;
+
+    if(isForceFullScreenOnSwipe) {
+        topSafeArea = 0;
+    }
+
+    if(!isWorkWithPortal) {
+        bottomSafeArea = 0;
+        topSafeArea = 0;
+    }
+
+    const scrollViewRef = useRef<ComponentRef<ScrollView>>(null);
+    const modalRef = useRef<IModalRef>(null);
+
+    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 ? 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);
+    const initialHeight = useRef(0);
+
+    const scrollViewContentHeight = useRef(-1);
+    const scrollViewLayoutHeight = useRef(-1);
+    const initialScrollOffset = useRef(0);
+    const scrollOffset = useRef(0);
+
+    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;
+    }
+
+    useImperativeHandle(
+        ref,
+        () => ({
+            close: (callback) => {
+                closeAnimation(undefined, callback);
+            },
+            open: () => {
+                setIsActive(true);
+            }
+        }),
+        []
+    );
+
+    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 = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
+            } else if(isAutoHeight || !snapPoint) {
+                maxHeight.current = contentHeight.current;
+            }
+        }
+    }, [isMeasured]);
+
+    useEffect(() => {
+        if(isActive && isMeasured) {
+            if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
+                maxHeight.current = snapPoint;
+            } else if(isWorkAsFullScreen) {
+                maxHeight.current = containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0);
+            } else {
+                maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
+            }
+
+            openAnimation();
+        }
+    }, [
+        isActive,
+        isMeasured
+    ]);
+
+    if(isActiveProp !== undefined) {
+        useEffect(() => {
+            setIsActive(isActiveProp);
+        }, [isActiveProp]);
+    }
+
+    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(() => {
+        if (!isActive) {
+            const newSnapValue = isWorkAsFullScreen
+                ? containerHeightRef.current
+                : (snapPoint ?? contentHeight.current);
+
+            animatedHeight.setValue(isAutoHeight ? 0 : newSnapValue);
+            animatedTranslateY.setValue(newSnapValue);
+
+            heightValue.current = newSnapValue;
+            translateYValue.current = newSnapValue;
+            initialHeight.current = newSnapValue;
+        }
+
+        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
+            );
+        }
+    }, [
+        snapPoint,
+        isWorkAsFullScreen
+    ]);
+
+    useEffect(() => {
+        const listenerAHeightId = animatedHeight.addListener(({
+            value
+        }) => {
+            heightValue.current = value;
+        });
+
+        const listenerTYId = animatedTranslateY.addListener(({
+            value
+        }) => {
+            translateYValue.current = value;
+        });
+
+        return () => {
+            animatedHeight.removeListener(listenerAHeightId);
+            animatedTranslateY.removeListener(listenerTYId);
+        };
+    }, []);
+
+    const checkScrollThreshold = () => {
+        if (!onScrollEnd) return;
+
+        const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);
+
+        if (maxS <= 0) return;
+
+        const ratio = scrollOffset.current / maxS;
+
+        if (ratio >= scrollEndThreshold) {
+            onScrollEnd();
+        }
+    };
+
+    const openAnimation = () => {
+        resetState();
+
+        if(onOpen) onOpen();
+
+        Animated.timing(animatedTranslateY, {
+            useNativeDriver: false,
+            duration: 300,
+            toValue: 0
+        }).start(({
+            finished
+        }) => {
+            if(finished) {
+                if(onOpened) onOpened();
+            }
+        });
+    };
+
+    const closeAnimation = (toValue?: number, callback?: () => void) => {
+        if(onClose) onClose();
+
+        const currentSnapPoint = isWorkAsFullScreen
+            ? containerHeightRef.current
+            : (isAutoHeight || !snapPoint ? contentHeight.current : snapPoint);
+
+        Animated.timing(animatedTranslateY, {
+            toValue: toValue ?? currentSnapPoint,
+            useNativeDriver: false,
+            duration: 300
+        }).start(({
+            finished
+        }) => {
+            if(finished) {
+                resetState();
+
+                setIsActive(false);
+
+                if(onClosed) onClosed();
+                if(callback) callback();
+            }
+        });
+    };
+
+    const onLayout = (event: LayoutChangeEvent) => {
+        if (isMeasured) return;
+
+        const {
+            height
+        } = event.nativeEvent.layout;
+
+        animatedHeight.setValue(height);
+
+        contentHeight.current = height;
+
+        setIsMeasured(true);
+    };
+
+    const resetState = () => {
+        animatedHeight.setOffset(0);
+        animatedTranslateY.setOffset(0);
+
+        scrollOffset.current = 0;
+        initialScrollOffset.current = 0;
+
+        const pivot = isWorkAsFullScreen
+            ? containerHeightRef.current
+            : (snapPoint ?? contentHeight.current);
+
+        initialHeight.current = pivot;
+        initialTranslateY.current = 0;
+
+        heightValue.current = pivot;
+        translateYValue.current = 0;
+    };
+
+    const panResponder = useRef(
+        PanResponder.create({
+            onStartShouldSetPanResponder: () => false,
+            onMoveShouldSetPanResponderCapture: (_, gestureState) => {
+                const {
+                    dy
+                } = gestureState;
+
+                if(!isCanSwipeRef.current) {
+                    return false;
+                }
+
+                if(Math.abs(dy) < 20) {
+                    return false;
+                }
+
+                return true;
+            },
+            onPanResponderGrant: (evt) => {
+                if(!isCanSwipeRef.current) return;
+
+                gestureStartY.current = evt.nativeEvent.pageY;
+
+                animatedTranslateY.stopAnimation((currentY) => {
+                    translateYValue.current = currentY;
+                });
+
+                animatedHeight.stopAnimation((currentH) => {
+                    heightValue.current = currentH;
+                });
+
+                animatedTranslateY.flattenOffset();
+                animatedHeight.flattenOffset();
+
+                initialTranslateY.current = translateYValue.current;
+                initialScrollOffset.current = scrollOffset.current;
+                initialHeight.current = heightValue.current;
+
+                animatedHeight.setOffset(heightValue.current);
+                animatedHeight.setValue(0);
+
+                animatedTranslateY.setOffset(translateYValue.current);
+                animatedTranslateY.setValue(0);
+            },
+            onPanResponderMove: (_, gestureState) => {
+                if(!isCanSwipeRef.current) return;
+
+                const {
+                    dy
+                } = gestureState;
+
+                const isAtTop = scrollOffset.current <= 0;
+                const isAtTavan = heightValue.current >= maxHeight.current - 1;
+                const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
+
+                const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;
+
+                if (dy > 0 && isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
+                    const currentDelta = -dy;
+
+                    const initialS = initialScrollOffset.current;
+
+                    const usedForS = Math.max(currentDelta, -initialS);
+
+                    scrollOffset.current = initialS + usedForS;
+
+                    scrollViewRef.current?.scrollTo({
+                        y: scrollOffset.current,
+                        animated: false
+                    });
+
+                    return;
+                }
+
+                const delta = -dy;
+
+                const pivot = snapPoint ?? contentHeight.current;
+
+                const initialH = initialHeight.current;
+                const initialS = initialScrollOffset.current;
+                const initialT = initialTranslateY.current;
+
+                const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);
+
+                if (dy < 0) {
+                    let currentDelta = delta;
+
+                    const effectiveInitialT = Math.max(0, initialT);
+                    const usedForT = Math.min(currentDelta, effectiveInitialT);
+
+                    animatedTranslateY.setValue(-usedForT);
+                    currentDelta -= usedForT;
+
+                    if (currentDelta > 0) {
+                        if (initialH < pivot) {
+                            const spaceToPivot = pivot - initialH;
+                            const usedForH = Math.min(currentDelta, spaceToPivot);
+
+                            animatedHeight.setValue(usedForH);
+
+                            currentDelta -= usedForH;
+                        }
+
+                        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);
+                                scrollOffset.current = initialS + usedForS;
+
+                                scrollViewRef.current?.scrollTo({
+                                    y: scrollOffset.current,
+                                    animated: false
+                                });
+
+                                currentDelta -= usedForS;
+
+                                animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
+                            }
+                        }
+
+                        if (currentDelta > 0) {
+                            if (isCanFullScreenOnSwipeRef.current) {
+                                const totalUsedBefore = (initialH < pivot ? (pivot - initialH) : 0);
+
+                                animatedHeight.setValue(totalUsedBefore + currentDelta);
+                            } else {
+                                animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
+                            }
+                        }
+                    }
+                } else {
+                    let currentDelta = delta;
+
+                    if (initialH > pivot || initialS > 0) {
+                        if (initialS > 0) {
+                            const usedForS = Math.max(currentDelta, -initialS);
+                            scrollOffset.current = initialS + usedForS;
+
+                            scrollViewRef.current?.scrollTo({
+                                y: scrollOffset.current,
+                                animated: false
+                            });
+
+                            currentDelta -= usedForS;
+
+                            animatedHeight.setValue(initialH > pivot ? 0 : pivot - initialH);
+                        }
+
+                        if (currentDelta < 0 && initialH > pivot) {
+                            const distanceToPivot = pivot - initialH;
+                            const usedForH = Math.max(currentDelta, distanceToPivot);
+
+                            animatedHeight.setValue(usedForH);
+
+                            currentDelta -= usedForH;
+                        }
+                    }
+
+                    if (currentDelta < 0) {
+                        animatedHeight.setValue(initialH > pivot ? (pivot - initialH) : 0);
+                        animatedTranslateY.setValue(-currentDelta);
+
+                        scrollOffset.current = 0;
+
+                        scrollViewRef.current?.scrollTo({
+                            y: 0,
+                            animated: false
+                        });
+                    }
+                }
+            },
+            onPanResponderEnd: (_, gestureState) => {
+                if(!isCanSwipeRef.current) return;
+
+                const isAtTop = scrollOffset.current <= 1;
+                const isAtTavan = heightValue.current >= maxHeight.current - 1;
+                const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
+
+                const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;
+
+                if (isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
+                    const velocity = gestureState.vy;
+
+                    if (Math.abs(velocity) > 0.1) {
+                        const momentum = velocity * -350;
+
+                        const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);
+
+                        const targetOffset = Math.max(0, Math.min(scrollOffset.current + momentum, maxS));
+
+                        const scrollAnim = new Animated.Value(scrollOffset.current);
+
+                        scrollAnim.addListener(({
+                            value
+                        }) => {
+                            scrollViewRef.current?.scrollTo({
+                                y: value,
+                                animated: false
+                            });
+                            scrollOffset.current = value;
+                        });
+
+                        Animated.timing(scrollAnim, {
+                            duration: Math.min(Math.abs(momentum) * 2, 600),
+                            easing: Easing.out(Easing.quad),
+                            useNativeDriver: false,
+                            toValue: targetOffset
+                        }).start(() => {
+                            scrollAnim.removeAllListeners();
+
+                            checkScrollThreshold();
+                        });
+                    } else {
+                        checkScrollThreshold();
+                    }
+
+                    return;
+                }
+
+                const currentH = (animatedHeight as Animated.Value & {
+                    __getValue(): number;
+                }).__getValue();
+
+                const currentT = (animatedTranslateY as Animated.Value & {
+                    __getValue(): number;
+                }).__getValue();
+
+                animatedHeight.flattenOffset();
+                animatedTranslateY.flattenOffset();
+
+                heightValue.current = currentH;
+                translateYValue.current = currentT;
+
+                const pivot = snapPoint ?? contentHeight.current;
+                const tavan = maxHeight.current;
+
+                const isFastSwipeDown = gestureState.vy > 0.5;
+                const isFastSwipeUp = gestureState.vy < -0.5;
+
+                if (currentT <= Math.max(pivot * 0.2, 60)) {
+                    let toValue = pivot;
+
+                    if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
+                        toValue = tavan;
+                    } else if (isFastSwipeDown) {
+                        toValue = pivot;
+                    } else {
+                        if (!isCanFullScreenOnSwipeRef.current) {
+                            toValue = pivot;
+                        } else {
+                            const totalRange = tavan - pivot;
+                            const distFromPivot = currentH - pivot;
+
+                            if (distFromPivot < totalRange * 0.33) {
+                                toValue = pivot;
+                            } else if (distFromPivot > totalRange * 0.66) {
+                                toValue = tavan;
+                            } else {
+                                toValue = (tavan - currentH) < (currentH - pivot) ? tavan : pivot;
+                            }
+                        }
+                    }
+
+                    Animated.spring(animatedHeight, {
+                        useNativeDriver: false,
+                        toValue: toValue,
+                        friction: 10,
+                        tension: 40
+                    }).start();
+
+                    Animated.spring(animatedTranslateY, {
+                        useNativeDriver: false,
+                        toValue: 0,
+                        friction: 10,
+                        tension: 40
+                    }).start();
+                } else {
+                    let toValueT = 0;
+                    let isClosing = false;
+
+                    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 (isFastSwipeDown) {
+                            toValueT = pivot + 100;
+
+                            isClosing = true;
+                        } else {
+                            if (currentT > pivot * 0.5) {
+                                toValueT = pivot + 100;
+
+                                isClosing = true;
+                            } else {
+                                toValueT = 0;
+
+                                isClosing = false;
+                            }
+                        }
+
+                        if(onClose) onClose();
+
+                        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 = () => {
+        return <Animated.View
+            {...props}
+            {...panResponder.panHandlers}
+            onLayout={onLayout}
+            style={[
+                style,
+                {
+                    height: (isAutoHeight || !snapPoint) && !isMeasured ? "auto" : animatedHeight,
+                    backgroundColor: colors.content.container[backgroundColor],
+                    paddingBottom: bottomSafeArea + spaces.spacingMd,
+                    opacity: isMeasured || !isAutoHeight ? 1 : 0,
+                    paddingRight: spaces.spacingMd,
+                    paddingLeft: spaces.spacingMd,
+                    paddingTop: spaces.spacingMd,
+                    maxHeight: maxHeight.current,
+                    transform: [{
+                        translateY: animatedTranslateY
+                    }]
+                },
+                stylesheet.container
+            ]}
+        >
+            {renderHeader()}
+            <ScrollView
+                {...scrollViewProps}
+                onContentSizeChange={(w, h) => {
+                    scrollViewContentHeight.current = h;
+                }}
+                onLayout={(e) => {
+                    scrollViewLayoutHeight.current = e.nativeEvent.layout.height;
+                }}
+                onStartShouldSetResponderCapture={() => false}
+                onMoveShouldSetResponderCapture={() => false}
+                showsHorizontalScrollIndicator={false}
+                showsVerticalScrollIndicator={false}
+                scrollEnabled={!isCanSwipe}
+                scrollEventThrottle={1}
+                overScrollMode="never"
+                ref={scrollViewRef}
+                bounces={false}
+                style={[
+                    scrollViewStyle
+                ]}
+            >
+                {children}
+            </ScrollView>
+            {renderBottom()}
+            {renderHandle()}
+        </Animated.View>;
+    };
+
+    const renderHeader = () => {
+        if(!RenderHeaderComponent) {
+            return null;
+        }
+
+        return RenderHeaderComponent();
+    };
+
+    const renderBottom = () => {
+        if(!RenderBottomComponent) {
+            return null;
+        }
+
+        return <RenderBottomComponent/>;
+    };
+
+    const renderHandle = () => {
+        if(!isShowHandle || isWorkAsFullScreen) {
+            return null;
+        }
+
+        const handleHeight = handleHeightProp ? handleHeightProp : 8;
+        const handleContainerHeight = handleHeight + ((handleContainerSpacing ? spaces[handleContainerSpacing] : spaces.spacingSm) * 2);
+        const handleBorderRadius = Math.ceil(handleHeight / 2);
+
+        return <View
+            {...panResponder.panHandlers}
+            onStartShouldSetResponderCapture={() => false}
+            onMoveShouldSetResponderCapture={() => false}
+            style={[
+                stylesheet.handleContainer,
+                {
+                    backgroundColor: handleContainerBackgroundColor
+                        ? colors.content.container[handleContainerBackgroundColor]
+                        : "transparent",
+                    height: handleContainerHeight,
+                    transform: [{
+                        translateY: -handleContainerHeight
+                    }]
+                }
+            ]}
+        >
+            <View
+                style={[
+                    stylesheet.handle,
+                    {
+                        backgroundColor: handleBackgroundColor
+                            ? colors.content.container[handleBackgroundColor]
+                            : colors.content.container.default,
+                        borderRadius: handleBorderRadius,
+                        height: handleHeight
+                    }
+                ]}
+            />
+        </View>;
+    };
+
+    return <Modal
+        {...modalProps}
+        isWorkWithPortal={isWorkWithPortal}
+        key={`${bottomSheetKey}-modal`}
+        isContentRequired={false}
+        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();
+            }
+        }}
+        style={[
+            {
+                paddingTop: topSafeArea
+            }
+        ]}
+    >
+        {renderView()}
+    </Modal>;
+};
+export default forwardRef(BottomSheet);

+ 28 - 0
src/components/bottomSheet/stylesheet.ts

@@ -0,0 +1,28 @@
+import {
+    StyleSheet
+} from "react-native";
+import {
+    windowWidth
+} from "../../utils";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        transformOrigin: "bottom",
+        position: "absolute",
+        zIndex: 99999,
+        bottom: 0,
+        right: 0,
+        left: 0
+    },
+    handleContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        position: "absolute",
+        right: 0,
+        left: 0
+    },
+    handle: {
+        width: windowWidth / 10
+    }
+});
+export default stylesheet;

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

@@ -0,0 +1,58 @@
+import {
+    type ReactNode
+} from "react";
+import type {
+    ScrollViewProps,
+    ViewStyle,
+    StyleProp
+} from "react-native";
+import type IModalProps from "../modal/type";
+
+export interface IBottomSheetRef {
+    close: (callback: () => void) => void;
+    open: () => void;
+}
+
+interface IBottomSheetProps {
+    handleContainerBackgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    handleContainerSpacing?: keyof NCoreUIKit.ActivePalette["spaces"];
+    handleBackgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    backgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    scrollViewStyle?: StyleProp<ViewStyle>;
+    isForceFullScreenOnSwipe?: boolean;
+    scrollViewProps?: ScrollViewProps;
+    isCanFullScreenOnSwipe?: boolean;
+    isWrapSafeAreaContext?: boolean;
+    renderHeader?: () => ReactNode;
+    renderBottom?: () => ReactNode;
+    onOverlayPressed?: () => void;
+    isWorkAsFullScreen?: boolean;
+    scrollEndThreshold?: number;
+    isCloseOnOverlay?: boolean;
+    isWorkWithPortal?: boolean;
+    onScrollEnd?: () => void;
+    modalProps?: IModalProps;
+    isSwipeClose?: boolean;
+    isShowHandle?: boolean;
+    isAutoHeight?: boolean;
+    handleHeight?: number;
+    onClosed?: () => void;
+    onOpened?: () => void;
+    isCanSwipe?: boolean;
+    onClose?: () => void;
+    children?: ReactNode;
+    onOpen?: () => void;
+    snapPoint?: number;
+    isActive?: boolean;
+    customKey?: string;
+    style?: ViewStyle;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+}
+export type {
+    IBottomSheetProps as default
+};

+ 2 - 1
src/components/button/index.tsx

@@ -36,6 +36,7 @@ const Button: FC<IButtonProps> = ({
     isDisabled = false,
     type = "primary",
     size = "medium",
+    customTheme,
     titleStyle,
     isLoading,
     onPress,
@@ -49,7 +50,7 @@ const Button: FC<IButtonProps> = ({
         borders,
         colors,
         spaces
-    } = NCoreUIKitTheme.useContext();
+    } = NCoreUIKitTheme.useContext(customTheme);
 
     const currentSize = getButtonSize({
         spaces,

+ 6 - 4
src/components/button/stylesheet.ts

@@ -152,11 +152,10 @@ const stylesheet = StyleSheet.create({
         borderStyle: "solid",
         alignItems: "center",
         position: "relative",
-        userSelect: "none",
         display: "flex"
     },
     title: {
-        margin: "0 auto",
+        margin: "0",
     },
     loading: {},
     overlay: {
@@ -224,6 +223,8 @@ export const useStyles = ({
     if (isLoading) {
         styles.title.marginLeft = spaces.spacingSm;
 
+        styles.overlay.display = "flex";
+
         if (displayBehaviourWhileLoading === "disabled") {
             if (variant !== "ghost") {
                 styles.container.borderColor = colors.system.state.overlay.disabled[styleType];
@@ -241,16 +242,17 @@ export const useStyles = ({
     }
 
     if (icon && !isLoading) {
-        styles.title.margin = "initial";
         styles.title.marginLeft = spaces.spacingSm;
+        styles.title.margin = "initial";
     }
 
     if (spreadBehaviour === "baseline") {
         styles.container.alignSelf = spreadBehaviour;
         styles.container.width = "auto";
     } else if (spreadBehaviour === "stretch") {
+        styles.container.alignSelf = spreadBehaviour;
         styles.container.justifyContent = "center";
-        // styles.container.alignSelf = spreadBehaviour; TODO: It was required but now is not. Why ?
+        styles.container.flexShrink = 1;
         styles.container.width = "100%";
     }
 

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

@@ -81,6 +81,12 @@ interface IButtonProps extends Omit<ButtonProps, "title"> {
     displayBehaviourWhileLoading?: ButtonDisplayBehaviourWhileLoading;
     titleStyle?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
     style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
     spreadBehaviour?: ButtonSpreadBehaviour;
     iconDirection?: "left" | "right";
     variant?: ButtonVariant;

+ 265 - 0
src/components/checkBox/index.tsx

@@ -0,0 +1,265 @@
+import {
+    type FC
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import type ICheckBoxProps from "./type";
+import stylesheet, {
+    getCheckBoxType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type ITextProps from "../text/type";
+import {
+    Check,
+    Minus
+} from "lucide-react-native";
+import Loading from "../loading";
+import Text from "../text";
+
+const CheckBox: FC<ICheckBoxProps> = ({
+    displayBehaviourWhileLoading = "disabled",
+    spreadBehaviour = "baseline",
+    isOptional = false,
+    isDisabled = false,
+    type = "neutral",
+    isFlip = false,
+    customLocalize,
+    subTitleStyle,
+    optionalText,
+    customTheme,
+    titleStyle,
+    isChecked,
+    isLoading,
+    subTitle,
+    onPress,
+    title,
+    style,
+    ...props
+}) => {
+    const {
+        inlineSpaces,
+        radiuses,
+        borders,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext(customLocalize);
+
+    const currentType = getCheckBoxType({
+        type
+    });
+
+    const {
+        indicatorContainer: indicatorContainerDynamicStyle,
+        contentContainer: contentContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
+        optionalText: optionalTextDynamicStyle,
+        container: containerDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        loading: loadingDynamicStyle,
+        overlay: overlayDynamicStyle,
+        title: titleDynamicStyle
+    } = useStyles({
+        displayBehaviourWhileLoading,
+        spreadBehaviour,
+        inlineSpaces,
+        currentType,
+        isDisabled,
+        isChecked,
+        isLoading,
+        radiuses,
+        borders,
+        isFlip,
+        colors,
+        spaces,
+        type
+    });
+
+    const titleProps: ITextProps = {
+        color: currentType.titleColor,
+    };
+
+    const subTitleProps: ITextProps = {
+        color: currentType.subTitleColor,
+    };
+
+    const indicatorIconProps: {
+        color: keyof NCoreUIKit.BorderContentColors;
+        customColor?: string;
+    } = {
+        color: currentType.indicatorColor
+    };
+
+    if (isDisabled || isLoading) {
+        const stateType = type === "danger" ? "error" : type;
+
+        subTitleProps.customColor = colors.system.state.content.disabled[stateType];
+        titleProps.customColor = colors.system.state.content.disabled[stateType];
+
+        if(isChecked && stateType === "neutral") {
+            indicatorIconProps.customColor = colors.system.state.border.disabled.primary;
+        } else {
+            indicatorIconProps.customColor = colors.system.state.border.disabled[stateType];
+        }
+    }
+
+    const renderOptionalText = () => {
+        if(!isOptional && !optionalText) {
+            return null;
+        }
+
+        return <Text
+            variant="labelLargeSize"
+            color={titleProps.color}
+            style={[
+                optionalTextDynamicStyle
+            ]}
+            {...titleProps}
+        >
+            ( {isOptional ? localize("is-optional") : optionalText} )
+        </Text>;
+    };
+
+    const renderTitle = () => {
+        if (!title) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
+            ]}
+        >
+            <Text
+                variant="bodyMediumSize"
+                style={[
+                    titleStyle,
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+                {...titleProps}
+            >
+                {title}
+            </Text>
+            {renderOptionalText()}
+        </View>;
+    };
+
+    const renderCheckIcon = () => {
+        if(!isChecked) {
+            return <View
+                style={{
+                    height: 16,
+                    width: 16
+                }}
+            />;
+        }
+
+        const selectedColor = indicatorIconProps.customColor ? indicatorIconProps.customColor : colors.content.border[indicatorIconProps.color];
+
+        if(isChecked === "partially") {
+            return <Minus
+                color={selectedColor}
+                size={16}
+            />;
+        }
+
+        return <Check
+            color={selectedColor}
+            size={16}
+        />;
+    };
+
+    const renderCheckIndicator = () => {
+        if (isLoading) {
+            return <Loading
+                style={[
+                    loadingDynamicStyle
+                ]}
+            />;
+        }
+
+        return <View
+            style={[
+                stylesheet.indicatorContainer,
+                indicatorContainerDynamicStyle
+            ]}
+        >
+            {renderCheckIcon()}
+
+            {renderOverlay()}
+        </View>;
+    };
+
+    const renderOverlay = () => {
+        return <View
+            style={[
+                stylesheet.overlay,
+                overlayDynamicStyle
+            ]}
+        />;
+    };
+
+    const renderSubtitle = () => {
+        if(!subTitle) {
+            return null;
+        }
+
+        return <Text
+            {...subTitleProps}
+            style={[
+                subTitleStyle,
+                stylesheet.subTitle,
+                subTitleDynamicStyle
+            ]}
+        >
+            {subTitle}
+        </Text>;
+    };
+
+    const renderContent = () => {
+        if(!title) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.contentContainer,
+                contentContainerDynamicStyle
+            ]}
+        >
+            {renderTitle()}
+            {renderSubtitle()}
+        </View>;
+    };
+
+    return (
+        <TouchableOpacity
+            {...props}
+            onPress={isDisabled || isLoading ? () => null : onPress ? onPress : undefined}
+            disabled={isDisabled || isLoading || !onPress}
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle
+            ]}
+        >
+            {isFlip ? null : renderCheckIndicator()}
+
+            {renderContent()}
+
+            {isFlip ? renderCheckIndicator() : null}
+        </TouchableOpacity>
+    );
+};
+export default CheckBox;

+ 223 - 0
src/components/checkBox/stylesheet.ts

@@ -0,0 +1,223 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type CheckBoxDynamicStyleType,
+    type CheckBoxTypeConstantType,
+    type CheckBoxTypes,
+    type CheckBoxType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const CHECK_BOX_TYPE_STYLES: Record<
+    CheckBoxType,
+    {
+        containerColor: keyof NCoreUIKit.ContainerContentColors;
+        indicatorColor: keyof NCoreUIKit.BorderContentColors;
+        subTitleColor: keyof NCoreUIKit.TextContentColors;
+        borderColor: keyof NCoreUIKit.BorderContentColors;
+        titleColor: keyof NCoreUIKit.TextContentColors;
+    }
+> = {
+    primary: {
+        indicatorColor: "emphasized",
+        borderColor: "emphasized",
+        containerColor: "mid",
+        subTitleColor: "low",
+        titleColor: "mid"
+    },
+    danger: {
+        subTitleColor: "dangerLow",
+        indicatorColor: "danger",
+        containerColor: "danger",
+        borderColor: "danger",
+        titleColor: "danger"
+    },
+    success: {
+        subTitleColor: "successLow",
+        indicatorColor: "success",
+        containerColor: "success",
+        borderColor: "success",
+        titleColor: "success"
+    },
+    warning: {
+        subTitleColor: "warningLow",
+        indicatorColor: "warning",
+        containerColor: "warning",
+        borderColor: "warning",
+        titleColor: "warning"
+    },
+    info: {
+        subTitleColor: "infoLow",
+        indicatorColor: "info",
+        containerColor: "info",
+        borderColor: "info",
+        titleColor: "info"
+    },
+    neutral: {
+        indicatorColor: "emphasized",
+        containerColor: "mid",
+        borderColor: "subtle",
+        subTitleColor: "low",
+        titleColor: "mid"
+    }
+};
+
+export const getCheckBoxType = ({
+    type
+}: CheckBoxTypeConstantType): CheckBoxTypes => {
+    const currentType = CHECK_BOX_TYPE_STYLES[type];
+
+    return currentType;
+};
+
+const stylesheet = StyleSheet.create({
+    container: {
+        backgroundColor: "transparent",
+        borderColor: "transparent",
+        flexDirection: "row",
+        borderStyle: "solid",
+        alignItems: "center",
+        display: "flex"
+    },
+    indicatorContainer: {
+        position: "relative"
+    },
+    contentContainer: {
+        justifyContent: "center",
+        flexDirection: "column"
+    },
+    titleContainer: {
+        justifyContent: "flex-start",
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    title: {
+        textAlign: "left",
+        margin: 0
+    },
+    subTitle: {
+        textAlign: "left"
+    },
+    loading: {},
+    overlay: {
+        position: "absolute",
+        display: "none",
+        zIndex: 999,
+        bottom: -2,
+        right: -2,
+        left: -2,
+        top: -2
+    }
+});
+
+export const useStyles = ({
+    displayBehaviourWhileLoading,
+    spreadBehaviour,
+    inlineSpaces,
+    currentType,
+    isDisabled,
+    isLoading,
+    isChecked,
+    radiuses,
+    borders,
+    colors,
+    isFlip,
+    spaces,
+    type
+}: CheckBoxDynamicStyleType) => {
+    const styleType = type === "danger" ? "error" : type;
+
+    const styles = {
+        container: {
+            padding: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        indicatorContainer: {
+            backgroundColor: colors.content.container[currentType.containerColor],
+            borderColor: colors.content.border[currentType.borderColor],
+            borderRadius: radiuses.actions,
+            borderWidth: borders.line,
+            padding: spaces.spacingXs
+        } as Mutable<ViewStyle>,
+        contentContainer: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        titleContainer: {
+
+        } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
+        subTitle: {
+
+        } as Mutable<TextStyle>,
+        loading: {
+        } as Mutable<ViewStyle>,
+        overlay: {
+            borderRadius: radiuses.actions
+        } as Mutable<ViewStyle>,
+        optionalText: {
+            marginLeft: inlineSpaces.subTitle
+        } as Mutable<TextStyle>
+    };
+
+    if(styleType === "neutral") {
+        styles.indicatorContainer.borderColor = colors.content.border.default;
+    }
+
+    if (isLoading) {
+        if (displayBehaviourWhileLoading === "disabled") {
+            styles.indicatorContainer.borderColor = colors.system.state.overlay.disabled[styleType];
+
+            styles.overlay.backgroundColor = colors.system.state.overlay.disabled[styleType];
+            styles.overlay.display = "flex";
+        }
+    }
+
+    if (isFlip) {
+        styles.contentContainer.marginRight = spaces.spacingSm;
+        styles.titleContainer.justifyContent = "flex-end";
+        styles.contentContainer.marginLeft = 0;
+        styles.subTitle.textAlign = "right";
+        styles.title.textAlign = "right";
+    }
+
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    } else if (spreadBehaviour === "stretch") {
+        styles.contentContainer.alignSelf = spreadBehaviour;
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.justifyContent = "center";
+        styles.contentContainer.flexShrink = 1;
+        styles.contentContainer.width = "100%";
+        styles.container.flexShrink = 1;
+        styles.container.width = "100%";
+    }
+
+    if(isChecked) {
+        if(styleType === "neutral") {
+            styles.indicatorContainer.backgroundColor = colors.content.container.selected;
+            styles.indicatorContainer.borderColor = colors.content.border.emphasized;
+        }
+    }
+
+    if (isDisabled) {
+        if(styleType === "neutral" && isChecked) {
+            styles.indicatorContainer.backgroundColor = colors.system.state.border.disabled.primary;
+            styles.indicatorContainer.borderColor = colors.system.state.border.disabled.primary;
+        } else {
+            styles.indicatorContainer.borderColor = colors.system.state.border.disabled[styleType];
+        }
+
+        styles.overlay.backgroundColor = colors.system.state.overlay.disabled[styleType];
+        styles.overlay.display = "flex";
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 70 - 0
src/components/checkBox/type.ts

@@ -0,0 +1,70 @@
+import {
+    type ViewStyle,
+    type StyleProp,
+    type TextStyle
+} from "react-native";
+
+export type CheckBoxDynamicStyleType = {
+    displayBehaviourWhileLoading?: CheckBoxDisplayBehaviourWhileLoading;
+    inlineSpaces: NCoreUIKit.ActivePalette["inlineSpaces"];
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    isChecked?: "checked" | "partially" | null;
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    spreadBehaviour?: CheckBoxSpreadBehaviour;
+    borders: NCoreUIKit.Borders;
+    currentType: CheckBoxTypes;
+    isDisabled?: boolean;
+    isLoading?: boolean;
+    type: CheckBoxType;
+    theme?: undefined;
+    isFlip?: boolean;
+};
+
+export type CheckBoxTypes = {
+    containerColor: keyof NCoreUIKit.ContainerContentColors;
+    indicatorColor: keyof NCoreUIKit.BorderContentColors;
+    subTitleColor: keyof NCoreUIKit.TextContentColors;
+    borderColor: keyof NCoreUIKit.BorderContentColors;
+    titleColor: keyof NCoreUIKit.TextContentColors;
+};
+
+export type CheckBoxTypeConstantType = {
+    type: CheckBoxType;
+};
+
+export type CheckBoxType = "primary" | "danger" | "warning" | "neutral" | "success" | "info";
+
+export type CheckBoxDisplayBehaviourWhileLoading = "none" | "disabled";
+
+export type CheckBoxSpreadBehaviour = "baseline" | "stretch" | "free";
+
+interface ICheckBoxProps {
+    displayBehaviourWhileLoading?: CheckBoxDisplayBehaviourWhileLoading;
+    subTitleStyle?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
+    titleStyle?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
+    style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    customLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
+    isChecked?: "checked" | "partially" | null;
+    spreadBehaviour?: CheckBoxSpreadBehaviour;
+    optionalText?: string;
+    isDisabled?: boolean;
+    isOptional?: boolean;
+    onPress?: () => void;
+    isLoading?: boolean;
+    type?: CheckBoxType;
+    subTitle?: string;
+    isFlip?: boolean;
+    title?: string;
+}
+export type {
+    ICheckBoxProps as default
+};

+ 17 - 2
src/components/index.ts

@@ -34,8 +34,23 @@ export {
 export {
     default as TextInput
 } from "./textInput";
-/*
+
 export {
     default as SelectBox
 } from "./selectBox";
-*/
+
+export {
+    default as BottomSheet
+} from "./bottomSheet";
+
+export type {
+    IBottomSheetRef
+} from "./bottomSheet/type";
+
+export {
+    default as SelectSheet
+} from "./selectSheet";
+
+export {
+    default as CheckBox
+} from "./checkBox";

+ 2 - 2
src/components/loading/index.tsx

@@ -6,13 +6,14 @@ import stylesheet from "./stylesheet";
 import LoadingIcon from "../../assets/svg/loadingIcon";
 
 const Loading: FC<ILoadingProps> = ({
-    color = "primary" as keyof NCoreUIKit.IconContentColors,
+    color = "emphasized" as keyof NCoreUIKit.IconContentColors,
     customColor,
     size = 22,
     style,
     ...props
 }) => {
     return <LoadingIcon
+        {...props}
         customColor={customColor}
         color={color}
         size={size}
@@ -20,7 +21,6 @@ const Loading: FC<ILoadingProps> = ({
             stylesheet.container,
             style
         ]}
-        {...props}
     />;
 };
 export default Loading;

+ 92 - 42
src/components/modal/index.tsx

@@ -2,9 +2,11 @@ import {
     useImperativeHandle,
     forwardRef,
     useEffect,
+    useState,
     useRef
 } from "react";
 import {
+    TouchableWithoutFeedback,
     Animated,
     Easing,
     View
@@ -31,49 +33,72 @@ import {
  */
 const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
     isDisabledOverlay = false,
+    isContentRequired = true,
     isOverlayVisible = true,
     alignContent = "center",
+    isWorkWithPortal = true,
+    isActive: isActiveProp,
+    onContainerLayout,
+    isAnimated = true,
     onOverlayPress,
     contentStyle,
+    overlayProps,
+    customTheme,
     children,
     style
 }, ref) => {
     const {
         colors
-    } = NCoreUIKitTheme.useContext();
+    } = NCoreUIKitTheme.useContext(customTheme);
 
-    const scaleAnim = useRef(new Animated.Value(0)).current;
+    const scaleAnim = useRef(new Animated.Value(isAnimated ? 0 : 1)).current;
+
+    const [
+        isActive,
+        setIsActive
+    ] = useState(isActiveProp === undefined ? true : isActiveProp);
 
     useEffect(() => {
-        Animated.timing(scaleAnim, {
-            easing: Easing.out(Easing.back(1.5)),
-            useNativeDriver: true,
-            duration: 350,
-            toValue: 1
-        }).start();
+        if(isAnimated) {
+            Animated.timing(scaleAnim, {
+                easing: Easing.out(Easing.back(1.5)),
+                useNativeDriver: true,
+                duration: 350,
+                toValue: 1
+            }).start();
+        }
     }, []);
 
+    useEffect(() => {
+        if(isActiveProp !== undefined) {
+            setIsActive(isActiveProp);
+        }
+    }, [isActiveProp]);
+
     useImperativeHandle(
         ref,
         () => ({
-            closeAnimation
+            closeAnimation,
+            setIsActive
         }),
         []
     );
 
     const closeAnimation = (onClosed?: () => void) => {
-        Animated.timing(scaleAnim, {
-            easing: Easing.in(Easing.ease),
-            useNativeDriver: true,
-            duration: 250,
-            toValue: 0
-        }).start(({
-            finished
-        }) => {
-            if(finished) {
-                if(onClosed) onClosed();
-            }
-        });
+        if(isAnimated) {
+            Animated.timing(scaleAnim, {
+                easing: Easing.in(Easing.ease),
+                useNativeDriver: true,
+                duration: 250,
+                toValue: 0
+            }).start(({
+                finished
+            }) => {
+                if(finished) {
+                    if(onClosed) onClosed();
+                }
+            });
+        }
     };
 
     const renderOverlay = () => {
@@ -81,11 +106,12 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
             return null;
         }
 
-        return <View
+        return <TouchableWithoutFeedback
+            {...overlayProps}
             style={[
                 stylesheet.overlay
             ]}
-            onClick={() => {
+            onPress={() => {
                 if(isDisabledOverlay) {
                     return;
                 }
@@ -94,7 +120,38 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
                     closeAnimation
                 });
             }}
-        />;
+        >
+            <View
+                style={[
+                    stylesheet.overlayContent,
+                    {
+                        backgroundColor: colors.system.scrim
+                    }
+                ]}
+            />
+        </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 = () => {
@@ -112,23 +169,16 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
         </View>;
     };
 
-    return <Portal>
-        <Animated.View
-            style={[
-                style,
-                {
-                    justifyContent: alignContent === "center" ? "center" : undefined,
-                    alignItems: alignContent === "center" ? "center" : "baseline",
-                    backgroundColor: colors.system.scrim,
-                    transform: [{
-                        scale: scaleAnim
-                    }]
-                }
-            ]}
-        >
-            {renderOverlay()}
-            {renderContent()}
-        </Animated.View>
-    </Portal>;
+    const renderWithPortal = () => {
+        return <Portal>
+            {renderContainer()}
+        </Portal>;
+    };
+
+    if(!isActive) {
+        return null;
+    }
+
+    return isWorkWithPortal ? renderWithPortal() : renderContainer();
 };
 export default forwardRef(Modal);

+ 10 - 2
src/components/modal/stylesheet.ts

@@ -4,7 +4,7 @@ import {
 
 const stylesheet = StyleSheet.create({
     container: {
-        position: "static",
+        position: "absolute",
         display: "flex",
         zIndex: 99997,
         bottom: 0,
@@ -13,7 +13,15 @@ const stylesheet = StyleSheet.create({
         top: 0
     },
     overlay: {
-        position: "static",
+        position: "absolute",
+        zIndex: 99998,
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0
+    },
+    overlayContent: {
+        position: "absolute",
         zIndex: 99998,
         bottom: 0,
         right: 0,

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

@@ -2,12 +2,15 @@ import {
     type ReactNode
 } from "react";
 import {
+    type LayoutChangeEvent,
     type StyleProp,
+    type ViewProps,
     type ViewStyle
 } from "react-native";
 
 export type IModalRef = {
     closeAnimation: (onClosed?: () => void) => void;
+    setIsActive: (newActiveState: boolean) => void;
 };
 
 export type ModalAlignContentProp = "center" | "free";
@@ -18,10 +21,22 @@ 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;
+    isActive?: boolean;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
 };
 
 interface IModalProps extends ModalInternalProps {

+ 3 - 2
src/components/pageContainer/index.tsx

@@ -16,12 +16,13 @@ import {
 
 const PageContainer: FC<IPageContainerProps> = ({
     backgroundColor = "default" as keyof NCoreUIKit.ContainerContentColors,
-    isWrapSafeareaContext = true,
+    isWrapSafeareaContext = false,
     safeAreaViewBackgroundColor,
     isScrollable = false,
     safeAreaViewStyle,
     scrollViewStyle,
     scrollViewProps,
+    customTheme,
     children,
     style,
     ...props
@@ -29,7 +30,7 @@ const PageContainer: FC<IPageContainerProps> = ({
     const {
         colors,
         spaces
-    } = NCoreUIKitTheme.useContext();
+    } = NCoreUIKitTheme.useContext(customTheme);
 
     const renderScrollview = () => {
         if(style) {

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

@@ -17,6 +17,12 @@ interface IPageContainerProps {
     style?: StyleProp<ViewStyle>;
     isScrollable?: boolean;
     children?: ReactNode;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
 }
 export type {
     IPageContainerProps as default

+ 848 - 0
src/components/selectBox/index.tsx

@@ -0,0 +1,848 @@
+import {
+    useImperativeHandle,
+    forwardRef,
+    useEffect,
+    useState,
+    type Ref,
+    useRef
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import {
+    type SelectBoxComponent,
+    type ISelectBoxRef,
+    type SelectBoxType,
+    type SelectedItem
+} from "./type";
+import type ISelectBoxProps from "./type";
+import stylesheet, {
+    getSelectBoxType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    type INCoreUIKitIconProps,
+    type NCoreUIKitIcon
+} from "../../types";
+import type {
+    IBottomSheetRef
+} from "../bottomSheet/type";
+import type ITextProps from "../text/type";
+import {
+    BadgeQuestionMarkIcon,
+    BadgeSuccessIcon,
+    BadgeDangerIcon,
+    BadgeAlertIcon,
+    BadgeInfoIcon,
+    CleanIcon
+} from "../../assets/svg";
+import {
+    uuid
+} from "../../utils";
+import SelectSheet from "../selectSheet";
+import Text from "../text";
+
+const SelectBoxTypeIcon: Record<Exclude<SelectBoxType, "default">, NCoreUIKitIcon> = {
+    "question": BadgeQuestionMarkIcon,
+    "success": BadgeSuccessIcon,
+    "warning": BadgeAlertIcon,
+    "danger": BadgeDangerIcon,
+    "info": BadgeInfoIcon
+};
+
+function SelectBox<T>({
+    renderLoadingIcon : LoadingIconComponentProp,
+    rightIcon: RightIconComponentProp,
+    hintTextIcon: HintTextIconProp,
+    isShowSelectSheetTools = true,
+    spreadBehaviour = "baseline",
+    isShowHintTextIcon = false,
+    customSelectSheetLocalize,
+    isWorkWithRealtime = true,
+    isMultipleSelect = false,
+    icon: IconComponentProp,
+    hintTextContainerStyle,
+    removeSelectValidation,
+    customSelectSheetTheme,
+    isCleanEnabled = false,
+    contentContainerStyle,
+    subTitle = "Optional",
+    initialSelectedItems,
+    isSearchable = false,
+    onFocus: onFocusProp,
+    isRequired = false,
+    isDisabled = false,
+    onBlur: onBlurProp,
+    moreLoadThreshold,
+    hintTextIconStyle,
+    data: initialData,
+    selectSheetProps,
+    variant = "text",
+    type = "default",
+    rightIconOnPress,
+    renderOptionIcon,
+    selectValidation,
+    customLocalize,
+    rightIconStyle,
+    isShowSubTitle,
+    cleanIconStyle,
+    titleExtractor,
+    maxChoice = -1,
+    keyExtractor,
+    contentStyle,
+    customTheme,
+    placeholder,
+    iconOnPress,
+    onMoreLoad,
+    isOptional,
+    renderItem,
+    customKey,
+    minChoice,
+    iconStyle,
+    onChange,
+    hintText,
+    onSearch,
+    style,
+    title
+}: ISelectBoxProps<T>, ref: Ref<ISelectBoxRef<T>>) {
+    const {
+        inlineSpaces,
+        typography,
+        radiuses,
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext(customLocalize);
+
+    const bottomSheetRef = useRef<IBottomSheetRef>(null);
+
+    const selectBoxKey = useRef(customKey ? customKey : uuid());
+
+    const currentType = getSelectBoxType({
+        type
+    });
+
+    const {
+        contentContainer: contentContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
+        hintTextIcon: hintTextIconDynamicStyle,
+        contentText: contentTextDynamicStyle,
+        cleanButton: cleanButtonDynamicStyle,
+        rightIcon: rightIconDynamicStyle,
+        container: containerDynamicStyle,
+        hintText: hintTextDynamicStyle,
+        required: requiredDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        overlay: overlayDynamicStyle,
+        content: contentDynamicStyle,
+        title: titleDynamicStyle,
+        icon: iconDynamicStyle
+    } = useStyles({
+        icon: IconComponentProp ? true : false,
+        spreadBehaviour,
+        inlineSpaces,
+        isSearchable,
+        currentType,
+        isDisabled,
+        radiuses,
+        borders,
+        spaces,
+        colors,
+        title,
+        type
+    });
+
+    const styleType = type === "default" ? "neutral" : type === "question" ? "neutral" : type === "danger" ? "error" : type;
+
+    const [
+        data,
+        setData
+    ] = useState<Array<T & SelectedItem | SelectedItem>>([]);
+
+    const [
+        searchedData,
+        setSearchedData
+    ] = useState<Array<T & SelectedItem | SelectedItem>>([]);
+
+    const [
+        isActive,
+        setIsActive
+    ] = useState(false);
+
+    const [
+        searchText,
+        setSearchText
+    ] = useState("");
+
+    const [
+        isLoading,
+        setIsLoading
+    ] = useState(false);
+
+    const [
+        isMoreLoading,
+        setIsMoreLoading
+    ] = useState(true);
+
+    const [
+        selectedItems,
+        setSelectedItems
+    ] = useState<Array<SelectedItem>>(initialSelectedItems ?? []);
+
+    const [
+        tempSelectedItems,
+        setTempSelectedItems
+    ] = useState<Array<SelectedItem>>(initialSelectedItems ?? []);
+
+    const mainSelectedItems = isWorkWithRealtime ? selectedItems : tempSelectedItems;
+
+    useImperativeHandle(
+        ref,
+        () => ({
+            setIsMoreLoading: (e) => setIsMoreLoading(e),
+            setIsLoading: (e) => setIsLoading(e),
+            updateSelections,
+            cleanSelections,
+            updateData,
+            selectAll,
+            cleanAll,
+            cancel,
+            focus,
+            blur,
+            ok
+        }),
+        []
+    );
+
+    useEffect(() => {
+        if(isActive) {
+            if(onFocus) onFocus();
+        } else {
+            if(!isWorkWithRealtime) {
+                setTempSelectedItems([]);
+            }
+
+            setSearchText("");
+            setSearchedData([]);
+
+            setIsMoreLoading(false);
+
+            if(onBlur) onBlur();
+        }
+    }, [isActive]);
+
+    useEffect(() => {
+        if(isSearchable && searchText && searchText.length) {
+            if(onSearch) {
+                const _searchedData = prepareDatas(onSearch({
+                    selectedItems: mainSelectedItems,
+                    setIsMoreLoading,
+                    setIsLoading,
+                    searchText,
+                    data
+                }) as Array<T & SelectedItem>, mainSelectedItems);
+
+                setSearchedData(_searchedData);
+            } else {
+                const _searchedData = data.filter(dI => {
+                    return dI.__title.toLocaleLowerCase().includes(searchText.toLocaleLowerCase());
+                }) as Array<T & SelectedItem>;
+
+                setSearchedData(_searchedData);
+            }
+        } else if(searchedData && searchedData.length) {
+            setSearchedData([]);
+        }
+    }, [
+        data,
+        searchText
+    ]);
+
+    useEffect(() => {
+        const initData = prepareDatas(initialData as Array<T & SelectedItem>, initialSelectedItems);
+
+        setData(initData);
+    }, []);
+
+    const titleProps: ITextProps = {
+        color: currentType.titleColor,
+        variant: "bodyLargeSize"
+    };
+
+    const iconProps: INCoreUIKitIconProps = {
+        size: Number(typography.labelLargeSize.fontSize) + 6,
+        color: currentType.iconColor
+    };
+
+    if(isDisabled) {
+        iconProps.color = "disabled";
+    }
+
+    const blur = () => {
+        setIsActive(false);
+    };
+
+    const focus = () => {
+        if(!isWorkWithRealtime) {
+            setTempSelectedItems(selectedItems);
+        }
+
+        setIsActive(true);
+    };
+
+    const cancel = () => {
+        if(!isWorkWithRealtime) {
+            bottomSheetRef.current?.close(() => {
+                blur();
+            });
+        }
+    };
+
+    const ok = () => {
+        if(!isWorkWithRealtime) {
+            setSelectedItems(tempSelectedItems);
+
+            bottomSheetRef.current?.close(() => {
+                blur();
+            });
+        }
+    };
+
+    const cleanSelections = () => {
+        setSelectedItems([]);
+
+        if(!isWorkWithRealtime) {
+            setTempSelectedItems([]);
+        }
+
+        if(onChange) {
+            onChange([], data, setIsMoreLoading);
+        }
+    };
+
+    const updateData = (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => {
+        const _newData = newData.map((bIItem, bIIndex) => {
+            return {
+                ...bIItem,
+                __title: titleExtractor(bIItem as T & SelectedItem, bIIndex),
+                __key: keyExtractor(bIItem as T & SelectedItem, bIIndex)
+            };
+        });
+
+        setData(_newData);
+
+        const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems ? newSelectedItems : mainSelectedItems)).map((sItem: SelectedItem) => {
+            const originalDataIndex = _newData.findIndex(ssItem => ssItem.__key === sItem.__key);
+
+            return {
+                __title: _newData[originalDataIndex]?.__title,
+                __key: _newData[originalDataIndex]?.__key
+            };
+        });
+
+        if(isWorkWithRealtime) {
+            setSelectedItems(_newSelectedItems);
+        } else {
+            setTempSelectedItems(_newSelectedItems);
+        }
+    };
+
+    const updateSelections = (newSelectedItems: Array<SelectedItem>) => {
+        const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems)).map((sItem: SelectedItem) => {
+            const originalDataIndex = data.findIndex(ssItem => ssItem.__key === sItem.__key);
+
+            return {
+                ...sItem,
+                __title: data[originalDataIndex]?.__title,
+                __key: data[originalDataIndex]?.__key
+            };
+        });
+
+        if(isWorkWithRealtime) {
+            setSelectedItems(_newSelectedItems);
+        } else {
+            setTempSelectedItems(_newSelectedItems);
+        }
+    };
+
+    const prepareDatas = (items: Array<T & SelectedItem>, sItems?: Array<SelectedItem>) => {
+        const initData: Array<SelectedItem | T & SelectedItem> = [];
+
+        if(sItems && sItems.length) {
+            sItems.forEach((iS) => {
+                const dataIndex = items.findIndex((iD, iDI) => keyExtractor(iD, iDI) === iS.__key);
+
+                if(dataIndex === -1) {
+                    initData.unshift(iS);
+                }
+            });
+        }
+
+        const finalData = items.map((bIItem, bIIndex) => {
+            return {
+                ...bIItem,
+                __title: titleExtractor(bIItem, bIIndex),
+                __key: keyExtractor(bIItem, bIIndex)
+            };
+        });
+
+        return [
+            ...initData,
+            ...finalData
+        ];
+    };
+
+    const selectObject = (selectedItem: T & SelectedItem | SelectedItem) => {
+        const isAlreadySelected = mainSelectedItems.findIndex(sItem => sItem.__key === selectedItem.__key);
+        let _selectedItems = JSON.parse(JSON.stringify(mainSelectedItems));
+
+        if(isAlreadySelected !== -1) {
+            let isProcessPerm = false;
+
+            if(isMultipleSelect) {
+                if(minChoice) {
+                    if(_selectedItems.length > minChoice) {
+                        isProcessPerm = true;
+                    }
+                } else {
+                    isProcessPerm = true;
+                }
+            } else {
+                const _minChoice = minChoice === undefined ? 0 : minChoice;
+
+                if(_minChoice > 0) {
+                    // TODO: make toast;
+                } else {
+                    isProcessPerm = true;
+                }
+            }
+
+            if(isProcessPerm) {
+                if(removeSelectValidation) {
+                    if(removeSelectValidation(selectedItem)) {
+                        _selectedItems = _selectedItems.filter((sItem: SelectedItem) => sItem.__key !== selectedItem.__key);
+                    }
+                } else {
+                    _selectedItems = _selectedItems.filter((sItem: SelectedItem) => sItem.__key !== selectedItem.__key);
+                }
+            }
+        } else {
+            let isProcessPerm = false;
+
+            if(isMultipleSelect) {
+                if(_selectedItems.length < maxChoice || maxChoice === -1) {
+                    isProcessPerm = true;
+                } else {
+                    // TODO: make toast;
+                }
+            } else {
+                _selectedItems = [];
+                isProcessPerm = true;
+            }
+
+            if(isProcessPerm) {
+                if(selectValidation) {
+                    if(selectValidation(selectedItem)) {
+                        _selectedItems.push({
+                            __title: selectedItem.__title,
+                            __key: selectedItem.__key
+                        });
+                    }
+                } else {
+                    _selectedItems.push({
+                        __title: selectedItem.__title,
+                        __key: selectedItem.__key
+                    });
+                }
+            }
+        }
+
+        if(isWorkWithRealtime) {
+            setSelectedItems(_selectedItems);
+        } else {
+            setTempSelectedItems(_selectedItems);
+        }
+    };
+
+    const cleanAll = () => {
+        if(minChoice) {
+            console.error("minChoice must be 0 or undefined for cleanAll function.");
+            return;
+        }
+
+        if(isWorkWithRealtime) {
+            setSelectedItems([]);
+        } else {
+            setTempSelectedItems([]);
+        }
+    };
+
+    const selectAll = () => {
+        if(maxChoice !== -1) {
+            console.error("maxChoice must be -1 for selectAll function.");
+            return;
+        }
+
+        const _selectedItems = JSON.parse(JSON.stringify(mainSelectedItems));
+
+        if(searchText && searchText.length) {
+            searchedData.forEach(searchedItem => {
+                if(mainSelectedItems.findIndex(sI => sI.__key === searchedItem.__key) === -1) {
+                    _selectedItems.push({
+                        __title: searchedItem.__title,
+                        __key: searchedItem.__key
+                    });
+                }
+            });
+        } else {
+            data.forEach(dataItem => {
+                if(mainSelectedItems.findIndex(sI => sI.__key === dataItem.__key) === -1) {
+                    _selectedItems.push({
+                        __title: dataItem.__title,
+                        __key: dataItem.__key
+                    });
+                }
+            });
+        }
+
+        if(isWorkWithRealtime) {
+            setSelectedItems(_selectedItems);
+        } else {
+            setTempSelectedItems(_selectedItems);
+        }
+    };
+
+    const onFocus = () => {
+        if(onFocusProp) onFocusProp();
+    };
+
+    const onBlur = () => {
+        if(onBlurProp) onBlurProp();
+    };
+
+    const renderCleanButton = () => {
+        if(isDisabled) {
+            return null;
+        }
+
+        if(variant !== "text") {
+            return null;
+        }
+
+        if(!isCleanEnabled || !selectedItems.length) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            style={[
+                cleanIconStyle,
+                stylesheet.cleanButton,
+                cleanButtonDynamicStyle
+            ]}
+            onPress={() => {
+                if(isWorkWithRealtime) {
+                    setSelectedItems([]);
+                } else {
+                    setTempSelectedItems([]);
+                }
+
+                if(onChange) {
+                    onChange([], data, setIsMoreLoading);
+                }
+            }}
+        >
+            <CleanIcon
+                color="mid"
+                size={20}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderIcon = () => {
+        if (!IconComponentProp) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={iconOnPress}
+            style={[
+                iconStyle,
+                stylesheet.icon,
+                iconDynamicStyle
+            ]}
+        >
+            <IconComponentProp
+                color={iconProps.color}
+                size={iconProps.size}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderRightIcon = () => {
+        if (!RightIconComponentProp) {
+            return null;
+        }
+
+        if(isCleanEnabled && selectedItems.length > 0 && variant === "text") {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={rightIconOnPress}
+            style={[
+                rightIconStyle,
+                stylesheet.rightIcon,
+                rightIconDynamicStyle
+            ]}
+        >
+            <RightIconComponentProp
+                color={iconProps.color}
+                size={iconProps.size}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderHintIcon = () => {
+        if(!isShowHintTextIcon) {
+            return null;
+        }
+
+        if(HintTextIconProp) {
+            return <HintTextIconProp
+                color={isDisabled ? "disabled" : currentType.hintTextIconColor}
+                size={20}
+                style={[
+                    hintTextIconStyle,
+                    stylesheet.hintTextIcon,
+                    hintTextIconDynamicStyle
+                ]}
+            />;
+        }
+
+        const CurrentHintIcon = SelectBoxTypeIcon[type === "default" ? "question" : type];
+
+        return <CurrentHintIcon
+            customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
+            color={currentType.hintTextIconColor}
+            size={20}
+            style={[
+                hintTextIconStyle,
+                stylesheet.hintTextIcon,
+                hintTextIconDynamicStyle
+            ]}
+        />;
+    };
+
+    const renderHintText = () => {
+        if (!hintText) {
+            return null;
+        }
+
+        return <View
+            style={[
+                hintTextContainerStyle,
+                stylesheet.hintText,
+                hintTextDynamicStyle
+            ]}
+        >
+            {renderHintIcon()}
+            <Text
+                customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
+                color={currentType.hintTextColor}
+                variant="labelSmallSize"
+            >
+                {hintText}
+            </Text>
+        </View>;
+    };
+
+    const renderRequired = () => {
+        if(!isRequired) {
+            return null;
+        }
+
+        return <Text
+            color="danger"
+            style={[
+                stylesheet.required,
+                requiredDynamicStyle
+            ]}
+        >*</Text>;
+    };
+
+    const renderSubtitle = () => {
+        if(!isShowSubTitle && !isOptional) {
+            return null;
+        }
+
+        return <Text
+            variant="labelLargeSize"
+            color={titleProps.color}
+            style={[
+                stylesheet.subTitle,
+                subTitleDynamicStyle
+            ]}
+        >
+            ( {isOptional ? localize("is-optional") : subTitle} )
+        </Text>;
+    };
+
+    const renderTitle = () => {
+        if (!title) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            style={[
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
+            ]}
+            onPress={() => {
+                if(!isDisabled) {
+                    focus();
+                }
+            }}
+        >
+            {renderRequired()}
+            <Text
+                {...titleProps}
+                variant={titleProps.variant}
+                color={titleProps.color}
+                style={[
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
+            {renderSubtitle()}
+        </TouchableOpacity>;
+    };
+
+    const renderValue = () => {
+        if(!selectedItems.length) {
+            return <Text
+                style={[
+                    stylesheet.contentText,
+                    contentTextDynamicStyle
+                ]}
+            >
+                {placeholder ? placeholder : localize("select-an-option")}
+            </Text>;
+        }
+
+        return <Text
+            style={[
+                stylesheet.contentText,
+                contentTextDynamicStyle
+            ]}
+        >
+            {selectedItems.length > 1 ? localize("selected-options-with-count", [
+                selectedItems.length
+            ]) : selectedItems[0]?.__title}
+        </Text>;
+    };
+
+    const renderContent = () => {
+        return <View
+            style={[
+                contentStyle,
+                stylesheet.content,
+                contentDynamicStyle
+            ]}
+        >
+            {renderValue()}
+        </View>;
+    };
+
+    const renderOverlay = () => {
+        return <View
+            style={[
+                stylesheet.overlay,
+                overlayDynamicStyle
+            ]}
+        />;
+    };
+
+    return <View
+        style={[
+            style,
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+    >
+        {renderTitle()}
+
+        <TouchableOpacity
+            disabled={isDisabled}
+            style={[
+                contentContainerStyle,
+                stylesheet.contentContainer,
+                contentContainerDynamicStyle
+            ]}
+            onPress={() => {
+                if(!isDisabled) {
+                    focus();
+                }
+            }}
+        >
+            {renderOverlay()}
+
+            {renderIcon()}
+
+            {renderContent()}
+
+            {renderCleanButton()}
+            {renderRightIcon()}
+        </TouchableOpacity>
+
+        {renderHintText()}
+
+        <SelectSheet
+            LoadingIconComponentProp={LoadingIconComponentProp}
+            customLocalize={customSelectSheetLocalize}
+            isWorkWithRealtime={isWorkWithRealtime}
+            mainSelectedItems={mainSelectedItems}
+            moreLoadThreshold={moreLoadThreshold}
+            isShowTools={isShowSelectSheetTools}
+            customTheme={customSelectSheetTheme}
+            isMultipleSelect={isMultipleSelect}
+            renderOptionIcon={renderOptionIcon}
+            setIsMoreLoading={setIsMoreLoading}
+            bottomSheetProps={selectSheetProps}
+            selectBoxKey={selectBoxKey.current}
+            bottomSheetRef={bottomSheetRef}
+            setSearchText={setSearchText}
+            isMoreLoading={isMoreLoading}
+            searchedData={searchedData}
+            isSearchable={isSearchable}
+            keyExtractor={keyExtractor}
+            selectObject={selectObject}
+            setIsLoading={setIsLoading}
+            setIsActive={setIsActive}
+            searchText={searchText}
+            renderItem={renderItem}
+            onMoreLoad={onMoreLoad}
+            maxChoice={maxChoice}
+            minChoice={minChoice}
+            isLoading={isLoading}
+            selectAll={selectAll}
+            cleanAll={cleanAll}
+            isActive={isActive}
+            cancel={cancel}
+            title={title}
+            data={data}
+            ok={ok}
+        />
+    </View>;
+};
+export default forwardRef(SelectBox) as unknown as SelectBoxComponent;

+ 245 - 0
src/components/selectBox/stylesheet.ts

@@ -0,0 +1,245 @@
+import {
+    type ViewStyle,
+    type TextStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type SelectBoxDynamicStyleType,
+    type SelectBoxTypes,
+    type SelectBoxType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const TEXT_INPUT_TYPE_STYLES: Record<
+    SelectBoxType,
+    SelectBoxTypes
+> = {
+    default: {
+        focusBorderColor: "emphasized",
+        borderColor: "emphasized",
+        hintTextIconColor: "mid",
+        placeholderColor: "low",
+        containerColor: "mid",
+        hintTextColor: "mid",
+        titleColor: "high",
+        inputColor: "mid",
+        iconColor: "mid"
+    },
+    danger: {
+        placeholderColor: "dangerLow",
+        hintTextIconColor: "danger",
+        focusBorderColor: "danger",
+        containerColor: "danger",
+        hintTextColor: "danger",
+        borderColor: "danger",
+        titleColor: "danger",
+        inputColor: "danger",
+        iconColor: "danger"
+    },
+    success: {
+        placeholderColor: "successLow",
+        hintTextIconColor: "success",
+        focusBorderColor: "success",
+        containerColor: "success",
+        hintTextColor: "success",
+        borderColor: "success",
+        titleColor: "success",
+        inputColor: "success",
+        iconColor: "success"
+    },
+    warning: {
+        placeholderColor: "warningLow",
+        hintTextIconColor: "warning",
+        focusBorderColor: "warning",
+        containerColor: "warning",
+        hintTextColor: "warning",
+        borderColor: "warning",
+        titleColor: "warning",
+        inputColor: "warning",
+        iconColor: "warning"
+    },
+    info: {
+        placeholderColor: "infoLow",
+        hintTextIconColor: "info",
+        focusBorderColor: "info",
+        containerColor: "info",
+        hintTextColor: "info",
+        borderColor: "info",
+        titleColor: "info",
+        inputColor: "info",
+        iconColor: "info"
+    },
+    question: {
+        focusBorderColor: "emphasized",
+        borderColor: "emphasized",
+        hintTextIconColor: "mid",
+        placeholderColor: "low",
+        containerColor: "mid",
+        hintTextColor: "mid",
+        titleColor: "mid",
+        inputColor: "mid",
+        iconColor: "mid"
+    }
+};
+
+export const getSelectBoxType = ({
+    type
+}: {
+    type: SelectBoxType;
+}) => {
+    const currentType = TEXT_INPUT_TYPE_STYLES[type];
+
+    return currentType;
+};
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flexDirection: "column",
+        boxSizing: "border-box"
+    },
+    contentContainer: {
+        boxSizing: "border-box",
+        flexDirection: "row",
+        borderStyle: "solid",
+        alignItems: "center",
+        position: "relative",
+        minWidth: 250
+    },
+    content: {
+
+    },
+    contentText: {
+        minHeight: 20
+    },
+    cleanButton: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        zIndex: 99
+    },
+    titleContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    title: {
+
+    },
+    icon: {
+        justifyContent: "center",
+        alignContent: "center",
+        alignItems: "center",
+        zIndex: 99
+    },
+    required: {
+    },
+    hintTextIcon: {
+    },
+    hintText: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    subTitle: {
+
+    },
+    overlay: {
+        position: "absolute",
+        zIndex: 98,
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0
+    },
+    rightIcon: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        zIndex: 99
+    }
+});
+
+export const useStyles = ({
+    spreadBehaviour,
+    inlineSpaces,
+    currentType,
+    isDisabled,
+    radiuses,
+    borders,
+    colors,
+    spaces,
+    type
+}: SelectBoxDynamicStyleType) => {
+    const styleType = type === "danger" ? "error" : type === "question" ? "neutral" : type;
+
+    const styles = {
+        container: {
+        } as Mutable<ViewStyle>,
+        contentContainer: {
+            backgroundColor: colors.content.container[currentType.containerColor],
+            borderColor: colors.content.container[currentType.containerColor],
+            paddingBottom: spaces.spacingMd,
+            paddingRight: spaces.spacingMd,
+            borderRadius: radiuses.actions,
+            paddingLeft: spaces.spacingMd,
+            paddingTop: spaces.spacingMd,
+            borderWidth: borders.line
+        } as Mutable<ViewStyle>,
+        content: {
+
+        } as Mutable<ViewStyle>,
+        contentText: {
+
+        } as Mutable<TextStyle>,
+        cleanButton: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        titleContainer: {
+            marginBottom: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
+        icon: {
+            marginRight: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        required: {
+            marginRight: inlineSpaces.required
+        } as Mutable<ViewStyle>,
+        hintTextIcon: {
+            marginRight: spaces.spacingXs
+        } as Mutable<ViewStyle>,
+        hintText: {
+            marginTop: spaces.spacingSm
+        } as Mutable<TextStyle>,
+        subTitle: {
+            marginLeft: inlineSpaces.subTitle
+        } as Mutable<TextStyle>,
+        overlay: {
+            borderRadius: radiuses.actions - 1
+        } as Mutable<ViewStyle>,
+        rightIcon: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>
+    };
+
+    if(isDisabled) {
+        const disableStyleType = styleType === "default" ? "neutral" : styleType;
+
+        styles.overlay.backgroundColor = colors.system.state.overlay.disabled[disableStyleType];
+        styles.container.borderColor = colors.system.state.overlay.disabled[disableStyleType];
+    }
+
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    }
+
+    if (spreadBehaviour === "stretch") {
+        styles.container.justifyContent = "center";
+        styles.container.width = "100%";
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 182 - 0
src/components/selectBox/type.ts

@@ -0,0 +1,182 @@
+import type {
+    ReactElement,
+    ReactNode,
+    Ref
+} from "react";
+import {
+    type ViewStyle
+} from "react-native";
+import {
+    type NCoreUIKitIcon
+} from "../../types";
+import type IBottomSheetProps from "../bottomSheet/type";
+
+export type ISelectBoxRef<T> = {
+    updateData: (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => void;
+    updateSelections: (newSelectedItems: Array<SelectedItem>) => void;
+    setIsMoreLoading: (state: boolean) => void;
+    setIsLoading: (state: boolean) => void;
+    cleanSelections: () => void;
+    selectAll: () => void;
+    cleanAll: () => void;
+    cancel: () => void;
+    focus: () => void;
+    blur: () => void;
+    ok: () => void;
+};
+
+export type SelectBoxDynamicStyleType = {
+    inlineSpaces: NCoreUIKit.ActivePalette["inlineSpaces"];
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spreadBehaviour?: SelectBoxSpreadBehaviour;
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    currentType: SelectBoxTypes;
+    borders: NCoreUIKit.Borders;
+    isSearchable?: boolean;
+    isDisabled?: boolean;
+    type: SelectBoxType;
+    title?: string;
+    icon?: boolean;
+};
+
+export type SelectBoxTypes = {
+    containerColor: keyof NCoreUIKit.ContainerContentColors;
+    focusBorderColor: keyof NCoreUIKit.BorderContentColors;
+    hintTextIconColor: keyof NCoreUIKit.IconContentColors;
+    placeholderColor: keyof NCoreUIKit.TextContentColors;
+    hintTextColor: keyof NCoreUIKit.TextContentColors;
+    borderColor: keyof NCoreUIKit.BorderContentColors;
+    inputColor: keyof NCoreUIKit.TextContentColors;
+    titleColor: keyof NCoreUIKit.TextContentColors;
+    iconColor: keyof NCoreUIKit.IconContentColors;
+};
+
+export type SelectBoxType = "default" | "danger" | "warning" | "question" | "success" | "info";
+
+export type SelectBoxSpreadBehaviour = "baseline" | "stretch" | "free";
+
+export type SelectBoxVariant = "text" | "hidden";
+
+export type SelectBoxComponent = <T>(
+  props: ISelectBoxProps<T> & { ref?: Ref<ISelectBoxRef<T>> }
+) => ReactNode;
+
+export type SelectedItem = {
+    __title: string;
+    __key: string;
+};
+
+interface ISelectBoxProps<T> {
+    onChange?: (
+        selectedItems: Array<SelectedItem>,
+        data: Array<T | T & SelectedItem | SelectedItem>,
+        setIsMoreLoading: (state: boolean) => void
+    ) => void;
+    titleExtractor: (item: T & SelectedItem, index: number) => string;
+    keyExtractor: (item: T & SelectedItem, index: number) => string;
+    removeSelectValidation?: (selectedItem: SelectedItem) => boolean;
+    selectValidation?: (selectedItem: SelectedItem) => boolean;
+    onSearch?: (props: {
+        data: Array<T & SelectedItem | SelectedItem>;
+        setIsMoreLoading: (state: boolean) => void;
+        setIsLoading: (state: boolean) => void;
+        selectedItems: Array<SelectedItem>;
+        searchText: string;
+    }) => Array<T | T & SelectedItem | SelectedItem>;
+    onMoreLoad?: (props: {
+        data: Array<T & SelectedItem | SelectedItem>;
+        setIsMoreLoading: (state: boolean) => void;
+        setIsLoading: (state: boolean) => void;
+        selectedItems: Array<SelectedItem>;
+        searchText: string;
+    }) => void;
+    spreadBehaviour?: SelectBoxSpreadBehaviour;
+    initialSelectedItems?: Array<SelectedItem>;
+    hintTextContainerStyle?: ViewStyle;
+    renderLoadingIcon?: NCoreUIKitIcon;
+    contentContainerStyle?: ViewStyle;
+    hintTextIconStyle?: ViewStyle;
+    rightIconOnPress?: () => void;
+    hintTextIcon?: NCoreUIKitIcon;
+    isShowHintTextIcon?: boolean;
+    moreLoadThreshold?: number;
+    renderOptionIcon?: (props: {
+        item: T & SelectedItem | SelectedItem;
+        index: number;
+    }) => ReactElement;
+    renderItem?: (props: {
+        onSelect: (selectedItem: SelectedItem) => void;
+        data: Array<T & SelectedItem | SelectedItem>;
+        setIsMoreLoading: (state: boolean) => void;
+        setIsLoading: (state: boolean) => void;
+        selectedItems: Array<SelectedItem>;
+        isSelected: boolean;
+        searchText: string;
+        item: SelectedItem;
+        index: number;
+        key: string;
+    }) => ReactElement;
+    customSelectSheetTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    customSelectSheetLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
+    customLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
+    selectSheetProps?: Omit<
+        IBottomSheetProps,
+        "key" |
+        "customKey" |
+        "ref" |
+        "isAutoHeight" |
+        "isActive" |
+        "renderBottom" |
+        "renderHeader" |
+        "onClose"
+    >;
+    isShowSelectSheetTools?: boolean;
+    isWorkWithRealtime?: boolean;
+    isMultipleSelect?: boolean;
+    cleanIconStyle?: ViewStyle;
+    rightIconStyle?: ViewStyle;
+    rightIcon?: NCoreUIKitIcon;
+    variant?: SelectBoxVariant;
+    isShowSubTitle?: boolean;
+    isCleanEnabled?: boolean;
+    iconOnPress?: () => void;
+    contentStyle?: ViewStyle;
+    isSearchable?: boolean;
+    iconStyle?: ViewStyle;
+    icon?: NCoreUIKitIcon;
+    placeholder?: string;
+    isRequired?: boolean;
+    isDisabled?: boolean;
+    isOptional?: boolean;
+    onFocus?: () => void;
+    type?: SelectBoxType;
+    onBlur?: () => void;
+    customKey?: string;
+    maxChoice?: number;
+    minChoice?: number;
+    hintText?: string;
+    subTitle?: string;
+    style?: ViewStyle;
+    title?: string;
+    data: Array<T>;
+    id?: string;
+};
+export type {
+    ISelectBoxProps as default
+};

+ 368 - 0
src/components/selectSheet/index.tsx

@@ -0,0 +1,368 @@
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import {
+    type SelectedItem
+} from "./type";
+import type ISelectSheetProps from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    CheckIcon
+} from "lucide-react-native";
+import BottomSheet from "../bottomSheet";
+import TextInput from "../textInput";
+import Loading from "../loading";
+import Button from "../button";
+import Text from "../text";
+import CheckBox from "../checkBox";
+
+function SelectSheet<T>({
+    LoadingIconComponentProp,
+    isWorkWithRealtime,
+    isShowTools = true,
+    mainSelectedItems,
+    moreLoadThreshold,
+    isMultipleSelect,
+    setIsMoreLoading,
+    renderOptionIcon,
+    bottomSheetProps,
+    bottomSheetRef,
+    customLocalize,
+    setSearchText,
+    isMoreLoading,
+    selectBoxKey,
+    setIsLoading,
+    selectObject,
+    keyExtractor,
+    searchedData,
+    isSearchable,
+    customTheme,
+    setIsActive,
+    onMoreLoad,
+    renderItem,
+    searchText,
+    maxChoice,
+    minChoice,
+    isLoading,
+    selectAll,
+    cleanAll,
+    isActive,
+    cancel,
+    title,
+    data,
+    ok
+}: ISelectSheetProps<T>) {
+    const {
+        radiuses,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext(customLocalize);
+
+    const {
+        moreLoadingContainer: moreLoadingContainerDynamicStyle,
+        checkIconContainer: checkIconContainerDynamicStyle,
+        loadingContainer: loadingContainerDynamicStyle,
+        contentContainer: contentContainerDynamicStyle,
+        headerContainer: headerContainerDynamicStyle,
+        bottomContainer: bottomContainerDynamicStyle,
+        toolsContainer: toolsContainerDynamicStyle,
+        itemContainer: itemContainerDynamicStyle,
+        cancelButton: cancelButtonDynamicStyle,
+        headerTitle: headerTitleDynamicStyle,
+        okButton: okButtonDynamicStyle
+    } = useStyles({
+        isSearchable,
+        radiuses,
+        spaces,
+        colors
+    });
+
+    const renderBottomSheetContent = () => {
+        if(isLoading) {
+            return <View
+                style={[
+                    stylesheet.loadingContainer,
+                    loadingContainerDynamicStyle
+                ]}
+            >
+                {LoadingIconComponentProp ? <LoadingIconComponentProp/> : <Loading/>}
+            </View>;
+        }
+
+        const currentData = searchText && searchText.length ? searchedData : data;
+
+        return <View
+            style={[
+                contentContainerDynamicStyle
+            ]}
+        >
+            {
+                currentData.map((item: T & SelectedItem | SelectedItem, index: number) => {
+                    const isSelected = mainSelectedItems.findIndex((sI: SelectedItem) => {
+                        return sI.__key === item.__key;
+                    }) !== -1;
+
+                    if(renderItem) {
+                        return renderItem({
+                            key: keyExtractor(item as T & SelectedItem, index),
+                            onSelect: (sItem) => {
+                                selectObject(sItem);
+                            },
+                            selectedItems: mainSelectedItems,
+                            setIsMoreLoading,
+                            setIsLoading,
+                            isSelected,
+                            searchText,
+                            index,
+                            item,
+                            data
+                        });
+                    }
+
+                    let isDisabled = false;
+
+                    if(isMultipleSelect && !isSelected && maxChoice !== -1) {
+                        if(!maxChoice) {
+                            throw new Error("If you want to use the \"isMultipleSelect\" prop, you must be provide \"maxChoice\" prop.");
+                        }
+
+                        if(mainSelectedItems.length >= maxChoice) {
+                            isDisabled = true;
+                        }
+                    }
+
+                    return <TouchableOpacity
+                        key={keyExtractor(item as T & SelectedItem, index)}
+                        disabled={isDisabled}
+                        style={[
+                            stylesheet.itemContainer,
+                            itemContainerDynamicStyle,
+                            {
+                                marginTop: index === 0 ? 0 : spaces.spacingSm,
+                                position: "relative"
+                            }
+                        ]}
+                        onPress={() => {
+                            if(isDisabled) {
+                                return;
+                            }
+
+                            selectObject(item);
+                        }}
+                    >
+                        <View
+                            style={[
+                                stylesheet.itemContentContainer,
+                                {
+                                    zIndex: 99999
+                                }
+                            ]}
+                        >
+                            {renderOptionIcon ? renderOptionIcon({
+                                item,
+                                index
+                            }) : null}
+                            <Text
+                                color={isSelected ? "emphasized" : undefined}
+                            >{item.__title}</Text>
+                        </View>
+                        <View
+                            style={[
+                                stylesheet.checkIconContainer,
+                                checkIconContainerDynamicStyle
+                            ]}
+                        >
+                            {isSelected ?
+                                <CheckIcon
+                                    color={colors.content.icon.emphasized}
+                                    size={24}
+                                /> : null}
+                        </View>
+                        {isDisabled ? <View
+                            style={[
+                                {
+                                    backgroundColor: colors.system.disabled.content,
+                                    borderRadius: radiuses.actions,
+                                    position: "absolute",
+                                    zIndex: 99998,
+                                    bottom: 0,
+                                    right: 0,
+                                    left: 0,
+                                    top: 0
+                                }
+                            ]}
+                        /> : null}
+                    </TouchableOpacity>;
+                })
+            }
+            {renderMoreLoading()}
+        </View>;
+    };
+
+    const renderBottomSheetSearchInput = () => {
+        if(!isSearchable) {
+            return null;
+        }
+
+        return <TextInput
+            key={`${selectBoxKey}-searchInput`}
+            placeholder={localize("search")}
+            spreadBehaviour="stretch"
+            onChangeText={(text: string) => {
+                setSearchText(text);
+            }}
+        />;
+    };
+
+    const renderTools = () => {
+        if(!isShowTools) {
+            return null;
+        }
+
+        if(!isMultipleSelect) {
+            return null;
+        }
+
+        if(minChoice && maxChoice !== -1) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.toolsContainer,
+                toolsContainerDynamicStyle
+            ]}
+        >
+            {!minChoice ? <CheckBox
+                isChecked={mainSelectedItems.length === 0 ? "checked" : null}
+                title={localize("clean-all")}
+                spreadBehaviour="free"
+                onPress={() => {
+                    cleanAll();
+                }}
+            /> : null}
+            {maxChoice === -1 ? <CheckBox
+                isChecked={data.length === mainSelectedItems.length ? "checked" : mainSelectedItems.length ? "partially" : null}
+                title={localize("select-all")}
+                spreadBehaviour="free"
+                isFlip={!minChoice}
+                onPress={() => {
+                    selectAll();
+                }}
+            /> : null}
+        </View>;
+    };
+
+    const renderBottomSheetHeader = () => {
+        return <View
+            style={[
+                stylesheet.headerContainer,
+                headerContainerDynamicStyle
+            ]}
+        >
+            <Text
+                variant="titleMediumSize"
+                style={[
+                    headerTitleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
+            {renderBottomSheetSearchInput()}
+            {renderTools()}
+        </View>;
+    };
+
+    const renderBottomSheetBottom = () => {
+        if(isWorkWithRealtime) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.bottomContainer,
+                bottomContainerDynamicStyle
+            ]}
+        >
+            <Button
+                title={localize("cancel")}
+                spreadBehaviour="stretch"
+                variant="outline"
+                type="neutral"
+                onPress={() => {
+                    cancel();
+                }}
+                style={{
+                    ...cancelButtonDynamicStyle
+                }}
+            />
+            <Button
+                spreadBehaviour="stretch"
+                title={localize("ok")}
+                onPress={() => {
+                    ok();
+                }}
+                style={{
+                    ...okButtonDynamicStyle
+                }}
+            />
+        </View>;
+    };
+
+    const renderMoreLoading = () => {
+        if(!isMoreLoading) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.moreLoadingContainer,
+                moreLoadingContainerDynamicStyle
+            ]}
+        >
+            <Loading/>
+        </View>;
+    };
+
+    return <BottomSheet
+        {...bottomSheetProps}
+        scrollEndThreshold={moreLoadThreshold}
+        key={`${selectBoxKey}-bottomsheet`}
+        customKey={selectBoxKey}
+        ref={bottomSheetRef}
+        isAutoHeight={true}
+        isActive={isActive}
+        onScrollEnd={() => {
+            if(onMoreLoad) onMoreLoad({
+                selectedItems: mainSelectedItems,
+                setIsMoreLoading,
+                setIsLoading,
+                searchText,
+                data
+            });
+        }}
+        renderBottom={() => {
+            return renderBottomSheetBottom();
+        }}
+        renderHeader={() => {
+            return renderBottomSheetHeader();
+        }}
+        onClose={() => {
+            setIsActive(false);
+        }}
+    >
+        {renderBottomSheetContent()}
+    </BottomSheet>;
+};
+export default SelectSheet;

+ 102 - 0
src/components/selectSheet/stylesheet.ts

@@ -0,0 +1,102 @@
+import {
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type SelectSheetDynamicStyleType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    headerContainer: {
+        flexDirection: "column",
+        width: "100%"
+    },
+    bottomContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    loadingContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        flex: 1
+    },
+    itemContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    itemContentContainer: {
+        flex: 1
+    },
+    checkIconContainer: {
+        justifyContent: "center",
+        alignItems: "center"
+    },
+    toolsContainer: {
+        justifyContent: "space-between",
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    moreLoadingContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        width: "100%"
+    }
+});
+
+export const useStyles = ({
+    isSearchable,
+    radiuses,
+    spaces,
+    colors
+}: SelectSheetDynamicStyleType) => {
+    const styles = {
+        contentContainer: {
+            paddingVertical: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        headerContainer: {
+            marginBottom: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        headerTitle: {
+
+        } as Mutable<ViewStyle>,
+        bottomContainer: {
+            marginTop: spaces.spacingSm,
+            padding: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        cancelButton: {
+            marginRight: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        okButton: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        itemContainer: {
+            backgroundColor: colors.content.container.mid,
+            paddingHorizontal: spaces.spacingMd,
+            paddingVertical: spaces.spacingSm,
+            borderRadius: radiuses.form
+        } as Mutable<ViewStyle>,
+        checkIconContainer: {
+            height: 24 + spaces.spacingSm,
+            width: 24 + spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        loadingContainer: {
+            padding: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        toolsContainer: {
+            marginTop: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        moreLoadingContainer: {
+            padding: spaces.spacingSm
+        } as Mutable<ViewStyle>
+    };
+
+    if(isSearchable) {
+        styles.headerTitle.marginBottom = spaces.spacingSm;
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 102 - 0
src/components/selectSheet/type.ts

@@ -0,0 +1,102 @@
+import type {
+    SetStateAction,
+    ReactElement,
+    Dispatch,
+    Ref
+} from "react";
+import {
+    type NCoreUIKitIcon
+} from "../../types";
+import type {
+    IBottomSheetRef
+} from "../bottomSheet/type";
+import type IBottomSheetProps from "../bottomSheet/type";
+
+export type SelectSheetDynamicStyleType = {
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    isSearchable?: boolean;
+};
+
+export type SelectedItem = {
+    __title: string;
+    __key: string;
+};
+
+interface ISelectSheetProps<T> {
+    selectObject: (selectedItem: T & SelectedItem | SelectedItem) => void;
+    keyExtractor: (item: T & SelectedItem, index: number) => string;
+    searchedData: Array<SelectedItem | (T & SelectedItem)>;
+    renderItem?: (props: {
+        onSelect: (selectedItem: SelectedItem) => void;
+        data: Array<T & SelectedItem | SelectedItem>;
+        setIsMoreLoading: (state: boolean) => void;
+        setIsLoading: (state: boolean) => void;
+        selectedItems: Array<SelectedItem>;
+        isSelected: boolean;
+        searchText: string;
+        item: SelectedItem;
+        index: number;
+        key: string;
+    }) => ReactElement;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    customLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
+    onMoreLoad?: (props: {
+        data: Array<T & SelectedItem | SelectedItem>;
+        setIsMoreLoading: (state: boolean) => void;
+        setIsLoading: (state: boolean) => void;
+        selectedItems: Array<SelectedItem>;
+        searchText: string;
+    }) => void;
+    setSearchText: Dispatch<SetStateAction<string>>;
+    setIsLoading: Dispatch<SetStateAction<boolean>>;
+    setIsActive: Dispatch<SetStateAction<boolean>>;
+    data: Array<SelectedItem | (T & SelectedItem)>;
+    bottomSheetRef: Ref<IBottomSheetRef> | null;
+    setIsMoreLoading: (state: boolean) => void;
+    LoadingIconComponentProp?: NCoreUIKitIcon;
+    moreLoadThreshold?: number;
+    bottomSheetProps?: Omit<
+        IBottomSheetProps,
+        "key" |
+        "customKey" |
+        "ref" |
+        "isAutoHeight" |
+        "isActive" |
+        "renderBottom" |
+        "renderHeader" |
+        "onClose"
+    >;
+    renderOptionIcon?: (props: {
+        item: T & SelectedItem | SelectedItem;
+        index: number;
+    }) => ReactElement;
+    mainSelectedItems: Array<SelectedItem>;
+    isWorkWithRealtime: boolean;
+    isMultipleSelect?: boolean;
+    isMoreLoading: boolean;
+    isSearchable?: boolean;
+    isShowTools?: boolean;
+    selectAll: () => void;
+    selectBoxKey: string;
+    cleanAll: () => void;
+    maxChoice?: number;
+    minChoice?: number;
+    searchText: string;
+    isLoading: boolean;
+    cancel: () => void;
+    isActive: boolean;
+    ok: () => void;
+    title?: string;
+};
+export type {
+    ISelectSheetProps as default
+};

+ 2 - 1
src/components/text/index.tsx

@@ -14,6 +14,7 @@ const Text: FC<ITextProps> = ({
     variant = "bodyMediumSize",
     color = "mid",
     customColor,
+    customTheme,
     children,
     style,
     ...props
@@ -21,7 +22,7 @@ const Text: FC<ITextProps> = ({
     const {
         typography,
         colors
-    } = NCoreUIKitTheme.useContext();
+    } = NCoreUIKitTheme.useContext(customTheme);
 
     return (
         <NativeText

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

@@ -13,6 +13,12 @@ interface ITextProps extends TextProps {
     variant?: keyof NCoreUIKit.Typography;
     customColor?: string;
     children?: ReactNode;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
 }
 export type {
     ITextProps as default

+ 23 - 12
src/components/textInput/index.tsx

@@ -54,6 +54,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     hintTextIcon: HintTextIconProp,
     spreadBehaviour = "baseline",
     isShowHideTextButton = true,
+    isShowHintTextIcon = false,
     icon: IconComponentProp,
     hintTextContainerStyle,
     isCleanEnabled = false,
@@ -67,11 +68,13 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     variant = "text",
     type = "default",
     rightIconOnPress,
+    customLocalize,
     rightIconStyle,
     isShowSubTitle,
     cleanIconStyle,
     onChangeText,
     initialValue,
+    customTheme,
     placeholder,
     iconOnPress,
     isOptional,
@@ -90,11 +93,11 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
         borders,
         spaces,
         colors
-    } = NCoreUIKitTheme.useContext();
+    } = NCoreUIKitTheme.useContext(customTheme);
 
     const {
         localize
-    } = NCoreUIKitLocalize.useContext();
+    } = NCoreUIKitLocalize.useContext(customLocalize);
 
     const currentType = getTextInputType({
         type
@@ -102,6 +105,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
 
     const {
         hideTextIconContainer: hideTextIconContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
         hintTextIcon: hintTextIconDynamicStyle,
         cleanButton: cleanButtonDynamicStyle,
         rightIcon: rightIconDynamicStyle,
@@ -315,7 +319,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     };
 
     const renderHintIcon = () => {
-        if(!type) {
+        if(!isShowHintTextIcon) {
             return null;
         }
 
@@ -402,25 +406,32 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
             return null;
         }
 
-        return <Text
-            {...titleProps}
-            variant={titleProps.variant}
-            color={titleProps.color}
+        return <View
             style={[
-                stylesheet.title,
-                titleDynamicStyle
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
             ]}
         >
             {renderRequired()}
-            {title}
+            <Text
+                {...titleProps}
+                variant={titleProps.variant}
+                color={titleProps.color}
+                style={[
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
             {renderSubtitle()}
-        </Text>;
+        </View>;
     };
 
     const renderInput = () => {
         return <NativeTextInput
             {...props}
-            placeholderTextColor={currentType.placeholderColor}
+            placeholderTextColor={colors.content.text[currentType.placeholderColor]}
             secureTextEntry={variant === "hidden" && hideValue}
             underlineColorAndroid="rgba(255,255,255,0)"
             placeholder={placeholder}

+ 10 - 1
src/components/textInput/stylesheet.ts

@@ -137,6 +137,10 @@ const stylesheet = StyleSheet.create({
         zIndex: 99,
         height: 18
     },
+    titleContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
     title: {
     },
     icon: {
@@ -211,9 +215,12 @@ export const useStyles = ({
         hideTextIconContainer: {
             marginLeft: spaces.spacingSm
         } as Mutable<ViewStyle>,
-        title: {
+        titleContainer: {
             marginBottom: spaces.spacingSm
         } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
         icon: {
             marginRight: spaces.spacingSm
         } as Mutable<ViewStyle>,
@@ -252,7 +259,9 @@ export const useStyles = ({
     }
 
     if (spreadBehaviour === "stretch") {
+        styles.container.alignSelf = spreadBehaviour;
         styles.container.justifyContent = "center";
+        styles.container.flexShrink = 1;
         styles.container.width = "100%";
     }
 

+ 10 - 0
src/components/textInput/type.ts

@@ -52,6 +52,15 @@ export type TextInputSpreadBehaviour = "baseline" | "stretch" | "free";
 export type TextInputVariant = "text" | "hidden";
 
 interface ITextInputProps extends TextInputProps {
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    customLocalize?: {
+        activeLocale?: NCoreUIKit.LocaleKey;
+    };
     spreadBehaviour?: TextInputSpreadBehaviour;
     onChangeText?: (value: string) => void;
     validation?: (text: string) => boolean;
@@ -62,6 +71,7 @@ interface ITextInputProps extends TextInputProps {
     hideTextIconStyle?: ViewStyle;
     rightIconOnPress?: () => void;
     hintTextIcon?: NCoreUIKitIcon;
+    isShowHintTextIcon?: boolean;
     cleanIconStyle?: ViewStyle;
     rightIconStyle?: ViewStyle;
     rightIcon?: NCoreUIKitIcon;

+ 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>;

+ 6 - 0
src/index.tsx

@@ -1,4 +1,5 @@
 export {
+    getNCoreUIKitVersion,
     setupNCoreUIKit
 } from "./core";
 
@@ -10,7 +11,11 @@ export {
 
 export {
     PageContainer,
+    BottomSheet,
+    SelectSheet,
     TextInput,
+    SelectBox,
+    CheckBox,
     Loading,
     // Dialog,
     Button,
@@ -19,6 +24,7 @@ export {
 } from "./components";
 
 export type {
+    IBottomSheetRef,
     // IDialogRef,
     IModalRef
 } from "./components";

+ 2 - 0
src/types/index.ts

@@ -65,6 +65,8 @@ type NCoreUIKitConfig = ThemesType & LocalizeType & ModalType;
 
 declare global {
     namespace NCoreUIKit {
+        type GapPropagationKey = GapPropagationType;
+        type SharpnessKey = SharpnessType;
         type PaletteKey = PaletteType;
         type LocaleKey = LocaleType;
         type ThemeKey = ThemeType;

+ 6 - 0
src/utils/index.ts

@@ -1,7 +1,13 @@
 import {
+    Dimensions,
     Platform
 } from "react-native";
 
+const dimensions = Dimensions.get("window");
+
+export const windowHeight = dimensions.height;
+export const windowWidth = dimensions.width;
+
 export const uuid = () => {
     return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
         const r = Math.random() * 16 | 0, v = c == "x" ? r : (r & 0x3 | 0x8);

+ 10 - 0
src/variants/locales/default.json

@@ -3,8 +3,13 @@
         "locale": "tr-TR",
         "isRTL": false,
         "translations": {
+            "selected-options-with-count": "{{0}} items selected",
+            "select-an-option": "Select an option",
+            "select-all": "Select All",
             "is-optional": "Opsiyonel",
+            "clean-all": "Clean All",
             "cancel": "İptal",
+            "search": "Ara",
             "ok": "Tamam"
         }
     },
@@ -12,8 +17,13 @@
         "locale": "en-US",
         "isRTL": false,
         "translations": {
+            "selected-options-with-count": "{{0}} seçim yapıldı",
+            "select-an-option": "Seçim yapın",
+            "clean-all": "Tümünü Temizle",
+            "select-all": "Tümünü Seç",
             "is-optional": "Optional",
             "cancel": "Cancel",
+            "search": "Search",
             "ok": "Okey"
         }
     }

+ 2 - 2
src/variants/themes/default.json

@@ -43,8 +43,8 @@
             }
         },
         "inlineSpaces": {
-            "subTitle": 2,
-            "required": 2
+            "subTitle": 4,
+            "required": 4
         },
         "borders": {
             "line": 2

+ 224 - 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"
@@ -11900,6 +12015,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lucide-react-native@npm:^1.8.0":
+  version: 1.8.0
+  resolution: "lucide-react-native@npm:1.8.0"
+  peerDependencies:
+    react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+    react-native: "*"
+    react-native-svg: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+  checksum: 10c0/3a04b9c2d8bf91c23a4c5ff64be4b27dbbc82df093e1a9c363d9ea7936e417a780a1226c84365ff2da32b90a9ecf73aaa1d7631421c66ba79299ffec6631f996
+  languageName: node
+  linkType: hard
+
 "macos-release@npm:^3.3.0":
   version: 3.4.0
   resolution: "macos-release@npm:3.4.0"
@@ -12985,7 +13111,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,10 +13173,14 @@ __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"
     expo-status-bar: "npm:~55.0.4"
+    lucide-react-native: "npm:^1.8.0"
     ncore-context: "npm:^1.0.5"
     react: "npm:19.2.0"
     react-dom: "npm:19.2.0"
@@ -13059,6 +13189,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 +14584,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 +14695,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 +14718,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 +14807,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 +15800,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 +15941,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 +16141,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 +16323,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 +17546,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 +17688,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