Bläddra i källkod

Feature: Dialog Component and Dialog system completed.

lfabl 1 månad sedan
förälder
incheckning
17fcdf38ee

+ 75 - 2
example/src/pages/home/index.tsx

@@ -9,6 +9,8 @@ import stylesheet from "./stylesheet";
 import {
     type IBottomSheetRef,
     getNCoreUIKitVersion,
+    NCoreUIKitSnackBar,
+    NCoreUIKitDialog,
     NCoreUIKitToast,
     NCoreUIKitTheme,
     BottomSheet,
@@ -16,8 +18,8 @@ import {
     SelectBox,
     CheckBox,
     Button,
-    Text,
-    NCoreUIKitSnackBar
+    Dialog,
+    Text
 } from "ncore-ui-kit-mobile";
 import {
     useNavigation
@@ -202,6 +204,11 @@ const Home = () => {
         setIsCheckboxActive
     ] = useState<"partially" | "checked" | null>("checked");
 
+    const [
+        isDialogActive,
+        setIsDialogActive
+    ] = useState(false);
+
     return <View
         style={[
             stylesheet.container,
@@ -261,6 +268,14 @@ const Home = () => {
             title="Open BottomSheet"
             variant="filled"
         />
+        <Button
+            onPress={() => {
+                setIsDialogActive(!isDialogActive);
+            }}
+            type="neutral"
+            title="Open Local Dialog"
+            variant="filled"
+        />
         <Button
             title="Open Snackbar"
             variant="filled"
@@ -274,6 +289,37 @@ const Home = () => {
                 });
             }}
         />
+        <Button
+            onPress={() => {
+                NCoreUIKitDialog.open({
+                    content: "etgweı09gı9w0eg",
+                    title: "Merhaba Dünya!",
+                    isVisible: true,
+                    onOverlayPress: ({
+                        closeAnimation
+                    }) => {
+                        closeAnimation();
+                    },
+                    variant: "info",
+                    secondaryButtonProps: {
+                        title: "sdfgsdg",
+                        onPress: () => {
+
+                        }
+                    },
+                    primaryButtonProps: {
+                        onPress: ({
+                            closeAnimation
+                        }) => {
+                            closeAnimation();
+                        }
+                    }
+                });
+            }}
+            type="neutral"
+            title="Open Dialog"
+            variant="filled"
+        />
         <Button
             title="Open Toast"
             variant="filled"
@@ -289,6 +335,33 @@ const Home = () => {
                 });
             }}
         />
