Ver código fonte

Merge branch 'release/1.0.0-pre-alpha.20'

lfabl 1 mês atrás
pai
commit
8d2920f36b

+ 27 - 6
example/src/pages/home/index.tsx

@@ -230,9 +230,11 @@ const Home = () => {
     ] = useState(false);
 
     return <PageContainer
-        isScrollable={true}
         scrollViewStyle={stylesheet.container}
+        isScrollable={true}
         scrollViewProps={{
+            showsHorizontalScrollIndicator: false,
+            showsVerticalScrollIndicator: false,
             contentContainerStyle: {
                 backgroundColor: colors.content.container.default,
                 ...stylesheet.contentContainer
@@ -365,15 +367,35 @@ const Home = () => {
         <MarkdownViewer
             content={`
 # Merhaba Furkan!.
+
 Nabersin ?
-## Deneme title 2
+
+<right>
+<link href="https://www.nibgat.com">
+## Deneme **title** 2
+</link>
+
+\`\`\`
+safasfasfasf
+\`\`\`
+</right>
 iyisindir **umarım.**
 
-* Merhaba madde1.
+\`\`\`
+Ahmet buraya gel.
+
+
+Geldin mi ?
+\`\`\`
+
+<<
+Burası alıntı metnidir.
+>>
+
+* Merhaba __madd__e1.
 - Deneme madde2.
 
-[Alt text için deneme](https://static.vecteezy.com/system/resources/thumbnails/060/843/811/small/close-up-of-raindrops-on-leaves-hd-background-luxury-hd-wallpaper-image-trendy-background-illustration-free-photo.jpg)
-            `}
+[Alt text için deneme](https://static.vecteezy.com/system/resources/thumbnails/060/843/811/small/close-up-of-raindrops-on-leaves-hd-background-luxury-hd-wallpaper-image-trendy-background-illustration-free-photo.jpg)<100%x200>--cover--`}
         />
         <NotificationIndicator
             type="danger"
@@ -479,7 +501,6 @@ iyisindir **umarım.**
                 navigation.navigate("TestSubPage");
             }}
             title="Open Test Sub Page"
-            isDisabled={true}
             variant="filled"
             spreadBehaviour="stretch"
         />

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
     "name": "ncore-ui-kit-mobile",
-    "version": "1.0.0-pre-alpha.19",
+    "version": "1.0.0-pre-alpha.20",
     "description": "NİBGAT® | NCore - UI Kit for React-Native Mobile Apps.",
     "main": "./lib/module/index.js",
     "types": "./lib/typescript/src/index.d.ts",

+ 5 - 6
src/components/dialog/index.tsx

@@ -197,6 +197,8 @@ const Dialog: RefForwardingComponent<IDialogRef, IDialogProps> = ({
         }
 
         return <Button
+            type="neutral"
+            {...secondaryButtonProps}
             onPress={() => {
                 if(secondaryButtonProps?.onPress) secondaryButtonProps.onPress({
                     closeAnimation: (onClosed?: (props: {
@@ -208,16 +210,16 @@ const Dialog: RefForwardingComponent<IDialogRef, IDialogProps> = ({
             }}
             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}
+            type="primary"
+            size="medium"
+            {...primaryButtonProps}
             spreadBehaviour={contentJustify === "centered" ? "stretch" : "free"}
             onPress={() => {
                 if(primaryButtonProps?.onPress) primaryButtonProps?.onPress({
@@ -229,15 +231,12 @@ const Dialog: RefForwardingComponent<IDialogRef, IDialogProps> = ({
                 });
             }}
             title={primaryButtonProps?.title || localize("ok")}
-            isLoading={primaryButtonProps?.isLoading}
             style={[
                 primaryButtonStyle,
                 stylesheet.primaryButton,
                 primaryButtonDynamicStyle
             ]}
             variant="filled"
-            type="primary"
-            size="medium"
         />;
     };
 

+ 1 - 7
src/components/dialog/type.ts

@@ -12,9 +12,6 @@ import type IButtonProps from "../button/type";
 import type {
     INCoreUIKitIconProps
 } from "../../types";
-import type {
-    ButtonDisplayBehaviourWhileLoading
-} from "../button/type";
 
 export type IDialogRef = {
     closeAnimation: (onClosed: (props: {
@@ -35,15 +32,12 @@ export type DialogVariant = "yes-no" | "ok" | "info";
 
 export type DialogContentJustify = "centered" | "language-based";
 
-type DialogButton = {
-    displayBehaviourWhileLoading?: ButtonDisplayBehaviourWhileLoading;
+type DialogButton = Omit<IButtonProps, "onPress"> & {
     onPress?: (props: {
         closeAnimation: (onClosed?: (props: {
             id: string;
         }) => void) => void;
     }) => void;
-    isLoading?: boolean;
-    title?: string;
 };
 
 interface IDialogProps {

+ 24 - 0
src/components/index.ts

@@ -93,3 +93,27 @@ export {
 export {
     default as MarkdownViewer
 } from "./markdownViewer";
+
+export type {
+    EnterMarkdownTypes,
+    CodeMarkdownTypes,
+    TextMarkdownTypes,
+    BlockquoteTypes,
+    MarkdownObject,
+    MarkdownKeys,
+    ListTypes
+} from "./markdownViewer/type";
+
+export {
+    blockquoteMarkdown,
+    centerMarkdown,
+    enterMarkdown,
+    imageMarkdown,
+    parseMarkdown,
+    rightMarkdown,
+    textMarkdown,
+    codeMarkdown,
+    leftMarkdown,
+    linkMarkdown,
+    listMarkdown
+} from "./markdownViewer/util";

+ 19 - 0
src/components/markdownViewer/constant.ts

@@ -0,0 +1,19 @@
+import type {
+    MarkdownKeys
+} from "./type";
+
+export const DEFAULT_VARIANTS: Record<MarkdownKeys, keyof NCoreUIKit.Typography> = {
+    "# ": "displayLargeSize",
+    "## ": "displayMediumSize",
+    "### ": "displaySmallSize",
+    "#### ": "headlineLargeSize",
+    "##### ": "headlineMediumSize",
+    "###### ": "headlineSmallSize",
+    "<br/>": "bodyMediumSize",
+    "```": "labelMediumSize",
+    "<p>": "bodyMediumSize",
+    "<<": "titleMediumSize",
+    "* ": "bodyLargeSize",
+    "- ": "bodyLargeSize",
+    "": "bodyLargeSize"
+};

+ 455 - 136
src/components/markdownViewer/index.tsx

@@ -1,178 +1,497 @@
-import type {
-    FC
+import {
+    useEffect,
+    useState,
+    type FC
 } from "react";
 import {
     Text as NativeText,
+    TouchableOpacity,
+    Linking,
     Image,
     View
 } from "react-native";
 import type IMarkdownViewerProps from "./type";
-import stylesheet from "./stylesheet";
+import type {
+    MarkdownObject,
+    TextMarkdownTypes
+} from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    parseMarkdown
+} from "./util";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type ITextProps from "../text/type";
+import {
+    Quote as QuoteIcon
+} from "lucide-react-native";
 import Text from "../text";
 
-type MarkdownKeys = "# " |
-    "## " |
-    "### " |
-    "#### " |
-    "##### " |
-    "###### " |
-    "* " |
-    "- " |
-    "p";
-
-const DEFAULT_VARIANTS: Record<MarkdownKeys, keyof NCoreUIKit.Typography> = {
-    "# ": "displayLargeSize",
-    "## ": "displayMediumSize",
-    "### ": "displaySmallSize",
-    "#### ": "headlineLargeSize",
-    "##### ": "headlineMediumSize",
-    "###### ": "headlineSmallSize",
-    "* ": "bodyLargeSize",
-    "- ": "bodyLargeSize",
-    "p": "bodyMediumSize"
-};
+const MarkdownViewer: FC<IMarkdownViewerProps> = ({
+    blockquoteIconSize = "titleLargeSize",
+    codeTextColor = "emphasized",
+    blockquoteIconColor = "mid",
+    blockquoteTextColor = "mid",
+    blockquoteContainerProps,
+    centerContainerProps,
+    blockquoteTextStyle,
+    rightContainerProps,
+    blockquoteIconStyle,
+    centerMarkdownProps,
+    leftContainerProps,
+    rightMarkdownProps,
+    listContainerProps,
+    linkContainerProps,
+    codeContainerProps,
+    leftMarkdownProps,
+    linkMarkdownProps,
+    customTextStyles,
+    customTextProps,
+    italicTextProps,
+    enterTextProps,
+    boldTextProps,
+    listIconProps,
+    codeTextProps,
+    listTextProps,
+    italicStyle,
+    imageProps,
+    boldStyle,
+    content,
+    style,
+    ...props
+}) => {
+    const {
+        typography,
+        radiuses,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext();
 
-const parseMarkdown = (rawText: string): Array<{
-    variant?: keyof NCoreUIKit.Typography;
-    nativeType: MarkdownKeys | "image";
-    type: "text" | "bullet" | "image";
-    content: string;
-    url?: string;
-    key: number;
-}> => {
-    const lines = rawText.split("\n");
-
-    return lines.map((line, index) => {
-        if (line.startsWith("# ")) return {
-            content: line.replace("# ", ""),
-            variant: DEFAULT_VARIANTS["# "],
-            nativeType: "# ",
-            type: "text",
-            key: index
-        };
+    const [
+        containerWidth,
+        setContainerWidth
+    ] = useState<null | number>(null);
 
-        if (line.startsWith("## ")) return {
-            content: line.replace("## ", ""),
-            variant: DEFAULT_VARIANTS["## "],
-            nativeType: "## ",
-            type: "text",
-            key: index
-        };
+    const {
+        blockquoteContainer: blockquoteContainerDynamicStyle,
+        blockquoteIcon: blockquoteIconDynamicStyle,
+        codeContainer: codeContainerDynamicStyle
+    } = useStyles({
+        radiuses,
+        colors,
+        spaces
+    });
+
+    const nodes = parseMarkdown(content);
 
-        const isStartWithImage = line.startsWith("[");
+    const renderBold = ({
+        part,
+        i
+    }: {
+        part: string;
+        i: number;
+    }) => {
+        return <NativeText
+            {...boldTextProps}
+            key={`bold-${i}`}
+            style={{
+                ...stylesheet.bold,
+                ...boldStyle
+            }}
+        >
+            {part.replace(/\*\*/g, "")}
+        </NativeText>;
+    };
 
-        if(isStartWithImage || line.startsWith("[")) {
-            const contentMatch = line.match(/\[(.*?)\]/);
+    const renderItalic = ({
+        part,
+        i
+    }: {
+        part: string;
+        i: number;
+    }) => {
+        return <NativeText
+            {...italicTextProps}
+            key={`italic-${i}`}
+            style={{
+                ...stylesheet.italic,
+                ...italicStyle
+            }}
+        >
+            {part.replace(/__/g, "")}
+        </NativeText>;
+    };
 
-            const imageContent = (contentMatch && contentMatch[1]) ? contentMatch[1] : "";
+    const renderInlineStyles = (_content: string) => {
+        const parts = _content.split(/(\*\*.*?\*\*|__.*?__)/g);
 
-            const tmpLine = line.replace(/\[.*?\]/g, "");
+        return parts.map((part, i) => {
+            if (part.startsWith("**") && part.endsWith("**")) return renderBold({
+                part,
+                i
+            });
+
+            if (part.startsWith("__") && part.endsWith("__")) return renderItalic({
+                part,
+                i
+            });
+
+            return part;
+        });
+    };
 
-            const urlMatch = tmpLine.match(/\((.*?)\)/);
+    const renderText = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        let textProps: ITextProps = {
+            variant: node.variant
+        };
 
-            const imageURL = (urlMatch && urlMatch[1]) ? urlMatch[1] : "";
+        if(customTextStyles && customTextStyles[node.nativeType as TextMarkdownTypes]) {
+            const currentTextStyle = customTextStyles[node.nativeType as TextMarkdownTypes];
 
-            return {
-                content: imageContent,
-                nativeType: "image",
-                type: "image",
-                url: imageURL,
-                key: index
-            };
+            textProps.style = currentTextStyle;
         }
 
-        const isStarList = line.startsWith("* ");
+        if(customTextProps && customTextProps[node.nativeType as TextMarkdownTypes]) {
+            const currentTextProps = customTextProps[node.nativeType as TextMarkdownTypes];
 
-        if (isStarList || line.startsWith("- ")) {
-            return {
-                variant: DEFAULT_VARIANTS[isStarList ? "* " : "- "],
-                nativeType: isStarList ? "* " : "- ",
-                content: line.substring(2),
-                type: "bullet",
-                key: index
+            textProps = {
+                ...textProps,
+                ...currentTextProps
             };
         }
 
-        return {
-            variant: DEFAULT_VARIANTS["p"],
-            nativeType: "p",
-            content: line,
-            type: "text",
-            key: index
-        };
-    });
-};
+        return <Text
+            {...textProps}
+            key={node.key}
+        >{renderInlineStyles(node.content)}</Text>;
+    };
 
-const MarkdownViewer: FC<IMarkdownViewerProps> = ({
-    content
-}) => {
-    const nodes = parseMarkdown(content);
+    const renderEnter = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...enterTextProps}
+            key={node.key}
+            style={[
+                stylesheet.enter,
+                {
+                    height: typography[node.variant as keyof NCoreUIKit.Typography].fontSize
+                },
+                enterTextProps?.style
+            ]}
+        />;
+    };
 
-    const renderInlineStyles = (_content: string) => {
-        const parts = _content.split(/(\*\*.*?\*\*)/g);
+    const renderBlockquote = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...blockquoteContainerProps}
+            key={node.key}
+            style={[
+                blockquoteContainerDynamicStyle,
+                blockquoteContainerProps?.style
+            ]}
+        >
+            <QuoteIcon
+                color={colors.content.icon[blockquoteIconColor]}
+                size={typography[blockquoteIconSize].fontSize}
+                style={{
+                    ...blockquoteIconDynamicStyle,
+                    ...blockquoteIconStyle
+                }}
+            />
+            <Text
+                color={blockquoteTextColor}
+                variant={node.variant}
+                style={{
+                    ...stylesheet.blockquoteText,
+                    ...blockquoteTextStyle
+                }}
+            >
+                {renderInlineStyles(node.content)}
+            </Text>
+        </View>;
+    };
 
-        return parts.map((part, i) => {
-            if (part.startsWith("**") && part.endsWith("**")) {
-                return (
-                    <NativeText
-                        key={i}
-                        style={{
-                            fontWeight: "bold"
-                        }}
-                    >
-                        {part.replace(/\*\*/g, "")}
-                    </NativeText>
-                );
+    const renderImage = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        const [
+            imgSize,
+            setImgSize
+        ] = useState<{
+            height: number | string;
+            width: number | string;
+            isSetted: boolean;
+        }>({
+            isSetted: false,
+            width: "100%",
+            height: 0
+        });
+
+        useEffect(() => {
+            if(!imgSize.isSetted && node.size) {
+                setImgSize({
+                    height: node.size[1] as number | string,
+                    width: node.size[0] as number | string,
+                    isSetted: true
+                });
             }
+        }, [imgSize]);
 
-            return part;
-        });
+        const currentWidth = typeof imgSize.width === "number" || imgSize.width.indexOf("%") === -1 || !containerWidth
+            ? imgSize.width
+            : (containerWidth * Number(imgSize.width.split("%")[0])) / 100;
+
+        const currentHeight = typeof imgSize.height === "number" || imgSize.height.indexOf("%") === -1 || !containerWidth
+            ? imgSize.height
+            : (containerWidth * Number(imgSize.height.split("%")[0])) / 100;
+
+        return <Image
+            {...imageProps}
+            alt={node.content}
+            key={node.key}
+            source={{
+                uri: node.url
+            }}
+            resizeMode={node.resizeMode ? node.resizeMode : "contain"}
+            onLoad={(event) => {
+                if(!node.size) {
+                    const {
+                        height,
+                        width
+                    } = event.nativeEvent.source;
+
+                    setImgSize({
+                        isSetted: true,
+                        height,
+                        width
+                    });
+                }
+            }}
+            style={{
+                ...stylesheet.image,
+                height: currentHeight,
+                width: currentWidth,
+                ...imageProps?.style
+            }}
+        />;
+    };
+
+    const renderList = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...listContainerProps}
+            key={node.key}
+            style={[
+                stylesheet.listContainer,
+                listContainerProps?.style
+            ]}
+        >
+            <Text
+                {...listIconProps}
+                variant={node.variant}
+            >
+                •{" "}
+            </Text>
+            <Text
+                {...listTextProps}
+                variant={node.variant}
+            >
+                {renderInlineStyles(node.content)}
+            </Text>
+        </View>;
+    };
+
+    const renderCode = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...codeContainerProps}
+            key={node.key}
+            style={[
+                codeContainerDynamicStyle,
+                codeContainerProps?.style
+            ]}
+        >
+            <Text
+                {...codeTextProps}
+                variant={node.variant}
+                color={codeTextColor}
+            >
+                {node.content}
+            </Text>
+        </View>;
+    };
+
+    const renderLink = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <TouchableOpacity
+            {...linkContainerProps}
+            key={node.key}
+            onPress={() => {
+                Linking.canOpenURL(node.url as string);
+            }}
+        >
+            <MarkdownViewer
+                {...linkMarkdownProps}
+                content={node.content}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderCenter = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...centerContainerProps}
+            key={node.key}
+            style={[
+                stylesheet.centerContainer,
+                centerContainerProps?.style
+            ]}
+        >
+            <MarkdownViewer
+                {...centerMarkdownProps}
+                content={node.content}
+            />
+        </View>;
+    };
+
+    const renderLeft = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...leftContainerProps}
+            key={node.key}
+            style={[
+                stylesheet.leftContainer,
+                leftContainerProps?.style
+            ]}
+        >
+            <MarkdownViewer
+                {...leftMarkdownProps}
+                content={node.content}
+            />
+        </View>;
+    };
+
+    const renderRight = ({
+        node
+    }: {
+        node: MarkdownObject
+    }) => {
+        return <View
+            {...rightContainerProps}
+            key={node.key}
+            style={[
+                stylesheet.rightContainer,
+                rightContainerProps?.style
+            ]}
+        >
+            <MarkdownViewer
+                {...rightMarkdownProps}
+                content={node.content}
+            />
+        </View>;
     };
 
     return <View
+        {...props}
+        onLayout={({
+            nativeEvent
+        }) => {
+            setContainerWidth(nativeEvent.layout.width);
+        }}
         style={[
-            stylesheet.container
+            stylesheet.container,
+            style
         ]}
     >
         {nodes.map((node) => {
-            if (!node.content.trim() && node.nativeType === "p") return null;
+            if (!node.content.trim() && node.nativeType === "<p>") return null;
 
             switch (node.type) {
-                case "text":
-                    return <Text
-                        variant={node.variant}
-                        key={node.key}
-                    >{renderInlineStyles(node.content)}</Text>;
-                case "image":
-                    return <Image
-                        alt={node.content}
-                        key={node.key}
-                        source={{
-                            uri: node.url
-                        }}
-                        resizeMode="contain"
-                        style={{
-                            width: 300,
-                            height: 300
-                        }}
-                    />;
-                case "bullet":
-                    return (
-                        <View
-                            key={node.key}
-                            style={{
-                                flexDirection: "row",
-                                alignItems: "center"
-                            }}
-                        >
-                            <Text variant={node.variant}>• </Text><Text variant={node.variant}>{renderInlineStyles(node.content)}</Text>
-                        </View>
-                    );
+                case undefined:
+                    return null;
+                case "text": {
+                    return renderText({
+                        node
+                    });
+                }
+                case "space":
+                    return renderEnter({
+                        node
+                    });
+                case "blockquote": {
+                    return renderBlockquote({
+                        node
+                    });
+                }
+                case "right": {
+                    return renderRight({
+                        node
+                    });
+                }
+                case "left": {
+                    return renderLeft({
+                        node
+                    });
+                }
+                case "center": {
+                    return renderCenter({
+                        node
+                    });
+                }
+                case "link": {
+                    return renderLink({
+                        node
+                    });
+                }
+                case "code": {
+                    return renderCode({
+                        node
+                    });
+                }
+                case "image": {
+                    return renderImage({
+                        node
+                    });
+                }
+                case "list":
+                    return renderList({
+                        node
+                    });
                 default:
-                    return <Text
-                        variant={node.variant}
-                        key={node.key}
-                    >{renderInlineStyles(node.content)}</Text>;
+                    return renderText({
+                        node
+                    });
             }
         })}
     </View>;

+ 63 - 1
src/components/markdownViewer/stylesheet.ts

@@ -1,10 +1,72 @@
 import {
     StyleSheet
 } from "react-native";
+import type {
+    MarkdownDynamicStyles
+} from "./type";
 
 const stylesheet = StyleSheet.create({
     container: {
-
+    },
+    bold: {
+        fontWeight: "bold"
+    },
+    italic: {
+        fontStyle: "italic"
+    },
+    enter: {
+        flexShrink: 1,
+        width: "100%"
+    },
+    blockquoteText: {
+        textAlignVertical: "bottom",
+        verticalAlign: "bottom"
+    },
+    image: {
+        maxWidth: "100%"
+    },
+    listContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    centerContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center"
+    },
+    leftContainer: {
+        justifyContent: "flex-start",
+        alignItems: "flex-start",
+        alignSelf: "flex-start"
+    },
+    rightContainer: {
+        justifyContent: "flex-end",
+        alignItems: "flex-end",
+        alignSelf: "flex-end"
     }
 });
+
+export const useStyles = ({
+    radiuses,
+    colors,
+    spaces
+}: MarkdownDynamicStyles) => {
+    const styles = {
+        blockquoteContainer: {
+            backgroundColor: colors.content.container.mid,
+            borderRadius: radiuses.form,
+            padding: spaces.spacingMd
+        },
+        blockquoteIcon: {
+            marginBottom: spaces.spacingXs
+        },
+        codeContainer: {
+            backgroundColor: colors.content.container.mid,
+            borderRadius: radiuses.form,
+            padding: spaces.spacingMd
+        }
+    };
+
+    return styles;
+};
 export default stylesheet;

+ 82 - 1
src/components/markdownViewer/type.ts

@@ -1,4 +1,85 @@
-interface IMarkdownViewerProps {
+import type {
+    TouchableOpacityProps,
+    ImageProps,
+    StyleProp,
+    TextProps,
+    TextStyle,
+    ViewProps,
+    ViewStyle
+} from "react-native";
+import type ITextProps from "../text/type";
+
+export type MarkdownDynamicStyles = {
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+};
+
+export type MarkdownObject = {
+    type?: "text" | "list" | "image" | "space" | "code" | "blockquote" | "center" | "right" | "left" | "link";
+    resizeMode?: "cover" | "contain" | "center" | "none" | "repeat" | "stretch";
+    nativeType: MarkdownKeys | "image" | "center" | "right" | "left" | "link";
+    variant?: keyof NCoreUIKit.Typography;
+    size?: Array<number | string>;
+    content: string;
+    url?: string;
+    key: number;
+};
+
+export type TextMarkdownTypes = "# " |
+    "## " |
+    "### " |
+    "#### " |
+    "##### " |
+    "###### " |
+    "<p>";
+
+export type EnterMarkdownTypes = "<br/>" | "";
+
+export type CodeMarkdownTypes = "```";
+
+export type BlockquoteTypes = "<<";
+
+export type ListTypes = "* " | "- ";
+
+export type MarkdownKeys = TextMarkdownTypes | EnterMarkdownTypes | CodeMarkdownTypes | BlockquoteTypes | ListTypes;
+
+interface IMarkdownViewerProps extends ViewProps {
+    blockquoteIconStyle?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    blockquoteTextColor?: keyof NCoreUIKit.TextContentColors;
+    blockquoteIconColor?: keyof NCoreUIKit.IconContentColors;
+    style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    codeTextColor?: keyof NCoreUIKit.TextContentColors;
+    blockquoteIconSize?: keyof NCoreUIKit.Typography;
+    linkContainerProps?: TouchableOpacityProps;
+    centerMarkdownProps?: IMarkdownViewerProps;
+    blockquoteTextStyle?: ITextProps["style"];
+    rightMarkdownProps?: IMarkdownViewerProps;
+    leftMarkdownProps?: IMarkdownViewerProps;
+    linkMarkdownProps?: IMarkdownViewerProps;
+    blockquoteContainerProps?: ViewProps;
+    centerContainerProps?: ViewProps;
+    rightContainerProps?: ViewProps;
+    leftContainerProps?: ViewProps;
+    codeContainerProps?: ViewProps;
+    listContainerProps?: ViewProps;
+    italicTextProps?: TextProps;
+    enterTextProps?: ITextProps;
+    codeTextProps?: ITextProps;
+    listTextProps?: ITextProps;
+    listIconProps?: ITextProps;
+    boldTextProps?: TextProps;
+    imageProps?: ImageProps;
+    customTextProps?: Record<
+        TextMarkdownTypes,
+        ITextProps
+    >;
+    customTextStyles?: Record<
+        TextMarkdownTypes,
+        ITextProps["style"]
+    >;
+    italicStyle?: TextStyle;
+    boldStyle?: TextStyle;
     content: string;
 };
 export type {

+ 453 - 0
src/components/markdownViewer/util.ts

@@ -0,0 +1,453 @@
+import type {
+    MarkdownObject
+} from "./type";
+import {
+    DEFAULT_VARIANTS
+} from "./constant";
+
+export const imageMarkdown = ({
+    index,
+    line
+}: {
+    index: number;
+    line: string;
+}): MarkdownObject => {
+    const contentMatch = line.match(/\[(.*?)\]/);
+    const imageContent = (contentMatch && contentMatch[1]) ? contentMatch[1] : "";
+    const tmpLine = line.replace(/\[.*?\]/g, "");
+
+    const urlMatch = tmpLine.match(/\((.*?)\)/);
+    const imageURL = (urlMatch && urlMatch[1]) ? urlMatch[1] : "";
+    const tmpLine2 = tmpLine.replace(/\((.*?)\)/, "");
+
+    const imageSizeMatch = tmpLine2.match(/<(.*?)>/);
+    const imageSize = (imageSizeMatch && imageSizeMatch[1]) ? imageSizeMatch[1].split("x").map(e => {
+        return e.indexOf("%") !== -1 || e.indexOf("auto") !== -1 ? e : Number(e);
+    }) : undefined;
+    const tmpLine3 = tmpLine2.replace(/<(.*?)>/, "");
+
+    const resizeModeMatch = tmpLine3.match(/--(.*?)--/);
+    const resizeMode = (resizeModeMatch && resizeModeMatch[1]) ? resizeModeMatch[1] as "cover" | "contain" | "center" | "none" | "repeat" | "stretch" : undefined;
+
+    return {
+        resizeMode: resizeMode,
+        content: imageContent,
+        nativeType: "image",
+        size: imageSize,
+        type: "image",
+        url: imageURL,
+        key: index
+    };
+};
+
+export const textMarkdown = ({
+    index,
+    type,
+    line
+}: {
+    type: "# " | "## " | "### " | "#### " | "##### " | "###### " | "<p>";
+    index: number;
+    line: string;
+}): MarkdownObject => {
+    return {
+        content: line.replace(type, ""),
+        variant: DEFAULT_VARIANTS[type],
+        nativeType: type,
+        type: "text",
+        key: index
+    };
+};
+
+export const enterMarkdown = ({
+    index,
+    line
+}: {
+    index: number;
+    line: string;
+}): MarkdownObject => {
+    const _line = line.replace("<br/>", "");
+
+    return {
+        variant: DEFAULT_VARIANTS["<br/>"],
+        nativeType: "<br/>",
+        content: _line,
+        type: "space",
+        key: index
+    };
+};
+
+export const codeMarkdown = ({
+    skippedIndices,
+    index,
+    lines
+}: {
+    skippedIndices: number[];
+    lines: string[];
+    index: number;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith("```")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const codeContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            variant: DEFAULT_VARIANTS["```"],
+            content: codeContent,
+            nativeType: "```",
+            type: "code",
+            key: index
+        };
+    }
+};
+
+export const centerMarkdown = ({
+    skippedIndices,
+    index,
+    lines
+}: {
+    skippedIndices: number[];
+    lines: string[];
+    index: number;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith("</center>")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const centerContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            content: centerContent,
+            nativeType: "center",
+            type: "center",
+            key: index
+        };
+    }
+};
+
+export const leftMarkdown = ({
+    skippedIndices,
+    index,
+    lines
+}: {
+    skippedIndices: number[];
+    lines: string[];
+    index: number;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith("</left>")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const leftContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            content: leftContent,
+            nativeType: "left",
+            type: "left",
+            key: index
+        };
+    }
+};
+
+export const rightMarkdown = ({
+    skippedIndices,
+    index,
+    lines
+}: {
+    skippedIndices: number[];
+    lines: string[];
+    index: number;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith("</right>")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const rightContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            content: rightContent,
+            nativeType: "right",
+            type: "right",
+            key: index
+        };
+    }
+};
+
+export const linkMarkdown = ({
+    skippedIndices,
+    linkStartRegex,
+    index,
+    lines,
+    line
+}: {
+    skippedIndices: number[];
+    linkStartRegex: RegExp;
+    lines: string[];
+    index: number;
+    line: string;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith("</link>")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        const match = line.match(linkStartRegex);
+        const href = match ? match[1] : "";
+
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const linkContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            content: linkContent,
+            nativeType: "link",
+            type: "link",
+            key: index,
+            url: href
+        };
+    }
+};
+
+export const blockquoteMarkdown = ({
+    skippedIndices,
+    index,
+    lines
+}: {
+    skippedIndices: number[];
+    lines: string[];
+    index: number;
+}): MarkdownObject | void => {
+    let endIndex = -1;
+
+    for (let j = index + 1; j < lines.length; j++) {
+        if (lines[j]?.startsWith(">>")) {
+            endIndex = j;
+            break;
+        }
+    }
+
+    if (endIndex !== -1) {
+        for (let k = index + 1; k <= endIndex; k++) {
+            skippedIndices.push(k);
+        }
+
+        const quoteContent = lines.slice(index + 1, endIndex).join("\n");
+
+        return {
+            variant: DEFAULT_VARIANTS["<<"],
+            content: quoteContent,
+            type: "blockquote",
+            nativeType: "<<",
+            key: index
+        };
+    }
+};
+
+export const listMarkdown = ({
+    isStarList,
+    index,
+    line
+}: {
+    isStarList: boolean;
+    index: number;
+    line: string;
+}): MarkdownObject => {
+    return {
+        variant: DEFAULT_VARIANTS[isStarList ? "* " : "- "],
+        nativeType: isStarList ? "* " : "- ",
+        content: line.substring(2),
+        type: "list",
+        key: index
+    };
+};
+
+export const parseMarkdown = (rawText: string): Array<MarkdownObject> => {
+    const lines = rawText.split("\n");
+    const skippedIndices: number[] = [];
+
+    return lines.map((line, index) => {
+        if (skippedIndices.includes(index)) {
+            return {
+                type: undefined,
+                nativeType: "",
+                content: "",
+                key: index
+            };
+        }
+
+        if (line.startsWith("# ")) return textMarkdown({
+            type: "# ",
+            index,
+            line
+        });
+
+        if (line.startsWith("## ")) return textMarkdown({
+            type: "## ",
+            index,
+            line
+        });
+
+        if (line.startsWith("### ")) return textMarkdown({
+            type: "### ",
+            index,
+            line
+        });
+
+        if (line.startsWith("#### ")) return textMarkdown({
+            type: "#### ",
+            index,
+            line
+        });
+
+        if (line.startsWith("##### ")) return textMarkdown({
+            type: "##### ",
+            index,
+            line
+        });
+
+        if (line.startsWith("###### ")) return textMarkdown({
+            type: "###### ",
+            index,
+            line
+        });
+
+        if (line.startsWith("<br/>") || line === "") return enterMarkdown({
+            index,
+            line
+        });
+
+        if (line.startsWith("```")) {
+            const returnedData = codeMarkdown({
+                skippedIndices,
+                index,
+                lines
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        if (line.startsWith("<center>")) {
+            const returnedData = centerMarkdown({
+                skippedIndices,
+                index,
+                lines
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        if (line.startsWith("<right>")) {
+            const returnedData = rightMarkdown({
+                skippedIndices,
+                index,
+                lines
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        if (line.startsWith("<left>")) {
+            const returnedData = leftMarkdown({
+                skippedIndices,
+                index,
+                lines
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        const linkStartRegex = /^<link\s+href=["'](.*?)["']>/;
+
+        if (linkStartRegex.test(line)) {
+            const returnedData = linkMarkdown({
+                skippedIndices,
+                linkStartRegex,
+                index,
+                lines,
+                line
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        if (line.startsWith("<<")) {
+            const returnedData = blockquoteMarkdown({
+                skippedIndices,
+                index,
+                lines
+            });
+
+            if(returnedData) return returnedData;
+        }
+
+        const isStartWithImage = line.startsWith("[");
+
+        if(isStartWithImage || line.startsWith("[")) return imageMarkdown({
+            index,
+            line
+        });
+
+        const isStarList = line.startsWith("* ");
+
+        if (isStarList || line.startsWith("- ")) return listMarkdown({
+            isStarList,
+            index,
+            line
+        });
+
+        return {
+            variant: DEFAULT_VARIANTS["<p>"],
+            nativeType: "<p>",
+            content: line,
+            type: "text",
+            key: index
+        };
+    });
+};

+ 21 - 9
src/components/selectBox/index.tsx

@@ -107,8 +107,10 @@ function SelectBox<T>({
     onChange,
     hintText,
     onSearch,
+    onCancel,
     style,
-    title
+    title,
+    onOk
 }: ISelectBoxProps<T>, ref: Ref<ISelectBoxRef<T>>) {
     const {
         inlineSpaces,
@@ -274,6 +276,16 @@ function SelectBox<T>({
         setData(initData);
     }, []);
 
+    useEffect(() => {
+        if(onChange) {
+            onChange(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
+        }
+    }, [
+        tempSelectedItems,
+        selectedItems,
+        data
+    ]);
+
     const titleProps: ITextProps = {
         color: currentType.titleColor,
         variant: "bodyLargeSize"
@@ -305,6 +317,10 @@ function SelectBox<T>({
             bottomSheetRef.current?.close(() => {
                 blur();
             });
+
+            if(onCancel) {
+                onCancel(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
+            }
         }
     };
 
@@ -315,6 +331,10 @@ function SelectBox<T>({
             bottomSheetRef.current?.close(() => {
                 blur();
             });
+
+            if(onOk) {
+                onOk(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
+            }
         }
     };
 
@@ -324,10 +344,6 @@ function SelectBox<T>({
         if(!isWorkWithRealtime) {
             setTempSelectedItems([]);
         }
-
-        if(onChange) {
-            onChange([], data, setIsMoreLoading);
-        }
     };
 
     const updateData = (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => {
@@ -559,10 +575,6 @@ function SelectBox<T>({
                 } else {
                     setTempSelectedItems([]);
                 }
-
-                if(onChange) {
-                    onChange([], data, setIsMoreLoading);
-                }
             }}
         >
             <CleanIcon

+ 14 - 1
src/components/selectBox/type.ts

@@ -72,7 +72,20 @@ interface ISelectBoxProps<T> {
     onChange?: (
         selectedItems: Array<SelectedItem>,
         data: Array<T | T & SelectedItem | SelectedItem>,
-        setIsMoreLoading: (state: boolean) => void
+        setIsMoreLoading: (state: boolean) => void,
+        setIsLoading: (state: boolean) => void
+    ) => void;
+    onOk?: (
+        selectedItems: Array<SelectedItem>,
+        data: Array<T | T & SelectedItem | SelectedItem>,
+        setIsMoreLoading: (state: boolean) => void,
+        setIsLoading: (state: boolean) => void
+    ) => void;
+    onCancel?: (
+        selectedItems: Array<SelectedItem>,
+        data: Array<T | T & SelectedItem | SelectedItem>,
+        setIsMoreLoading: (state: boolean) => void,
+        setIsLoading: (state: boolean) => void
     ) => void;
     titleExtractor: (item: T & SelectedItem, index: number) => string;
     keyExtractor: (item: T & SelectedItem, index: number) => string;

+ 21 - 0
src/index.tsx

@@ -36,13 +36,34 @@ export {
 } from "./components";
 
 export type {
+    EnterMarkdownTypes,
     ITextAreaInputRef,
+    CodeMarkdownTypes,
+    TextMarkdownTypes,
     IBottomSheetRef,
+    BlockquoteTypes,
+    MarkdownObject,
     ITextInputRef,
+    MarkdownKeys,
     IDialogRef,
+    ListTypes,
     IModalRef
 } from "./components";
 
+export {
+    blockquoteMarkdown,
+    centerMarkdown,
+    enterMarkdown,
+    imageMarkdown,
+    parseMarkdown,
+    rightMarkdown,
+    codeMarkdown,
+    leftMarkdown,
+    linkMarkdown,
+    listMarkdown,
+    textMarkdown
+} from "./components/markdownViewer/util";
+
 export {
     ChevronRightIcon,
     LoadingIcon,