+        <Dialog
+            isVisible={isDialogActive}
+            content="etgweı09gı9w0eg"
+            title="Merhaba Dünya!"
+            modalProps={{
+                isWorkWithPortal: false
+            }}
+            onOverlayPress={({
+                closeAnimation
+            }) => {
+                closeAnimation(() => setIsDialogActive(false));
+            }}
+            variant="info"
+            secondaryButtonProps={{
+                title: "sdfgsdg",
+                onPress: () => {
+
+                }
+            }}
+            primaryButtonProps={{
+                onPress: ({
+                    closeAnimation
+                }) => {
+                    closeAnimation(() => setIsDialogActive(false));
+                }
+            }}
+        />
         <BottomSheet
             ref={bottomSheetRef}
             renderHeader={() => {

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

@@ -24,7 +24,6 @@ export type ButtonDynamicStyleType = {
     icon?: NCoreUIKitIcon;
     isDisabled?: boolean;
     isLoading?: boolean;
-    theme?: undefined;
     type: ButtonType;
     title?: string;
 };

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

@@ -17,7 +17,6 @@ export type CheckBoxDynamicStyleType = {
     isDisabled?: boolean;
     isLoading?: boolean;
     type: CheckBoxType;
-    theme?: undefined;
     isFlip?: boolean;
 };
 

+ 335 - 0
src/components/dialog/index.tsx

@@ -0,0 +1,335 @@
+import {
+    useImperativeHandle,
+    forwardRef,
+    useEffect,
+    useRef
+} from "react";
+import {
+    Animated,
+    Easing,
+    View
+} from "react-native";
+import type IDialogProps from "./type";
+import type {
+    IDialogRef
+} from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type {
+    RefForwardingComponent
+} from "../../types";
+import type {
+    IModalRef
+} from "../modal/type";
+import {
+    X as XIcon
+} from "lucide-react-native";
+import Button from "../button";
+import Modal from "../modal";
+import Text from "../text";
+import {
+    uuid
+} from "../../utils";
+
+const Dialog: RefForwardingComponent<IDialogRef, IDialogProps> = ({
+    bottomContentContainerStyle,
+    contentJustify = "centered",
+    contentContainerStyle,
+    bottomContainerStyle,
+    headerContainerStyle,
+    secondaryButtonStyle,
+    secondaryButtonProps,
+    primaryButtonStyle,
+    primaryButtonProps,
+    closeIconProps = {
+        color: "low",
+        size: 22
+    },
+    isVisible = false,
+    withModal = true,
+    headerComponent,
+    bottomComponent,
+    onOverlayPress,
+    variant = "ok",
+    modalProps,
+    id: outID,
+    onClosed,
+    children,
+    content,
+    style,
+    title
+}, ref) => {
+    const {
+        radiuses,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext();
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const id = useRef(outID ? outID : uuid());
+
+    const {
+        bottomContentContainer: bottomContentContainerDynamicStyle,
+        headerContainer: headerContainerDynamicStyle,
+        bottomContainer: bottomContainerDynamicStyle,
+        primaryButton: primaryButtonDynamicStyle,
+        contentText: contentTextDynamicStyle,
+        headerTitle: headerTitleDynamicStyle,
+        container: containerDynamicStyle,
+        closeIcon: closeIconDynamicStyle,
+        content: contentDynamicStyle
+    } = useStyles({
+        contentJustify,
+        isVisible,
+        radiuses,
+        variant,
+        colors,
+        spaces
+    });
+
+    const scaleAnim = useRef(new Animated.Value(0)).current;
+
+    const modalRef = useRef<IModalRef>(null);
+
+    useEffect(() => {
+        if(isVisible) {
+            Animated.timing(scaleAnim, {
+                useNativeDriver: true,
+                easing: Easing.linear,
+                duration: 350,
+                toValue: 1
+            }).start();
+        }
+    }, [isVisible]);
+
+    useImperativeHandle(
+        ref,
+        () => ({
+            closeAnimation
+        }),
+        []
+    );
+
+    const closeAnimation = (_onClosed?: (props: {
+        id: string;
+    }) => void) => {
+        Animated.timing(scaleAnim, {
+            useNativeDriver: true,
+            easing: Easing.linear,
+            duration: 250,
+            toValue: 0
+        }).start(({
+            finished
+        }) => {
+            if(finished) {
+                if(_onClosed) _onClosed({
+                    id: id.current
+                });
+
+                if(onClosed) onClosed({
+                    id: id.current
+                });
+
+                if(modalRef && modalRef.current) modalRef.current.closeAnimation();
+            }
+        });
+    };
+
+    const renderHeader = () => {
+        return <View
+            style={[
+                headerContainerStyle,
+                stylesheet.headerContainer,
+                headerContainerDynamicStyle
+            ]}
+        >
+            {headerComponent || <Text
+                style={[
+                    stylesheet.headerTitle,
+                    headerTitleDynamicStyle
+                ]}
+                variant="titleLargeSize"
+                color="mid"
+            >
+                {title}
+            </Text>}
+        </View>;
+    };
+
+    const renderBottom = () => {
+        if(variant === "info") {
+            return null;
+        }
+
+        return <View
+            style={[
+                bottomContainerStyle,
+                stylesheet.bottomContainer,
+                bottomContainerDynamicStyle
+            ]}
+        >
+            {bottomComponent || <View
+                style={[
+                    bottomContentContainerStyle,
+                    stylesheet.bottomContentContainer,
+                    bottomContentContainerDynamicStyle
+                ]}
+            >
+                {secondaryButton()}
+                {primaryButton()}
+            </View>}
+        </View>;
+    };
+
+    const secondaryButton = () => {
+        if(variant !== "yes-no") {
+            return null;
+        }
+
+        return <Button
+            onPress={() => {
+                if(secondaryButtonProps?.onPress) secondaryButtonProps.onPress({
+                    closeAnimation: (onClosed?: (props: {
+                        id: string;
+                    }) => void) => {
+                        closeAnimation(onClosed);
+                    }
+                });
+            }}
+            spreadBehaviour={contentJustify === "centered" ? "stretch" : "free"}
+            title={secondaryButtonProps?.title || localize("cancel")}
+            isLoading={secondaryButtonProps?.isLoading}
+            style={secondaryButtonStyle}
+            variant="outline"
+            type="neutral"
+        />;
+    };
+
+    const primaryButton = () => {
+        return <Button
+            displayBehaviourWhileLoading={primaryButtonProps?.displayBehaviourWhileLoading}
+            spreadBehaviour={contentJustify === "centered" ? "stretch" : "free"}
+            onPress={() => {
+                if(primaryButtonProps?.onPress) primaryButtonProps?.onPress({
+                    closeAnimation: (onClosed?: (props: {
+                        id: string;
+                    }) => void) => {
+                        closeAnimation(onClosed);
+                    }
+                });
+            }}
+            title={primaryButtonProps?.title || localize("ok")}
+            isLoading={primaryButtonProps?.isLoading}
+            style={[
+                primaryButtonStyle,
+                stylesheet.primaryButton,
+                primaryButtonDynamicStyle
+            ]}
+            variant="filled"
+            type="primary"
+            size="medium"
+        />;
+    };
+
+    const renderCloseIcon = () => {
+        if(variant !== "info") {
+            return null;
+        }
+
+        if(contentJustify === "centered") {
+            return null;
+        }
+
+        return <Button
+            onPress={() => {
+                if(onOverlayPress) onOverlayPress({
+                    closeAnimation: (onClosed?: (props: {
+                        id: string;
+                    }) => void) => {
+                        closeAnimation(onClosed);
+                    }
+                });
+            }}
+            icon={() => <XIcon
+                color={closeIconProps.color}
+                size={closeIconProps.size}
+            />}
+            style={[
+                stylesheet.closeIcon,
+                closeIconDynamicStyle
+            ]}
+            isCustomPadding={true}
+            variant="ghost"
+        />;
+    };
+
+    const renderDialog = () => {
+        if(!isVisible) {
+            return null;
+        }
+
+        return <Animated.View
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle,
+                {
+                    transform: [{
+                        scale: scaleAnim
+                    }]
+                }
+            ]}
+        >
+            {renderCloseIcon()}
+            {renderHeader()}
+            <View
+                style={[
+                    contentContainerStyle,
+                    stylesheet.content,
+                    contentDynamicStyle
+                ]}
+            >
+                {children || <Text
+                    style={[
+                        stylesheet.contentText,
+                        contentTextDynamicStyle
+                    ]}
+                    variant="bodyLargeSize"
+                >
+                    {content}
+                </Text>}
+            </View>
+            {renderBottom()}
+        </Animated.View>;
+    };
+
+    if(!isVisible) {
+        return null;
+    }
+
+    return withModal ? <Modal
+        {...modalProps}
+        id={`${id.current}-modal`}
+        ref={modalRef}
+        onOverlayPress={() => {
+            if(onOverlayPress) onOverlayPress({
+                closeAnimation: (onClosed?: (props: {
+                    id: string;
+                }) => void) => {
+                    closeAnimation(onClosed);
+                }
+            });
+        }}
+    >
+        {renderDialog()}
+    </Modal> : renderDialog();
+};
+export default forwardRef(Dialog);

+ 126 - 0
src/components/dialog/stylesheet.ts

@@ -0,0 +1,126 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type DialogDynamicStyleType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flexDirection: "column",
+        position: "relative",
+        overflow: "hidden",
+        maxHeight: "80%",
+        maxWidth: "85%",
+        minWidth: "23%",
+        zIndex: 99998
+    },
+    content: {
+        flexWrap: "wrap"
+    },
+    headerContainer: {
+        paddingBottom: 0
+    },
+    bottomContainer: {
+        width: "100%"
+    },
+    bottomContentContainer: {
+        justifyContent: "flex-end",
+        flexDirection: "row"
+    },
+    headerTitle: {
+
+    },
+    contentText: {
+
+    },
+    primaryButton: {
+
+    },
+    closeIcon: {
+        position: "absolute",
+        right: 0,
+        top: 0
+    }
+});
+
+export const useStyles = ({
+    contentJustify,
+    isVisible,
+    radiuses,
+    variant,
+    colors,
+    spaces
+}: DialogDynamicStyleType) => {
+    const styles = {
+        container: {
+            backgroundColor: colors.content.container.default,
+            borderRadius: radiuses.md
+        } as Mutable<ViewStyle>,
+        content: {
+            paddingBottom: spaces.spacingMd,
+            paddingRight: spaces.spacingMd,
+            paddingLeft: spaces.spacingMd,
+            paddingTop: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        headerContainer: {
+            paddingRight: spaces.spacingMd,
+            paddingLeft: spaces.spacingMd,
+            paddingTop: spaces.spacingLg
+        } as Mutable<ViewStyle>,
+        bottomContainer: {
+            paddingTop: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        bottomContentContainer: {
+            paddingBottom: spaces.spacingMd,
+            paddingRight: spaces.spacingMd,
+            paddingLeft: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        headerTitle: {
+
+        } as Mutable<TextStyle>,
+        contentText: {
+
+        } as Mutable<TextStyle>,
+        closeIcon: {
+            padding: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        primaryButton: {
+
+        } as Mutable<ViewStyle>
+    };
+
+    if(variant === "yes-no") {
+        styles.primaryButton.marginLeft = spaces.spacingMd;
+    }
+
+    if(isVisible === false) {
+        styles.container.display = "none";
+    }
+
+    if(contentJustify === "centered") {
+        styles.content.justifyContent = "center";
+        styles.content.alignItems = "center";
+        styles.content.display = "flex";
+
+        styles.contentText.textAlign = "center";
+
+        styles.headerContainer.justifyContent = "center";
+        styles.headerContainer.alignItems = "center";
+        styles.headerContainer.display = "flex";
+
+        styles.headerTitle.textAlign = "center";
+    }
+
+    if(variant === "info") {
+        styles.content.paddingBottom = spaces.spacingLg;
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 83 - 0
src/components/dialog/type.ts

@@ -0,0 +1,83 @@
+import {
+    type ReactNode
+} from "react";
+import {
+    type StyleProp,
+    type ViewStyle
+} from "react-native";
+import type {
+    ModalInternalProps
+} from "../modal/type";
+import type IButtonProps from "../button/type";
+import type {
+    INCoreUIKitIconProps
+} from "../../types";
+import type {
+    ButtonDisplayBehaviourWhileLoading
+} from "../button/type";
+
+export type IDialogRef = {
+    closeAnimation: (onClosed: (props: {
+        id: string;
+    }) => void) => void;
+};
+
+export type DialogDynamicStyleType = {
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    contentJustify?: DialogContentJustify;
+    variant: DialogVariant;
+    isVisible?: boolean;
+};
+
+export type DialogVariant = "yes-no" | "ok" | "info";
+
+export type DialogContentJustify = "centered" | "language-based";
+
+type DialogButton = {
+    displayBehaviourWhileLoading?: ButtonDisplayBehaviourWhileLoading;
+    onPress?: (props: {
+        closeAnimation: (onClosed?: (props: {
+            id: string;
+        }) => void) => void;
+    }) => void;
+    isLoading?: boolean;
+    title?: string;
+};
+
+interface IDialogProps {
+    bottomContentContainerStyle?: StyleProp<ViewStyle> | Array<StyleProp<ViewStyle>>;
+    contentContainerStyle?: StyleProp<ViewStyle> | Array<StyleProp<ViewStyle>>;
+    bottomContainerStyle?: StyleProp<ViewStyle> | Array<StyleProp<ViewStyle>>;
+    headerContainerStyle?: StyleProp<ViewStyle> | Array<StyleProp<ViewStyle>>;
+    style?: StyleProp<ViewStyle> | Array<StyleProp<ViewStyle>>;
+    onOverlayPress?: (props: {
+        closeAnimation: (onClosed?: (props: {
+            id: string;
+        }) => void) => void;
+    }) => void;
+    secondaryButtonStyle?: IButtonProps["style"];
+    modalProps?: Omit<ModalInternalProps, "id">;
+    primaryButtonStyle?: IButtonProps["style"];
+    closeIconProps?: INCoreUIKitIconProps;
+    contentJustify?: DialogContentJustify;
+    secondaryButtonProps?: DialogButton;
+    primaryButtonProps?: DialogButton;
+    headerComponent?: ReactNode;
+    bottomComponent?: ReactNode;
+    variant?: DialogVariant;
+    children?: ReactNode;
+    isVisible?: boolean;
+    withModal?: boolean;
+    onClosed?: (props: {
+        id: string;
+    }) => void;
+    content?: string;
+    title: string;
+    id?: string;
+};
+
+export type {
+    IDialogProps as default
+};

+ 1 - 2
src/components/index.ts

@@ -10,7 +10,6 @@ export {
     default as PageContainer
 } from "./pageContainer";
 
-/*
 export {
     default as Dialog
 } from "./dialog";
@@ -18,7 +17,7 @@ export {
 export type {
     IDialogRef
 } from "./dialog/type";
-*/
+
 export {
     default as Modal
 } from "./modal";

+ 45 - 8
src/components/modal/index.tsx

@@ -25,6 +25,9 @@ import {
 import {
     Portal
 } from "../../helpers/portalize";
+import {
+    uuid
+} from "../../utils";
 
 /**
  * A generic modal
@@ -32,9 +35,11 @@ import {
  * @returns Element
  */
 const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
+    isScaleAnimatedOnlyOpen = true,
     isDisabledOverlay = false,
     isContentRequired = true,
     isOverlayVisible = true,
+    isScaleAnimated = false,
     alignContent = "center",
     isWorkWithPortal = true,
     isActive: isActiveProp,
@@ -44,14 +49,19 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
     contentStyle,
     overlayProps,
     customTheme,
+    id: outID,
     children,
+    onClosed,
     style
 }, ref) => {
     const {
         colors
     } = NCoreUIKitTheme.useContext(customTheme);
 
-    const scaleAnim = useRef(new Animated.Value(isAnimated ? 0 : 1)).current;
+    const id = useRef(outID ? outID : uuid());
+
+    const scaleAnim = useRef(new Animated.Value(isAnimated && isScaleAnimated ? 0 : 1)).current;
+    const opacityAnim = useRef(new Animated.Value(isAnimated ? 0 : 1)).current;
 
     const [
         isActive,
@@ -60,14 +70,23 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
 
     useEffect(() => {
         if(isAnimated) {
-            Animated.timing(scaleAnim, {
-                easing: Easing.out(Easing.back(1.5)),
+            if(isScaleAnimated) {
+                Animated.timing(scaleAnim, {
+                    easing: Easing.out(Easing.back(1.5)),
+                    useNativeDriver: true,
+                    duration: 350,
+                    toValue: 1
+                }).start();
+            }
+
+            Animated.timing(opacityAnim, {
                 useNativeDriver: true,
+                easing: Easing.linear,
                 duration: 350,
                 toValue: 1
             }).start();
         }
-    }, []);
+    }, [isActive]);
 
     useEffect(() => {
         if(isActiveProp !== undefined) {
@@ -84,18 +103,35 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
         []
     );
 
-    const closeAnimation = (onClosed?: () => void) => {
+    const closeAnimation = (_onClosed?: (props: {
+        id: string;
+    }) => void) => {
         if(isAnimated) {
-            Animated.timing(scaleAnim, {
-                easing: Easing.in(Easing.ease),
+            if(isScaleAnimated && !isScaleAnimatedOnlyOpen) {
+                Animated.timing(scaleAnim, {
+                    easing: Easing.in(Easing.ease),
+                    useNativeDriver: true,
+                    duration: 250,
+                    toValue: 0
+                }).start();
+            }
+
+            Animated.timing(opacityAnim, {
                 useNativeDriver: true,
+                easing: Easing.linear,
                 duration: 250,
                 toValue: 0
             }).start(({
                 finished
             }) => {
                 if(finished) {
-                    if(onClosed) onClosed();
+                    if(_onClosed) _onClosed({
+                        id: id.current
+                    });
+
+                    if(onClosed) onClosed({
+                        id: id.current
+                    });
                 }
             });
         }
@@ -143,6 +179,7 @@ const Modal: RefForwardingComponent<IModalRef, IModalProps> = ({
                 {
                     justifyContent: alignContent === "center" ? "center" : undefined,
                     alignItems: alignContent === "center" ? "center" : "baseline",
+                    opacity: opacityAnim,
                     transform: [{
                         scale: scaleAnim
                     }]

+ 17 - 9
src/components/modal/type.ts

@@ -16,27 +16,35 @@ export type IModalRef = {
 export type ModalAlignContentProp = "center" | "free";
 
 export type ModalInternalProps = {
-    onOverlayPress?: (props: {
-        closeAnimation: (callback?: () => void) => void
-    }) => void;
     contentStyle?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
     style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
     onContainerLayout?: (e: LayoutChangeEvent) => void;
+    customTheme?: {
+        gapPropagation?: NCoreUIKit.GapPropagationKey;
+        sharpness?: NCoreUIKit.SharpnessKey;
+        paletteKey?: NCoreUIKit.PaletteKey;
+        themeKey?: NCoreUIKit.ThemeKey;
+    };
+    onOverlayPress?: (props: {
+        closeAnimation: (onClosed?: (props: {
+            id: string;
+        }) => void) => void;
+    }) => void;
     alignContent?: ModalAlignContentProp;
+    isScaleAnimatedOnlyOpen?: boolean;
     isDisabledOverlay?: boolean;
     isContentRequired?: boolean;
     isOverlayVisible?: boolean;
     isWorkWithPortal?: boolean;
+    isScaleAnimated?: boolean;
     overlayProps?: ViewProps;
     isAnimated?: boolean;
     children?: ReactNode;
+    onClosed?: (props: {
+        id: string;
+    }) => void;
     isActive?: boolean;
-    customTheme?: {
-        gapPropagation?: NCoreUIKit.GapPropagationKey;
-        sharpness?: NCoreUIKit.SharpnessKey;
-        paletteKey?: NCoreUIKit.PaletteKey;
-        themeKey?: NCoreUIKit.ThemeKey;
-    };
+    id: string;
 };
 
 interface IModalProps extends ModalInternalProps {

+ 0 - 1
src/components/snackBar/type.ts

@@ -16,7 +16,6 @@ export type SnackBarDynamicStyleType = {
     currentType: SnackBarTypes;
     safeAreaTop: number;
     type: SnackBarType;
-    theme?: undefined;
 };
 
 export type SnackBarTypes = {

+ 0 - 1
src/components/toast/type.ts

@@ -16,7 +16,6 @@ export type ToastDynamicStyleType = {
     currentType: ToastTypes;
     safeAreaBottom: number;
     subTitle?: string;
-    theme?: undefined;
     type: ToastType;
 };
 

+ 120 - 0
src/context/dialog.tsx

@@ -0,0 +1,120 @@
+import {
+    type ReactNode,
+    Fragment
+} from "react";
+import {
+    type DialogContextType,
+    type DialogDataType
+} from "../types/dialog";
+import NCoreContext, {
+    type ConfigType
+} from "ncore-context";
+import Dialog from "../components/dialog";
+import {
+    uuid
+} from "../utils";
+
+class NCoreUIKitDialog extends NCoreContext<DialogContextType, ConfigType<DialogContextType>> {
+    constructor({
+        data = []
+    }: {
+        data?: Array<DialogDataType>
+    }) {
+        super({
+            close: () => {},
+            open: () => "",
+            data: data
+        }, {
+            key: "NCoreUIKit-DialogContext"
+        });
+    };
+
+    open = (dialogData: DialogDataType) => {
+        const currentData = this.state.data;
+
+        const dialogID = dialogData.id ? dialogData.id : uuid();
+
+        currentData.push({
+            ...dialogData,
+            id: dialogID
+        });
+
+        this.setState({
+            data: currentData
+        });
+
+        return dialogID;
+    };
+
+    close = (props?: {
+        index?: number;
+        id?: string;
+    }) => {
+        const currentData = this.state.data;
+
+        if (props && props.id) {
+            const keyIndex = currentData.findIndex((dialog) => dialog.id === props.id);
+
+            if (keyIndex !== -1) {
+                currentData.splice(keyIndex, 1);
+
+                this.setState({
+                    data: currentData
+                });
+            }
+
+            return;
+        }
+
+        if (props && props.index !== undefined) {
+            currentData.splice(props.index, 1);
+
+            this.setState({
+                data: currentData
+            });
+
+            return;
+        }
+
+        currentData.pop();
+
+        this.setState({
+            data: currentData
+        });
+    };
+
+    Render = ({
+        children
+    }: {
+        children: ReactNode;
+    }) => {
+        const {
+            data
+        } = this.useContext();
+
+        return <Fragment>
+            {children}
+            {data.map((item: DialogDataType) => {
+                return <Dialog
+                    key={`NCoreUIKit-Dialog-${item.id}`}
+                    id={item.id as string}
+                    onClosed={() => {
+                        if(item.onClosed) {
+                            item.onClosed({
+                                id: item.id as string
+                            });
+                        }
+
+                        if(item.isAutoClosed === undefined || item.isAutoClosed === true) {
+                            this.close({
+                                id: item.id
+                            });
+                        }
+                    }}
+                    {...item}
+                />;
+            })}
+        </Fragment>;
+    };
+}
+export default NCoreUIKitDialog;

+ 23 - 12
src/context/index.tsx

@@ -2,8 +2,9 @@ import {
     type ReactNode
 } from "react";
 import NCoreUIKitLocalize from "./localize";
-import NCoreUIKitModal from "./modal";
 import NCoreUIKitSnackBar from "./snackBar";
+import NCoreUIKitDialog from "./dialog";
+import NCoreUIKitModal from "./modal";
 import NCoreUIKitTheme from "./theme";
 import NCoreUIKitToast from "./toast";
 import {
@@ -17,6 +18,7 @@ class CoreContext<T extends NCoreUIKitConfig> {
     NCoreUIKitLocalize: NCoreUIKitLocalize<T>;
     NCoreUIKitSnackBar: NCoreUIKitSnackBar;
     NCoreUIKitTheme: NCoreUIKitTheme<T>;
+    NCoreUIKitDialog: NCoreUIKitDialog;
     NCoreUIKitModal: NCoreUIKitModal;
     NCoreUIKitToast: NCoreUIKitToast;
 
@@ -45,6 +47,10 @@ class CoreContext<T extends NCoreUIKitConfig> {
         this.NCoreUIKitSnackBar = new NCoreUIKitSnackBar({
             data: []
         });
+
+        this.NCoreUIKitDialog = new NCoreUIKitDialog({
+            data: []
+        });
     }
 
     Provider = ({
@@ -54,6 +60,7 @@ class CoreContext<T extends NCoreUIKitConfig> {
     }) => {
         const LocalizeContext = this.NCoreUIKitLocalize;
         const SnackBarContext = this.NCoreUIKitSnackBar;
+        const DialogContext = this.NCoreUIKitDialog;
         const ModalContext = this.NCoreUIKitModal;
         const ToastContext = this.NCoreUIKitToast;
         const ThemeContext = this.NCoreUIKitTheme;
@@ -64,17 +71,21 @@ class CoreContext<T extends NCoreUIKitConfig> {
                     <SnackBarContext.Provider>
                         <SnackBarContext.Render>
                             <Host name="toast-system">
-                                <ModalContext.Provider>
-                                    <ModalContext.Render>
-                                        <Host name="modal-system">
-                                            <ToastContext.Provider>
-                                                <ToastContext.Render>
-                                                    {children}
-                                                </ToastContext.Render>
-                                            </ToastContext.Provider>
-                                        </Host>
-                                    </ModalContext.Render>
-                                </ModalContext.Provider>
+                                <Host name="modal-system">
+                                    <ModalContext.Provider>
+                                        <ModalContext.Render>
+                                            <DialogContext.Provider>
+                                                <DialogContext.Render>
+                                                    <ToastContext.Provider>
+                                                        <ToastContext.Render>
+                                                            {children}
+                                                        </ToastContext.Render>
+                                                    </ToastContext.Provider>
+                                                </DialogContext.Render>
+                                            </DialogContext.Provider>
+                                        </ModalContext.Render>
+                                    </ModalContext.Provider>
+                                </Host>
                             </Host>
                         </SnackBarContext.Render>
                     </SnackBarContext.Provider>

+ 19 - 4
src/context/modal.tsx

@@ -10,6 +10,9 @@ import NCoreContext, {
     type ConfigType
 } from "ncore-context";
 import Modal from "../components/modal";
+import {
+    uuid
+} from "../utils";
 
 class NCoreUIKitModal extends NCoreContext<ModalContextType, ConfigType<ModalContextType>> {
     constructor({
@@ -29,7 +32,12 @@ class NCoreUIKitModal extends NCoreContext<ModalContextType, ConfigType<ModalCon
     open = (modalData: ModalDataType) => {
         const currentData = this.state.data;
 
-        currentData.push(modalData);
+        const modalID = modalData.id ? modalData.id : uuid();
+
+        currentData.push({
+            ...modalData,
+            id: modalID
+        });
 
         this.setState({
             data: currentData
@@ -87,10 +95,17 @@ class NCoreUIKitModal extends NCoreContext<ModalContextType, ConfigType<ModalCon
             {data.map((item: ModalDataType) => {
                 return <Modal
                     key={`NCoreUIKit-Modal-${item.id}`}
-                    onOverlayPress={item.onOverlayPress ? item.onOverlayPress : () => {
-                        this.close({
-                            id: item.id
+                    id={item.id as string}
+                    onClosed={() => {
+                        if(item.onClosed) item.onClosed({
+                            id: item.id as string
                         });
+
+                        if(item.isAutoClose === undefined || item.isAutoClose === true) {
+                            this.close({
+                                id: item.id
+                            });
+                        }
                     }}
                     {...item}
                 />;

+ 3 - 0
src/core/hooks.ts

@@ -8,6 +8,7 @@ import type {
 } from "../types";
 import type NCoreUIKitLocalizeClass from "../context/localize";
 import type NCoreUIKitSnackBarClass from "../context/snackBar";
+import type NCoreUIKitDialogClass from "../context/dialog";
 import type NCoreUIKitModalClass from "../context/modal";
 import type NCoreUIKitToastClass from "../context/toast";
 import type NCoreUIKitThemeClass from "../context/theme";
@@ -15,12 +16,14 @@ import type NCoreUIKitThemeClass from "../context/theme";
 export let NCoreUIKitLocalize: NCoreUIKitLocalizeClass<LocalizeType>;
 export let NCoreUIKitTheme: NCoreUIKitThemeClass<ThemesType>;
 export let NCoreUIKitSnackBar: NCoreUIKitSnackBarClass;
+export let NCoreUIKitDialog: NCoreUIKitDialogClass;
 export let NCoreUIKitModal: NCoreUIKitModalClass;
 export let NCoreUIKitToast: NCoreUIKitToastClass;
 
 export const initializeInstances = (NCoreUIKit: NCoreUIKitBase<NCoreUIKitConfig>) => {
     NCoreUIKitLocalize = NCoreUIKit.NCoreUIKitContext.NCoreUIKitLocalize;
     NCoreUIKitSnackBar = NCoreUIKit.NCoreUIKitContext.NCoreUIKitSnackBar;
+    NCoreUIKitDialog = NCoreUIKit.NCoreUIKitContext.NCoreUIKitDialog;
     NCoreUIKitTheme = NCoreUIKit.NCoreUIKitContext.NCoreUIKitTheme;
     NCoreUIKitModal = NCoreUIKit.NCoreUIKitContext.NCoreUIKitModal;
     NCoreUIKitToast = NCoreUIKit.NCoreUIKitContext.NCoreUIKitToast;

+ 1 - 1
src/core/index.tsx

@@ -32,7 +32,7 @@ export class NCoreUIKitBase<T extends NCoreUIKitConfig> {
         return <SafeAreaProvider>
             <NCoreUIKitContext.Provider>
                 {children}
-            </NCoreUIKitContext.Provider>;
+            </NCoreUIKitContext.Provider>
         </SafeAreaProvider>;
     };
 };

+ 3 - 2
src/index.tsx

@@ -6,6 +6,7 @@ export {
 export {
     NCoreUIKitLocalize,
     NCoreUIKitSnackBar,
+    NCoreUIKitDialog,
     NCoreUIKitModal,
     NCoreUIKitTheme,
     NCoreUIKitToast
@@ -19,7 +20,7 @@ export {
     SelectBox,
     CheckBox,
     Loading,
-    // Dialog,
+    Dialog,
     Button,
     Modal,
     Toast,
@@ -28,7 +29,7 @@ export {
 
 export type {
     IBottomSheetRef,
-    // IDialogRef,
+    IDialogRef,
     IModalRef
 } from "./components";
 

+ 23 - 0
src/types/dialog.ts

@@ -0,0 +1,23 @@
+import type IDialogProps from "../components/dialog/type";
+
+export type DialogType = {
+    data?: Array<DialogDataType>;
+};
+
+export type DialogDataType = Omit<IDialogProps, "id"> & {
+    isAutoClosed?: boolean;
+    id?: string;
+};
+
+export type DialogContextType = {
+    open: (dialogData: DialogDataType) => string;
+    close: (props?: {
+        index?: number;
+        id?: string;
+    }) => void;
+    data: Array<DialogDataType>;
+};
+
+export type DialogStateContextType = {
+    data: Array<DialogDataType>;
+};

+ 3 - 2
src/types/modal.ts

@@ -6,8 +6,9 @@ export type ModalType = {
     data?: Array<ModalDataType>;
 };
 
-export type ModalDataType = ModalInternalProps & {
-    id: string;
+export type ModalDataType = Omit<ModalInternalProps, "id"> & {
+    isAutoClose?: boolean;
+    id?: string;
 };
 
 export type ModalContextType = {